上傳流程核心原理
- 瀏覽器以
multipart/form-data 編碼發送 POST 請求
- Web 服務器(Nginx/Apache)接收請求體,暫存到臨時目錄
- PHP 啟動,將臨時文件信息填充
$_FILES 超全局數組
- 腳本執行驗證邏輯,通過
move_uploaded_file() 移動到永久存儲
- 請求結束,PHP 自動清理未被移動的臨時文件
表單必要屬性
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2000000">
<input type="file" name="avatar">
<button type="submit">上傳</button>
</form>
enctype="multipart/form-data":缺一不可,否則 $_FILES 為空
method="post":GET 無法上傳文件
MAX_FILE_SIZE:客戶端提示上限(隱藏字段,不可信任,服務端必驗證)
$_FILES 結構詳解
Array
(
[avatar] => Array
(
[name] => photo.jpg // 客戶端原始文件名(不可信)
[type] => image/jpeg // 客戶端聲明 MIME(不可信)
[tmp_name] => /tmp/phpA1B2C3 // 服務端臨時文件路徑(唯一可信來源)
[error] => 0 // 錯誤碼:0=成功,1-8 對應具體錯誤
[size] => 102400 // 文件大小(字節)
)
)
php.ini 關鍵配置
| 指令 | 默認 | 建議生產值 | 說明 |
file_uploads | On | On | 總開關 |
upload_max_filesize | 2M | 10M-50M | 單文件上限 |
post_max_size | 8M | > upload_max_filesize | POST 總體上限(含所有表單字段) |
upload_tmp_dir | 系統臨時目錄 | /var/tmp/php-upload | 臨時文件存放,需寫權限 |
max_file_uploads | 20 | 50 | 單請求最大文件數 |
五層安全驗證體系(由淺入深)
<?php
function secureUpload(array $file, string $destDir, array $allowTypes, int $maxSize): array
{
// 第 1 層:錯誤碼
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception(uploadErrorMsg($file['error']));
}
// 第 2 層:真實上傳文件
if (!is_uploaded_file($file['tmp_name'])) {
throw new Exception('非法文件來源');
}
// 第 3 層:MIME 類型(finfo 讀取文件頭,防偽裝)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($realType, $allowTypes, true)) {
throw new Exception("類型不允許: $realType");
}
// 第 4 層:圖片二次驗證(getimagesize 解析文件結構,防掛馬)
if (str_starts_with($realType, 'image/') && $realType !== 'image/gif') {
if (!@getimagesize($file['tmp_name'])) {
throw new Exception('無效圖片文件');
}
}
// 第 5 層:大小限制
if ($file['size'] > $maxSize) {
throw new Exception('文件過大');
}
// 生成安全文件名
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destPath = rtrim($destDir, '/') . '/' . $safeName;
// 移動文件(原子操作,跨分區自動複製+刪除)
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
throw new Exception('存儲失敗');
}
return [
'original' => $file['name'],
'saved' => $safeName,
'path' => $destPath,
'size' => $file['size'],
'type' => $realType,
];
}
function uploadErrorMsg(int $code): string
{
return match ($code) {
UPLOAD_ERR_INI_SIZE => '超過 php.ini upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => '超過表單 MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => '文件部分上傳',
UPLOAD_ERR_NO_FILE => '無文件上傳',
UPLOAD_ERR_NO_TMP_DIR => '臨時目錄不存在',
UPLOAD_ERR_CANT_WRITE => '寫入失敗',
UPLOAD_ERR_EXTENSION => '擴展阻止上傳',
default => '未知錯誤',
};
}
常用輔助函數速查
| 函數 | 用途 | 返回值 |
is_uploaded_file | 判斷是否 HTTP POST 上傳 | bool |
move_uploaded_file | 移動上傳文件(安全檢查) | bool |
file_exists | 文件/目錄是否存在 | bool |
is_writable | 目錄是否可寫 | bool |
getimagesize | 驗證圖片並獲取尺寸 | array|false |
finfo_file | 獲取真實 MIME 類型 | string |
iconv | 字符編碼轉換 | string |
進階主題
- 大文件分片上傳:前端切片 → 並發上傳 → 後端合併 → 校驗 MD5
- 異步處理管道:上傳完成即返回 → 隊列任務執行壓縮、水印、轉碼、掃毒
- 雲存儲直傳:後端簽名 Policy → 前端直傳 OSS/S3 → 回調業務服務器
- 防重複存儲:計算 SHA256 → 查詢數據庫 → 存在則返回現有路徑
- 上一篇文章: PHP 函數進階:作用域、靜態變量、變量函數、回調與內部函數分類
- 下一篇文章: PHP return 全局作用域特性詳解:包含文件返回值與腳本中止控制
Copyright ©
程式碼之家 All Rights Reserved