前端表單三大關鍵點
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2000000"> <!-- 限制 2MB -->
<input name="file" type="file">
<input type="submit" value="上傳">
</form>
| 屬性 | 說明 |
enctype="multipart/form-data" | 必須,否則 $_FILES 為空 |
MAX_FILE_SIZE | 客戶端提示上限,實際以 php.ini upload_max_filesize 為準 |
type="file" | 瀏覽器顯示「選擇檔案」按鈕 |
後端處理流程 upload.php
<?php
$uploaddir = './files/'; // 儲存目錄,需可寫
$allowExt = ['jpg','gif','bmp','jpeg','png']; // 允許副檔名
// 取得副檔名
function fileext($filename) {
return substr(strrchr($filename, '.'), 1);
}
// 產生隨機檔名:CR- + 10 位英數
function random($len = 10) {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$str = 'CR-';
for ($i = 0; $i < $len; $i++) {
$str .= $chars[mt_rand(0, strlen($chars)-1)];
}
return $str;
}
$ext = strtolower(fileext($_FILES['file']['name']));
// 1. 類型檢查
if (!in_array($ext, $allowExt)) {
exit('僅允許上傳:' . implode(', ', $allowExt));
}
// 2. 組合目標路徑(避免重名)
$filename = explode('.', $_FILES['file']['name']);
do {
$filename[0] = random(10);
$newName = implode('.', $filename);
$dest = $uploaddir . $newName;
} while (file_exists($dest));
// 3. 移動暫存檔
if (move_uploaded_file($_FILES['file']['tmp_name'], $dest)) {
if (is_uploaded_file($_FILES['file']['tmp_name'])) {
echo "<center>上傳成功!預覽:<br><img src='$dest'></center>";
echo "<br><center><a href='javascript:history.back()'>繼續上傳</a></center>";
} else {
echo '上傳驗證失敗!';
}
} else {
echo '移動檔案失敗,請檢查目錄權限。';
}
?>
關鍵函數解析
strrchr($str, '.'):取最後一個 . 及其後內容(含點)
substr(..., 1):去掉點號,得純副檔名
mt_srand((double)microtime()*1e6):播種更強隨機數(PHP 4.2+ 可省略)
$_FILES['file']['tmp_name']:暫存路徑(系統自動清理)
move_uploaded_file():安全移動,自動檢查是否為 HTTP POST 上傳
is_uploaded_file():二次驗證,防偽造
常見坑與解決
| 現象 | 原因 | 對策 |
| 上傳大檔失敗 | php.ini upload_max_filesize / post_max_size 過小 | 調大並重啟 PHP-FPM/Apache |
| 中文檔名亂碼 | 檔案系統編碼不一 | 強制隨機命名(如本例)或 iconv 轉碼 |
| 權限拒絕 | 目錄不可寫 | chmod 755 files/、確認 PHP-FPM 用戶權限 |
| 圖片無法預覽 | 路徑錯誤 / 權限 / Nginx 靜態檔案配置 | 用相對路徑、確認 location /files/ 允許存取 |
進階擴展方向
- 多檔上傳:
<input name="files[]" type="file" multiple> + 迴圈處理
- 分片/斷點續傳:Plupload / WebUploader + 後端合併分片
- AJAX 無刷新:
FormData + fetch() + 進度條 xhr.upload.onprogress
- 雲端儲存:上傳至 S3 / OSS / COS,回傳 CDN URL 入庫
- 圖片處理:Intervention Image / GD / Imagick 縮圖、水印、格式轉換
安全性清單
- ✅ 白名單驗證副檔名 + MIME 類型(
finfo_file)
- ✅ 隨機檔名 + 存放非 Web 可執行目錄
- ✅ 禁止
.php、.phtml 等腳本副檔名
- ✅ Nginx/Apache 配置
location ~ \.php$ { deny all; } 於上傳目錄
- ✅ 病毒掃描(ClamAV)或內容安全檢測
總結
檔案上傳看似簡單,實則涉及「前端編碼、PHP 配置、檔案系統權限、安全防護、效能優化」多個面向。本例以「隨機命名 + 白名單 + 移動驗證 + 圖片預覽」為核心流程,構建了最小可用的上傳閉環。掌握這套基礎後,再疊加分片、雲存、圖片處理等企業級能力,即可應對各類上傳場景。