這篇文章提供了難得的「前後端完整配合」範例,從 HTML 表單、JS 處理到 PHP 後端類別,展示一個最小可行的圖片上傳功能閉環。
upphoto.php<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>圖片上傳測試</title>
</head>
<body>
<form action="upphoto.php?action=act" method="post" enctype="multipart/form-data">
圖片來源:<input type="file" name="photo" accept="image/*">
<input type="submit" name="submit" value="上傳">
</form>
<?php
if (isset($_GET['action']) && $_GET['action'] === 'act') {
require_once 'uppoo.php';
$up = new upphoto();
$up->get_ph_tmpname($_FILES['photo']['tmp_name']);
$up->get_ph_type($_FILES['photo']['type']);
$up->get_ph_size($_FILES['photo']['size']);
$up->get_ph_name($_FILES['photo']['name']);
$up->save();
}
?>
</body>
</html>
uppoo.php<?php
class upphoto {
// 設定屬性
public $previewsize = 0.125; // 預覽縮放比例
public $preview = 0; // 是否生成預覽
public $ph_path = "./userimg/"; // 儲存路徑
public $al_ph_type = [ // 允許 MIME
'image/jpg','image/jpeg','image/png',
'image/pjpeg','image/gif','image/bmp','image/x-png'
];
public $al_ph_size = 1000000; // 1MB
// 執行時屬性
public $ph_name; // 完整儲存路徑
public $ph_tmp_name; // 暫存路徑
public $ph_type; // MIME 類型
public $ph_size; // 檔案大小
public $imgsize; // 圖片尺寸資訊
public $datetime; // 時間戳 (作為檔名基礎)
public function __construct() {
$this->set_datatime();
}
private function set_datatime() {
$this->datetime = date("YmdHis");
}
// --- Setter 方法群 ---
public function get_ph_type($t) { $this->ph_type = $t; }
public function get_ph_size($s) { $this->ph_size = $s; }
public function get_ph_tmpname($t) {
$this->ph_tmp_name = $t;
$this->imgsize = getimagesize($t); // 預先讀取尺寸
}
public function get_ph_name($n) {
// 取得副檔名 (含點號)
$ext = strrchr($n, ".");
$this->ph_name = $this->ph_path . $this->datetime . $ext;
return $this->ph_name;
}
// --- 核心驗證流程 ---
private function check_path() {
if (!file_exists($this->ph_path)) mkdir($this->ph_path, 0755, true);
}
private function check_size() {
if ($this->ph_size > $this->al_ph_size) {
$this->showerror("上傳圖片超過 1MB");
}
}
private function check_type() {
if (!in_array($this->ph_type, $this->al_ph_type)) {
$this->showerror("上傳圖片類型錯誤");
}
}
private function up_photo() {
if (!move_uploaded_file($this->ph_tmp_name, $this->ph_name)) {
$this->showerror("上傳檔案出錯");
}
}
// --- 輔助功能 ---
public function showphoto() {
if ($this->preview == 1) {
$w = $this->imgsize[0];
$h = $this->imgsize[1];
if ($w > 2000) { // 超大圖自動縮小預覽
$w *= $this->previewsize;
$h *= $this->previewsize;
}
echo "<img src='{$this->ph_name}' width='{$w}' height='{$h}'>";
}
}
private function showerror($msg) {
echo "<script>alert('$msg'); history.back();</script>";
exit;
}
// --- 總管方法 ---
public function save() {
$this->check_path();
$this->check_size();
$this->check_type();
$this->up_photo();
$this->showphoto();
}
}
?>
採用分散的 setter 方法收集 $_FILES 資料,而非在建構子一次處理,優點是:
$ext = strrchr($phname, "."); // 從右側找第一個點 $ph_name = $ph_path . $datetime . $ext; // 產生: ./userimg/20240115143025.jpg
注意:高併發下可能衝突,建議加入 microtime(true) 或 uniqid()。
上傳成功後直接輸出 <img> 標籤,配合 getimagesize() 取得的尺寸資訊,實現無刷新預覽 (需前端配合 iframe 或 AJAX)。
<!-- 前端: upphoto_modern.php -->
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" name="photo" id="photo" accept="image/*">
<button type="submit">上傳</button>
<div id="preview"></div>
</form>
<script>
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('photo', document.getElementById('photo').files[0]);
const res = await fetch('upload_api.php', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.status === 'success') {
document.getElementById('preview').innerHTML =
`<img src="${data.url}" style="max-width:300px">`;
} else {
alert(data.message);
}
});
</script>
<?php
// 後端: upload_api.php
header('Content-Type: application/json');
require_once 'uppoo.php';
$up = new upphoto();
$up->preview = 0; // API 模式不輸出 HTML
try {
$up->get_ph_tmpname($_FILES['photo']['tmp_name']);
$up->get_ph_type($_FILES['photo']['type']);
$up->get_ph_size($_FILES['photo']['size']);
$up->get_ph_name($_FILES['photo']['name']);
$up->save();
echo json_encode([
'status' => 'success',
'url' => $up->ph_name,
'size' => $up->imgsize
]);
} catch (Throwable $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
| 風險 | 對策 |
|---|---|
| MIME 偽裝 | 使用 finfo_file(FILEINFO_MIME_TYPE) 驗證真實類型 |
| 腳本偽裝成圖片 | 上傳目錄設定 php_flag engine off 或移至 Webroot 外 |
| 檔名路徑遍歷 | 使用 basename() + 白名單副檔名驗證 |
| 大檔案 DoS | 設定 upload_max_filesize、post_max_size、Nginx client_max_body_size |
| 圖片炸彈 | 使用 getimagesize() 檢查寬高限制 (如 < 5000px) |
這個範例雖然結構簡單,但完整展示了「表單提交 → 後端驗證 → 檔案儲存 → 回應前端」的標準流程。理解其運作機制,是進階學習雲端上傳、分片上傳、圖片處理管線的基石。