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

PHP文件上傳完整實現:前端表單與後端安全處理指南

     編輯:PHP 相關知識

PHP圖片上傳全流程:從表單構建到安全存儲的最佳實踐

文件上傳是Web開發高風險操作,涉及類型偽造、路徑遍歷、腳本執行、大小繞過等安全隱患。本文提供完整的前後端代碼,重點講解安全驗證、隨機命名、目錄隔離、錯誤處理等生產級要點。

前端表單:必要屬性與用戶體驗

<form action="upload.php" method="post" enctype="multipart/form-data" id="uploadForm">
  <input type="hidden" name="MAX_FILE_SIZE" value="5242880"> <!-- 5MB 客戶端提示 -->
  <div class="form-group">
    <label>選擇圖片 (JPG/PNG/GIF, <5MB)</label>
    <input type="file" name="image" id="image" accept="image/jpeg,image/png,image/gif" required>
    <div class="preview" id="preview"></div> <!-- JS預覽 -->
  </div>
  <button type="submit" class="btn btn-primary">上傳</button>
  <div class="progress" style="display:none"><div class="progress-bar" id="progressBar"></div></div>
</form>

<script>
// 簡單預覽與進度條 (需配合 XMLHttpRequest 或 fetch)
document.getElementById('image').addEventListener('change', function(e) {
  const file = e.target.files[0];
  if (file) {
    document.getElementById('preview').innerHTML = '<img src="'+URL.createObjectURL(file)+'" style="max-width:200px">';
  }
});
</script>

後端核心處理邏輯:upload.php

<?php
// === 配置區 ===
const UPLOAD_ROOT = __DIR__ . '/uploads';      // 存儲根目錄(非Web可訪問目錄更安全)
const MAX_SIZE    = 5 * 1024 * 1024;           // 5MB
const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/gif'];
const ALLOWED_EXT  = ['jpg', 'jpeg', 'png', 'gif'];

// === 初始化 ===
header('Content-Type: application/json; charset=utf-8');
$response = ['code' => 0, 'msg' => '', 'data' => null];

// 請求方法檢查
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    $response['msg'] = '僅支持POST請求';
    exit(json_encode($response));
}

// 文件上傳檢查
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
    $errors = [
        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  => '擴展中斷上傳',
    ];
    $response['msg'] = $errors[$_FILES['image']['error'] ?? '未知上傳錯誤'];
    exit(json_encode($response));
}

$file = $_FILES['image'];

// 1. 文件大小二次檢查 (防繞過前端)
if ($file['size'] > MAX_SIZE) {
    $response['msg'] = '文件大小超過限制 (5MB)';
    exit(json_encode($response));
}

// 2. MIME類型檢測 (finfo 比 $_FILES['type'] 可靠)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, ALLOWED_MIME, true)) {
    $response['msg'] = '文件類型不允許,僅支持 JPG/PNG/GIF';
    exit(json_encode($response));
}

// 3. 擴展名驗證 (雙重保險)
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ALLOWED_EXT, true)) {
    $response['msg'] = '文件擴展名不允許';
    exit(json_encode($response));
}

// 4. 圖片完整性驗證 (防偽裝腳本)
$imgInfo = @getimagesize($file['tmp_name']);
if (!$imgInfo || !in_array($imgInfo[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF])) {
    $response['msg'] = '非法圖片文件或文件損壞';
    exit(json_encode($response));
}

// 5. 生成安全存儲路徑
$dateDir = date('Y/m/d');                    // 按日期分目錄,避免單目錄文件過多
$saveDir = UPLOAD_ROOT . '/' . $dateDir;
if (!is_dir($saveDir)) {
    if (!mkdir($saveDir, 0755, true)) {
        $response['msg'] = '創建存儲目錄失敗';
        exit(json_encode($response));
    }
    // 安全:禁止腳本執行
    file_put_contents($saveDir . '/.htaccess', "SetHandler default-handler\nOptions -ExecCGI\nphp_flag engine off");
}

$filename = bin2hex(random_bytes(16)) . '.' . $ext; // 32位隨機十六進制名
$savePath = $saveDir . '/' . $filename;
$webPath  = '/uploads/' . $dateDir . '/' . $filename; // Web訪問路徑

// 6. 移動文件 (move_uploaded_file 防止臨時文件殘留)
if (!move_uploaded_file($file['tmp_name'], $savePath)) {
    $response['msg'] = '文件保存失敗';
    exit(json_encode($response));
}

// 7. 設置權限 (僅讀,防篡改)
chmod($savePath, 0644);

// 8. 可選:生成縮略圖、記錄數據庫、掃描病毒...
// generate_thumb($savePath);
// db_insert('uploads', ['path'=>$webPath, 'mime'=>$mime, 'size'=>$file['size']]);

// 成功響應
$response['code'] = 1;
$response['msg'] = '上傳成功';
$response['data'] = [
    'url' => $webPath,
    'path' => $savePath,
    'size' => $file['size'],
    'mime' => $mime,
    'width' => $imgInfo[0],
    'height' => $imgInfo[1],
];
echo json_encode($response);

關鍵安全點總結

進階擴展:分片上傳與斷點續傳

大文件(>100MB)建議採用分片上傳:

  1. 前端使用 spark-md5 計算文件MD5,Blob.slice() 分片
  2. 後端提供 /upload/chunk 接收分片,/upload/merge 合併驗證
  3. 支持秒傳:檢查MD5是否已存在,存在則直接返回成功
  4. 斷點續傳:記錄已上傳分片索引,續傳時跳過

成熟方案推薦:Uppy (前端) + tus-php (後端,實現TUS協議)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
風險防護措施代碼體現
類型偽造finode + getimagesize 雙重驗證$finfo->file() + getimagesize()
腳本執行存儲目錄禁用PHP/腳本執行 + 隨機文件名.htaccess + random_bytes()
路徑遍歷basename + 固定目錄結構,不使用用戶輸入路徑$dateDir 固定格式
大小繞過服務端二次檢查 $_FILES['size'] + php.ini 限制MAX_SIZE 常量
文件殘留move_uploaded_file 自動清理臨時文件move_uploaded_file()
併發衝突隨機文件名 (16字節 = 2^128 空間)bin2hex(random_bytes(16))