首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP $_FILES 上傳錯誤代碼完整對照表與處理最佳實踐

     編輯:PHP 綜合知識

錯誤代碼常量對照表

常量值含義典型原因
UPLOAD_ERR_OK0上傳成功—
UPLOAD_ERR_INI_SIZE1超過 php.ini upload_max_filesize文件過大,需調整配置
UPLOAD_ERR_FORM_SIZE2超過表單 MAX_FILE_SIZE 隱藏域前端限制,可移除或增大
UPLOAD_ERR_PARTIAL3文件只有部分被上傳網絡中斷、客戶端取消
UPLOAD_ERR_NO_FILE4沒有文件被上傳用戶未選文件直接提交
UPLOAD_ERR_NO_TMP_DIR6找不到臨時目錄php.ini upload_tmp_dir 未配置或無權限
UPLOAD_ERR_CANT_WRITE7文件寫入失敗磁盤滿、權限不足、SELinux
UPLOAD_ERR_EXTENSION8PHP 擴展阻止上傳如 suhosin、自定義擴展攔截

注意:值 5 保留未使用,歷史上曾表示「臨時目錄丟失」但已合併到 6。

標準處理代碼模板

<?php
function handleUpload(array $file, array $allowedTypes = [], int $maxSize = 2 * 1024 * 1024): array {
    // 1. 檢查錯誤代碼
    if ($file['error'] !== UPLOAD_ERR_OK) {
        return ['success' => false, 'code' => $file['error'], 'message' => uploadErrorMessage($file['error'])];
    }
    
    // 2. 檢查文件大小(雙重保險)
    if ($file['size'] > $maxSize) {
        return ['success' => false, 'code' => UPLOAD_ERR_INI_SIZE, 'message' => '文件超過大小限制'];
    }
    
    // 3. 檢查 MIME 類型(不信任瀏覽器提供的 type)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!empty($allowedTypes) && !in_array($mime, $allowedTypes, true)) {
        return ['success' => false, 'code' => 400, 'message' => '文件類型不允許: ' . $mime];
    }
    
    // 4. 檢查副檔名
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
    if (!in_array($ext, $allowedExt, true)) {
        return ['success' => false, 'code' => 400, 'message' => '副檔名不允許'];
    }
    
    // 5. 生成安全文件名並移動
    $newName = bin2hex(random_bytes(16)) . '.' . $ext;
    $destDir = __DIR__ . '/uploads/' . date('Y/m/d');
    if (!is_dir($destDir)) mkdir($destDir, 0755, true);
    $destPath = $destDir . '/' . $newName;
    
    if (!move_uploaded_file($file['tmp_name'], $destPath)) {
        return ['success' => false, 'code' => UPLOAD_ERR_CANT_WRITE, 'message' => '文件移動失敗'];
    }
    
    return [
        'success' => true,
        'path'    => $destPath,
        'url'     => '/uploads/' . date('Y/m/d') . '/' . $newName,
        'size'    => $file['size'],
        'mime'    => $mime,
    ];
}

function uploadErrorMessage(int $code): string {
    return match ($code) {
        UPLOAD_ERR_INI_SIZE      => '文件超過服務器限制 (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     => '被 PHP 擴展攔截',
        default                  => '未知上傳錯誤',
    };
}

// 使用
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $result = handleUpload($_FILES['avatar'], ['image/jpeg', 'image/png', 'image/gif']);
    header('Content-Type: application/json');
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
}
?>

關鍵配置檢查清單

配置項推薦值說明
file_uploadsOn啟用文件上傳
upload_max_filesize10M單文件最大
post_max_size12MPOST 總大小,必須 ≥ upload_max_filesize
max_file_uploads20單次請求最大文件數
upload_tmp_dir/tmp/php_uploads專用臨時目錄,確保可寫
max_execution_time60大文件上傳需足夠時間
memory_limit128M處理圖片縮略圖等需內存

進階:分片上傳大文件(斷點續傳)

<?php
// 前端將大文件切片,每片發送:chunk_index, total_chunks, file_hash, filename
$chunkIndex = (int)$_POST['chunk_index'];
$totalChunks = (int)$_POST['total_chunks'];
$fileHash = $_POST['file_hash']; // 全文件 SHA256
$filename = basename($_POST['filename']);

$chunkDir = sys_get_temp_dir() . "/chunks/$fileHash";
if (!is_dir($chunkDir)) mkdir($chunkDir, 0755, true);
$chunkPath = "$chunkDir/$chunkIndex";

// 接收分片
move_uploaded_file($_FILES['chunk']['tmp_name'], $chunkPath);

// 判斷是否為最後一片
if ($chunkIndex === $totalChunks - 1) {
    // 合併
    $finalPath = "/uploads/$fileHash-" . $filename;
    $fp = fopen($finalPath, 'wb');
    for ($i = 0; $i < $totalChunks; $i++) {
        fwrite($fp, file_get_contents("$chunkDir/$i"));
    }
    fclose($fp);
    
    // 清理分片
    array_map('unlink', glob("$chunkDir/*"));
    rmdir($chunkDir);
    
    echo json_encode(['success' => true, 'path' => $finalPath]);
} else {
    echo json_encode(['success' => true, 'message' => "分片 $chunkIndex 接收完成"]);
}
?>

安全加固要點

  • ✅ 使用 finfo 驗證真實 MIME,不信任 $_FILES['type']
  • ✅ 文件名用隨機哈希重命名,防止路徑遍歷、覆蓋、腳本執行
  • ✅ 上傳目錄設置 php_flag engine off / Nginx location ~ \.php$ { deny all; } 禁用腳本執行
  • ✅ 設置 Content-Security-Policy 限制上傳域
  • ✅ 病毒掃描集成(ClamAV 等)
  • ✅ 限制上傳頻率、大小、類型
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved