第十二章-PHP文件上傳

第十二章-PHP文件上傳

一,文件上傳原理

一、HTTP協議與文件上傳

1. 請求體結構
  • 當表單設置enctype="multipart/form-data"時,瀏覽器會將表單數據編碼為多部分(multipart)格式

  • Boundary分隔符:隨機生成的字符串(如----WebKitFormBoundaryABC123),用于分隔表單字段和文件內容。

  • 請求頭示例

    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
    
  • 請求體示例

    ------WebKitFormBoundaryABC123
    Content-Disposition: form-data; name="userfile"; filename="photo.jpg"
    Content-Type: image/jpeg[文件二進制數據]
    ------WebKitFormBoundaryABC123--
    
2. 數據分塊傳輸
  • 大文件上傳時,HTTP協議支持分塊傳輸(Transfer-Encoding: chunked),但PHP會自動重組完整數據。

二、PHP服務端處理機制

1. 接收與解析
  • 數據流處理:PHP通過SAPI(Server API)接收原始HTTP請求數據。
  • 臨時文件生成
    • PHP將上傳的文件內容寫入臨時目錄(sys_get_temp_dir()),默認路徑由php.iniupload_tmp_dir指定。
    • 臨時文件名隨機生成(如/tmp/phpA3b4cD),與原始文件名無關
2. $_FILES數組結構
  • PHP自動解析請求體,提取文件信息并填充到$_FILES數組中:

    $_FILES['userfile'] = ['name'     => 'photo.jpg',        // 客戶端原始文件名'type'     => 'image/jpeg',       // 瀏覽器提供的MIME類型(可能被篡改)'tmp_name' => '/tmp/phpA3b4cD',   // 臨時文件路徑'error'    => UPLOAD_ERR_OK,      // 錯誤碼'size'     => 102400              // 文件大小(字節)
    ];
    
3. 臨時文件生命周期
  • 自動清理:如果未調用move_uploaded_file(),腳本結束時PHP自動刪除臨時文件。
  • 手動管理:可通過register_shutdown_function()自定義清理邏輯。

三、核心安全機制

1. move_uploaded_file()的安全性
  • 防路徑注入:自動檢查目標路徑是否包含../等非法字符。
  • 防偽造上傳:驗證文件是否通過HTTP POST上傳(避免直接操作臨時文件)。
2. 文件類型驗證
  • MIME類型檢測

    • 使用finfo_file()(基于文件內容簽名,非擴展名)。

    • 示例:檢測JPEG文件的真實MIME類型:

      $finfo = finfo_open(FILEINFO_MIME_TYPE);
      $mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
      // 返回 'image/jpeg' 而非客戶端提供的可能偽造值
      
  • 擴展名白名單

    $allowedExts = ['jpg', 'jpeg', 'png'];
    $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExts)) {die("非法文件擴展名");
    }
    
3. 防目錄遍歷攻擊
  • 使用basename()過濾文件名中的路徑符號:

    $safeFilename = basename($_FILES['file']['name']);
    

四、服務器配置詳解(php.ini)

配置項默認值作用
file_uploadsOn是否允許HTTP文件上傳
upload_max_filesize2M單個文件最大大小
post_max_size8MPOST請求最大數據量(必須大于上傳限制)
upload_tmp_dir系統臨時目錄臨時文件存儲路徑
max_file_uploads20單次請求允許上傳的最大文件數

配置關系

post_max_size >= upload_max_filesize * max_file_uploads

五、完整上傳流程

  1. 客戶端提交表單
    • 瀏覽器將文件編碼為multipart/form-data格式。
    • 分塊傳輸至服務器(對用戶透明)。
  2. 服務端接收數據
    • Web服務器(如Nginx/Apache)接收原始數據流。
    • PHP SAPI解析請求體,生成臨時文件。
  3. PHP腳本處理
    • 訪問$_FILES獲取文件信息。
    • 執行錯誤檢查、安全驗證、文件移動。
  4. 文件持久化存儲
    • 使用move_uploaded_file()將文件移至安全目錄。
    • 建議存儲路徑與Web根目錄分離(如/var/uploads/)。

六、高級話題

1. 大文件上傳優化
  • 調整配置

    upload_max_filesize = 2G
    post_max_size = 2G
    max_execution_time = 3600
    
  • 分片上傳:通過JavaScript實現文件分片,服務端重組。

2. 異步上傳
  • 使用AJAX + FormData對象實現無刷新上傳:

    let formData = new FormData();
    formData.append('file', fileInput.files[0]);
    fetch('/upload.php', { method: 'POST', body: formData });
    
3. 防御0day漏洞
  • 禁用危險函數:確保上傳目錄不可執行PHP代碼。
  • 內容二次渲染:對圖片文件進行GD庫處理,破壞潛在惡意代碼。

七、錯誤處理深度解析

  • 自定義錯誤消息

    $phpFileUploadErrors = [0 => '成功',1 => '文件超過php.ini限制',2 => '文件超過表單限制',3 => '文件僅部分上傳',4 => '未選擇文件',6 => '缺少臨時文件夾',7 => '寫入磁盤失敗',8 => 'PHP擴展阻止了上傳',
    ];
    
  • 錯誤觸發場景

    • UPLOAD_ERR_INI_SIZE:文件大小超過upload_max_filesize
    • UPLOAD_ERR_PARTIAL:網絡中斷導致上傳不完整。

二,表單制作

一、基礎表單結構

1.必要屬性配置
<form action="upload.php" method="POST" enctype="multipart/form-data"><input type="file" name="userfile" required><input type="submit" value="上傳">
</form>

關鍵要素

  • method="POST":必須使用POST方法傳輸文件
  • enctype="multipart/form-data":啟用二進制流傳輸模式
  • required:HTML5客戶端必填驗證
2.多文件上傳支持
<input type="file" name="files[]" multiple accept=".jpg,.png">

特性說明

  • multiple:允許選擇多個文件
  • accept:限制可選文件類型(客戶端過濾)

二、高級表單功能

1. 文件類型限制
<!-- 僅允許圖片文件 -->
<input type="file" accept="image/*"><!-- 指定具體擴展名 -->
<input type="file" accept=".pdf,.doc,.docx">
2. 文件大小提示
<input type="file" onchange="checkSize(this)">
<script>
function checkSize(input) {const maxSize = 2 * 1024 * 1024; // 2MBif (input.files[0].size > maxSize) {alert('文件大小超過限制');input.value = ''; // 清空選擇}
}
</script>
3. 拖拽上傳實現
<div id="drop-zone" style="border:2px dashed #ccc; padding:20px;">拖拽文件至此區域
</div><script>
const dropZone = document.getElementById('drop-zone');dropZone.addEventListener('dragover', (e) => {e.preventDefault();dropZone.style.borderColor = '#666';
});dropZone.addEventListener('drop', (e) => {e.preventDefault();const files = e.dataTransfer.files;// 處理文件上傳邏輯
});
</script>

三、安全增強配置

1. 隱藏域Token驗證
<input type="hidden" name="csrf_token" value="<?= $_SESSION['token'] ?>">

后端驗證

if ($_POST['csrf_token'] !== $_SESSION['token']) {die("非法請求");
}
2. 文件名過濾處理
// 刪除特殊字符
$cleanName = preg_replace("/[^\w\.]/", '', $_FILES['file']['name']);
// 防止覆蓋
$filename = uniqid().'_'.$cleanName;

三,$_FILES變量

一、$_FILES變量基礎結構

$_FILES是PHP自動生成的超全局數組,用于存儲通過HTTP POST上傳的文件信息。其結構為多維數組,典型結構如下:

$_FILES = ['file_field_name' => ['name'     => 'example.jpg',    // 客戶端原始文件名'type'     => 'image/jpeg',     // 瀏覽器報告的MIME類型'tmp_name' => '/tmp/php3h4j8h', // 服務器上的臨時文件路徑'error'    => 0,                // 上傳錯誤代碼'size'     => 102400            // 文件大小(字節)]
];

二、核心字段深度解析

1. name字段
  • 來源:客戶端文件系統原始名稱

  • 風險:可能包含特殊字符或路徑信息(如../../shell.php

  • 安全處理

    // 過濾非法字符并提取安全文件名
    $safe_name = basename($_FILES['file']['name']);
    $clean_name = preg_replace('/[^\w\.-]/', '', $safe_name);
    
2. type字段
  • 來源:瀏覽器根據文件擴展名猜測的類型

  • 可靠性:極易偽造(如將.exe文件重命名為.jpg)

  • 驗證方法

    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $real_mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
    finfo_close($finfo);
    
3. tmp_name字段
  • 特性
    • 臨時文件路徑由php.iniupload_tmp_dir配置決定
    • 文件命名規則為phpXXXXXX(X為隨機字符)
  • 生命周期
    • 腳本執行結束后自動刪除
    • 必須使用move_uploaded_file()轉移文件
4. error字段
  • 錯誤代碼對照表

    常量說明
    UPLOAD_ERR_OK0上傳成功
    UPLOAD_ERR_INI_SIZE1超過php.ini大小限制
    UPLOAD_ERR_FORM_SIZE2超過表單MAX_FILE_SIZE值
    UPLOAD_ERR_PARTIAL3文件只有部分被上傳
    UPLOAD_ERR_NO_FILE4沒有文件被上傳
    UPLOAD_ERR_NO_TMP_DIR6找不到臨時文件夾
    UPLOAD_ERR_CANT_WRITE7文件寫入失敗
    UPLOAD_ERR_EXTENSION8PHP擴展阻止上傳
  • 錯誤處理示例

    $error_messages = [0 => 'Success',1 => 'File exceeds php.ini upload_max_filesize',2 => 'File exceeds form MAX_FILE_SIZE',3 => 'Partial upload',4 => 'No file uploaded',6 => 'Missing temporary directory',7 => 'Failed to write to disk',8 => 'PHP extension blocked upload'
    ];if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {die($error_messages[$_FILES['file']['error']]);
    }
    
5. size字段
  • 單位:字節(1MB = 1,048,576字節)

  • 驗證示例

    $max_size = 5 * 1024 * 1024; // 5MB
    if ($_FILES['file']['size'] > $max_size) {die("文件大小超過5MB限制");
    }
    

三、保存上傳文件

1. is_uploaded_file()

核心作用

  • 驗證指定文件是否通過HTTP POST上傳
  • 防止偽造文件路徑攻擊

函數原型

bool is_uploaded_file(string $filename)

使用場景

// 驗證臨時文件合法性
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {die("非法文件來源");
}

安全機制

  • 檢查文件路徑是否在upload_tmp_dir目錄下
  • 驗證文件名匹配PHP臨時文件命名規則(如phpXXXXXX
  • 防止攻擊者通過偽造路徑訪問系統文件

典型錯誤用法

// 錯誤:直接使用$_FILES中的原始名稱
$tmp = '/tmp/' . $_FILES['file']['name']; 
if (file_exists($tmp)) { ... } // 存在路徑注入風險
2. move_uploaded_file()

核心作用

  • 安全移動上傳的臨時文件到目標位置
  • 兼具is_uploaded_file()驗證功能

函數原型

bool move_uploaded_file(string $from, string $to)

使用規范

$safe_dir = '/var/www/uploads/';
$new_name = uniqid() . '_' . basename($_FILES['file']['name']);if (move_uploaded_file($_FILES['file']['tmp_name'], $safe_dir . $new_name
)) {// 成功處理
} else {// 失敗處理
}

安全特性

  1. 自動執行is_uploaded_file()驗證
  2. 防止路徑遍歷攻擊(自動處理../
  3. 原子操作:移動失敗時不會殘留部分文件

與普通移動函數的對比

特性move_uploaded_file()rename()/copy()
自動安全驗證???
跨設備移動支持???
保持文件權限???
防止路徑遍歷???

雙函數協作流程圖

通過
失敗
成功
失敗
上傳請求
is_uploaded_file驗證
move_uploaded_file移動
終止處理
文件持久化
錯誤處理

最佳實踐示例
function validateUpload($file) {// 錯誤檢查if ($file['error'] !== UPLOAD_ERR_OK) return false;// 臨時文件驗證if (!is_uploaded_file($file['tmp_name'])) return false;// MIME類型檢測$finfo = finfo_open(FILEINFO_MIME_TYPE);$mime = finfo_file($finfo, $file['tmp_name']);finfo_close($finfo);if (!in_array($mime, ['image/jpeg', 'image/png'])) return false;// 擴展名驗證$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, ['jpg', 'jpeg', 'png'])) return false;// 文件大小限制if ($file['size'] > 5*1024*1024) return false;// 內容安全檢查(示例:圖片驗證)$image = @imagecreatefromjpeg($file['tmp_name']);if (!$image) return false;imagedestroy($image);return true;
}

四、調試技巧

1. 打印完整結構
echo '<pre>' . print_r($_FILES, true) . '</pre>';
2. 臨時文件檢查
if (file_exists($_FILES['file']['tmp_name'])) {echo '臨時文件大小: ' . filesize($_FILES['file']['tmp_name']);
} else {echo '臨時文件已消失';
}
3. 上傳限制檢測
echo 'PHP最大上傳: ' . ini_get('upload_max_filesize');
echo 'POST最大大小: ' . ini_get('post_max_size');
echo '臨時目錄: ' . sys_get_temp_dir();

五、常見問題解決

問題1:$_FILES數組為空

  • 檢查php.inifile_uploads是否開啟
  • 驗證表單enctype="multipart/form-data"
  • 檢查Web服務器配置(如Nginx的client_max_body_size

問題2:部分文件上傳失敗

  • 確認upload_tmp_dir有足夠權限(至少755)
  • 檢查磁盤空間是否充足
  • 監控max_file_uploads配置

問題3:大文件上傳中斷

  • 調整以下配置:

    upload_max_filesize = 256M
    post_max_size = 257M
    max_execution_time = 3600
    max_input_time = 3600
    memory_limit = 512M
    

總結

  1. 永遠不要信任$_FILES中的客戶端數據
  2. 必須進行雙重驗證(MIME類型+擴展名+內容檢查)
  3. 使用move_uploaded_file()而非copy()rename()
  4. 上傳目錄設置為不可執行(chmod 755 uploads/
  5. 定期清理舊文件(通過cron作業)

四,多文件上傳

一、前端表單設置

<form action="upload.php" method="post" enctype="multipart/form-data"><input type="file" name="user_files[]" multiple accept=".jpg,.png"><input type="submit" value="批量上傳">
</form>

關鍵點

  • name="user_files[]":必須使用數組形式命名
  • multiple:啟用多選支持(HTML5特性)
  • accept:限制可選文件類型(客戶端過濾)

二、后端文件數據結構

1. 原生$_FILES結構
$_FILES = ['user_files' => ['name'     => ['a.jpg', 'b.png'],    // 文件名數組'type'     => ['image/jpeg', 'image/png'],  'tmp_name' => ['/tmp/phpX1', '/tmp/phpX2'],'error'    => [0, 0],                // 錯誤碼數組'size'     => [102400, 204800]        // 大小數組]
];
2. 重組為易用格式
$files = [];
$fileCount = count($_FILES['user_files']['name']);for ($i = 0; $i < $fileCount; $i++) {$files[] = ['name'     => $_FILES['user_files']['name'][$i],'type'     => $_FILES['user_files']['type'][$i],'tmp_name' => $_FILES['user_files']['tmp_name'][$i],'error'    => $_FILES['user_files']['error'][$i],'size'     => $_FILES['user_files']['size'][$i]];
}

重組后結構

$files = [['name' => 'a.jpg','type' => 'image/jpeg','tmp_name' => '/tmp/phpX1','error' => 0,'size' => 102400],['name' => 'b.png','type' => 'image/png','tmp_name' => '/tmp/phpX2','error' => 0,'size' => 204800]
];

三、完整處理流程

1. 驗證上傳狀態
if (empty($_FILES['user_files']['tmp_name'][0])) {die("未選擇任何文件");
}
2. 遍歷處理每個文件
$uploadResults = [];
$allowedTypes = ['image/jpeg', 'image/png'];
$maxFileSize = 2 * 1024 * 1024; // 2MB
$uploadDir = __DIR__ . '/uploads/';foreach ($files as $index => $file) {try {// 檢查上傳錯誤if ($file['error'] !== UPLOAD_ERR_OK) {throw new Exception("文件{$index}上傳失敗,錯誤碼:{$file['error']}");}// 驗證MIME類型$finfo = finfo_open(FILEINFO_MIME_TYPE);$realMime = finfo_file($finfo, $file['tmp_name']);finfo_close($finfo);if (!in_array($realMime, $allowedTypes)) {throw new Exception("文件{$index}類型不合法");}// 驗證擴展名$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {throw new Exception("文件{$index}擴展名不合法");}// 驗證大小if ($file['size'] > $maxFileSize) {throw new Exception("文件{$index}超過大小限制");}// 生成唯一文件名$safeName = md5(uniqid() . $file['name']) . '.' . $ext;$targetPath = $uploadDir . $safeName;// 移動文件if (!move_uploaded_file($file['tmp_name'], $targetPath)) {throw new Exception("文件{$index}保存失敗");}$uploadResults[] = ['original' => $file['name'],'saved_as' => $safeName,'status' => 'success'];} catch (Exception $e) {$uploadResults[] = ['original' => $file['name'],'error' => $e->getMessage(),'status' => 'failed'];}
}
3. 輸出結果
echo json_encode(['total' => count($files),'success' => count(array_filter($uploadResults, fn($item) => $item['status'] === 'success')),'results' => $uploadResults
]);

四、高級處理技巧

1. 并發上傳優化
// 使用PHP的并行處理擴展(需安裝parallel)
$parallel = new \parallel\Runtime();
$futures = [];foreach ($files as $file) {$futures[] = $parallel->run(function($file) {// 文件處理邏輯}, [$file]);
}// 收集結果
$results = array_map(fn($f) => $f->value(), $futures);
2. 進度監控實現
// 前端JavaScript
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => {const percent = Math.round((e.loaded / e.total) * 100);progressBar.style.width = percent + '%';
});
3. 斷點續傳支持

實現步驟

  1. 前端分片文件(使用Blob.slice())
  2. 服務端記錄已接收分片
  3. 合并分片文件:
// 合并示例
$totalChunks = 5;
$finalFile = 'merged_file.zip';for ($i=1; $i<=$totalChunks; $i++) {$chunk = file_get_contents("chunk_{$i}.part");file_put_contents($finalFile, $chunk, FILE_APPEND);
}

五、安全防護策略

1. 防御DDoS攻擊
// 限制并發上傳數量
if (count($files) > 10) {http_response_code(429);die("一次最多上傳10個文件");
}
2. 病毒掃描集成
// 使用ClamAV掃描
$clamscan = '/usr/bin/clamscan';
$output = shell_exec("$clamscan --no-summary $targetPath");
if (strpos($output, 'OK') === false) {unlink($targetPath);throw new Exception("文件感染病毒");
}
3. 敏感內容檢測
// 檢查圖片是否包含裸露內容(示例使用NSFW.js)
$imageData = file_get_contents($targetPath);
$nsfwCheck = shell_exec("node nsfw-check.js $imageData");
if ($nsfwCheck > 0.7) {unlink($targetPath);throw new Exception("檢測到違規內容");
}

六、服務器配置優化

php.ini關鍵參數
; 允許同時上傳的文件數
max_file_uploads = 20; 單個文件最大尺寸
upload_max_filesize = 50M; POST數據最大尺寸
post_max_size = 55M; 腳本最大執行時間
max_execution_time = 1800
Nginx配置示例
client_max_body_size 55M;
client_body_temp_path /var/nginx/client_temp;
client_body_in_file_only clean;

七、錯誤排查指南

現象可能原因解決方案
$_FILES數組為空表單未設置enctype檢查表單enctype屬性
部分文件上傳失敗臨時目錄權限不足chmod 755 /tmp
文件名亂碼編碼不一致使用mb_convert_encoding轉換
大文件上傳中斷超時設置過小調整max_execution_time
無法生成縮略圖GD庫未安裝安裝php-gd擴展

八、完整類封裝示例

class MultiFileUploader {private $uploadDir;private $allowedMimes;private $maxSize;public function __construct($uploadDir, $allowedMimes, $maxSize) {$this->uploadDir = rtrim($uploadDir, '/') . '/';$this->allowedMimes = $allowedMimes;$this->maxSize = $maxSize;$this->createUploadDir();}private function createUploadDir() {if (!is_dir($this->uploadDir)) {mkdir($this->uploadDir, 0755, true);}}public function process($fileField) {$files = $this->reorganizeFiles($_FILES[$fileField]);$results = [];foreach ($files as $file) {try {$this->validateFile($file);$filename = $this->generateFilename($file);$this->moveFile($file['tmp_name'], $filename);$results[] = $this->successResult($file, $filename);} catch (Exception $e) {$results[] = $this->errorResult($file, $e);}}return $results;}private function reorganizeFiles($files) {$organized = [];foreach ($files as $key => $values) {foreach ($values as $index => $value) {$organized[$index][$key] = $value;}}return $organized;}// ...其他方法實現...
}// 使用示例
$uploader = new MultiFileUploader(__DIR__ . '/uploads',['image/jpeg', 'image/png'],2 * 1024 * 1024
);
$results = $uploader->process('user_files');

五,函數封裝


一、函數封裝理論體系

1. 抽象層次模型
HTTP協議層
PHP運行時層
安全驗證層
業務邏輯層
持久化層
  • 協議抽象:封裝multipart/form-data解析細節
  • 資源管理:統一處理臨時文件生命周期
  • 正交設計:驗證邏輯與存儲邏輯解耦
2. 設計模式應用
  • 策略模式:可插拔的驗證規則(MIME檢測策略、病毒掃描策略)
  • 工廠模式:根據文件類型創建不同的處理器(圖片處理器、文檔處理器)
  • 裝飾器模式:動態添加功能(日志記錄、內容過濾)
  • 觀察者模式:實現上傳進度通知機制
3. SOLID原則映射
原則實現方式
單一職責分離驗證、存儲、后處理模塊
開閉原則通過繼承擴展功能而非修改源碼
里氏替換子類處理器保持父類接口兼容
接口隔離定義UploadValidator獨立接口
依賴倒置依賴抽象接口而非具體實現

二、核心技術實現

1. 安全防御技術棧
輸入消毒
類型驗證
內容掃描
權限控制
審計追蹤
  • 深度防御模型

    1. 文件名消毒:正則過濾/[^a-z0-9\-_.]/i
    2. 雙驗證機制:文件簽名+MIME類型
    3. 沙箱檢測:使用QEMU虛擬環境執行可疑文件
    4. 權限最小化:上傳目錄chmod 755 + open_basedir限制
  • 零信任實現

    class ZeroTrustValidator {public function validate($file) {$this->checkOrigin($file['tmp_name']);$this->verifySignature($file['tmp_name']);$this->analyzeEntropy($file['tmp_name']);}private function checkOrigin($path) {if (!is_uploaded_file($path)) {throw new SecurityException("非法文件來源");}}
    }
    
2. 異步處理架構
Client API_Gateway Message_Queue Upload_Worker 發起上傳請求 存入任務隊列 分發處理任務 回調通知結果 Client API_Gateway Message_Queue Upload_Worker
  • 分片上傳算法

    def upload_chunk(file, chunk_size=5*1024*1024):total = math.ceil(file.size / chunk_size)for i in range(total):chunk = file.read(chunk_size)hash = sha256(chunk).hexdigest()redis.set(f"upload:{file.id}:{i}", {'hash': hash,'data': base64.b64encode(chunk)})return merge_chunks(file.id, total)
    
3. 可觀測性設計
  • 指標收集

    # TYPE file_upload_size histogram
    file_upload_size_bucket{status="success",le="1048576"} 42
    file_upload_size_bucket{status="success",le="5242880"} 87# TYPE upload_error_counter counter
    upload_error_counter{type="size_limit"} 3
    
  • 分布式追蹤

    {"trace_id": "abc123","span_id": "def456","operation": "FileUpload","tags": {"file.size": "2.4MB","validation.time": "128ms"}
    }
    

三,FileUploader 類設計

<?php
/*** 安全文件上傳處理器* * 功能特性:* 1. 多文件上傳支持* 2. MIME類型白名單驗證* 3. 文件擴展名過濾* 4. 自動生成安全文件名* 5. 病毒掃描集成接口* 6. 圖片EXIF信息處理* 7. 上傳進度跟蹤* 8. 自動目錄創建* 9. 防御性錯誤處理*/
class FileUploader {// 配置參數private $config = ['upload_dir'      => __DIR__.'/uploads', // 上傳目錄'allowed_mimes'   => [],                // 允許的MIME類型'allowed_exts'    => [],                // 允許的擴展名'max_size'        => 2 * 1024 * 1024,   // 最大文件尺寸(2MB)'overwrite'       => false,             // 是否覆蓋同名文件'sanitize_name'   => true,              // 自動清理文件名'hash_name'       => true,              // 使用哈希文件名'virus_scan'      => false,             // 啟用病毒掃描'image_handling'  => [                  // 圖片處理配置'resize' => ['enabled' => false,'width'   => 800,'height'  => 600],'strip_exif' => true]];// 運行時狀態private $errors = [];private $uploadedFiles = [];/*** 構造函數* @param array $config 自定義配置項*/public function __construct(array $config = []) {$this->config = array_merge($this->config, $config);$this->init();}/*** 初始化驗證*/private function init() {// 檢查上傳功能是否啟用if (!ini_get('file_uploads')) {throw new RuntimeException('服務器未啟用文件上傳功能');}// 創建上傳目錄if (!is_dir($this->config['upload_dir'])) {$this->createDirectory($this->config['upload_dir']);}// 驗證目錄可寫if (!is_writable($this->config['upload_dir'])) {throw new RuntimeException('上傳目錄不可寫: '.$this->config['upload_dir']);}}/*** 處理文件上傳* @param string $fieldName 表單字段名* @return array 上傳結果*/public function upload(string $fieldName): array {$this->resetState();if (!isset($_FILES[$fieldName])) {$this->errors[] = "未找到上傳字段: {$fieldName}";return $this->getResult();}$files = $this->reorganizeFiles($_FILES[$fieldName]);foreach ($files as $file) {$this->processSingleFile($file);}return $this->getResult();}/*** 重組多文件數組結構*/private function reorganizeFiles(array $files): array {$organized = [];foreach ($files as $key => $values) {foreach ($values as $index => $value) {$organized[$index][$key] = $value;}}return $organized;}/*** 處理單個文件*/private function processSingleFile(array $file) {try {// 基礎驗證$this->validateBasic($file);// 安全驗證$this->validateSecurity($file);// 生成目標路徑$destination = $this->generateDestination($file);// 移動文件$this->moveUploadedFile($file['tmp_name'], $destination);// 后處理$this->postProcess($destination, $file);// 記錄成功$this->uploadedFiles[] = ['original_name' => $file['name'],'saved_path'    => $destination,'size'          => $file['size'],'mime_type'     => $this->getRealMimeType($file['tmp_name'])];} catch (Exception $e) {$this->errors[] = $file['name'].': '.$e->getMessage();}}/*** 基礎驗證*/private function validateBasic(array $file) {// 錯誤代碼驗證if ($file['error'] !== UPLOAD_ERR_OK) {throw new RuntimeException($this->getUploadError($file['error']));}// 臨時文件驗證if (!is_uploaded_file($file['tmp_name'])) {throw new RuntimeException('非法文件來源');}// 文件大小驗證if ($file['size'] > $this->config['max_size']) {$maxSize = round($this->config['max_size'] / 1024 / 1024, 1);throw new RuntimeException("文件超過 {$maxSize}MB 限制");}}/*** 安全驗證*/private function validateSecurity(array $file) {// MIME類型驗證$realMime = $this->getRealMimeType($file['tmp_name']);if (!in_array($realMime, $this->config['allowed_mimes'])) {throw new RuntimeException("禁止的文件類型: {$realMime}");}// 擴展名驗證$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, $this->config['allowed_exts'])) {throw new RuntimeException("禁止的文件擴展名: .{$ext}");}// 病毒掃描if ($this->config['virus_scan']) {$this->scanForVirus($file['tmp_name']);}}/*** 獲取真實MIME類型*/private function getRealMimeType(string $tmpPath): string {$finfo = finfo_open(FILEINFO_MIME_TYPE);$mime = finfo_file($finfo, $tmpPath);finfo_close($finfo);return $mime;}/*** 生成目標路徑*/private function generateDestination(array $file): string {$filename = $this->config['sanitize_name'] ? $this->sanitizeFilename($file['name']): $file['name'];if ($this->config['hash_name']) {$ext = pathinfo($filename, PATHINFO_EXTENSION);$filename = md5(uniqid().microtime()).'.'.$ext;}$destination = $this->config['upload_dir'].DIRECTORY_SEPARATOR.$filename;// 防覆蓋處理if (!$this->config['overwrite'] && file_exists($destination)) {throw new RuntimeException('文件已存在');}return $destination;}/*** 安全移動文件*/private function moveUploadedFile(string $tmpPath, string $destination) {if (!move_uploaded_file($tmpPath, $destination)) {throw new RuntimeException('文件保存失敗');}// 設置安全權限chmod($destination, 0644);}/*** 文件名消毒*/private function sanitizeFilename(string $filename): string {// 刪除路徑信息$clean = basename($filename);// 替換特殊字符$clean = preg_replace("/[^a-zA-Z0-9\-_.]/", '_', $clean);// 縮短長度return substr($clean, 0, 200);}/*** 病毒掃描*/private function scanForVirus(string $filePath) {// 示例:集成ClamAV$output = shell_exec("clamscan --no-summary {$filePath}");if (strpos($output, 'OK') === false) {unlink($filePath);throw new RuntimeException('文件包含病毒或惡意代碼');}}/*** 上傳后處理*/private function postProcess(string $filePath, array $originalFile) {// 圖片處理if (strpos($originalFile['type'], 'image/') === 0) {$this->processImage($filePath);}}/*** 圖片處理*/private function processImage(string $filePath) {try {// 去除EXIF信息if ($this->config['image_handling']['strip_exif']) {$this->stripExif($filePath);}// 調整尺寸if ($this->config['image_handling']['resize']['enabled']) {$this->resizeImage($filePath,$this->config['image_handling']['resize']['width'],$this->config['image_handling']['resize']['height']);}} catch (Exception $e) {unlink($filePath);throw new RuntimeException('圖片處理失敗: '.$e->getMessage());}}// ...其他輔助方法.../*** 獲取最終結果*/public function getResult(): array {return ['success' => $this->uploadedFiles,'errors'  => $this->errors,'total'   => count($this->uploadedFiles) + count($this->errors),'passed'  => count($this->uploadedFiles),'failed'  => count($this->errors)];}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78711.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78711.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78711.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CSS元素動畫篇:基于當前位置的變換動畫(三)

基于當前位置的變換動畫&#xff08;三&#xff09; 前言縮放效果類元素動畫脈沖動畫效果效果預覽代碼實現 橡皮筋動畫效果效果預覽代碼實現 果凍動畫效果效果預覽代碼實現 歡呼動畫效果效果預覽代碼實現 心跳動畫效果效果預覽代碼實現 結語 前言 CSS元素動畫一般分為兩種&…

Redis ssd是什么?Redis 內存空間優化的點都有哪些?embstr 和 row、intset、ziplist分別是什么?

Redis SSD 是什么&#xff1f; Redis SSD 通常指 Redis 使用 SSD&#xff08;固態硬盤&#xff09;作為持久化存儲介質的場景。雖然 Redis 是內存數據庫&#xff08;數據主要駐留內存&#xff09;&#xff0c;但其持久化機制&#xff08;如 RDB 快照和 AOF 日志&#xff09;需…

【藍橋杯】 數字詩意

數字詩意 在詩人的眼中&#xff0c;數字是生活的韻律&#xff0c;也是詩意的表達。 小藍&#xff0c;當代頂級詩人與數學家&#xff0c;被賦予了”數學詩人”的美譽。他擅長將冰冷的數字與抽象的詩意相融合&#xff0c;并用優雅的文字將數學之美展現于紙上。 某日&#xff0…

DHCP 服務器運行流程圖

以常見的 DHCP v4 為例,其完整流程如下: 一、客戶端請求 IP 地址階段 DHCPDiscover:客戶端啟動后,會以廣播的形式發送 DHCPDiscover 報文,目的是在網絡中尋找可用的 DHCP 服務器。該報文中包含客戶端的 MAC 地址等信息,以便服務器能夠識別客戶端。DHCPOffer:網絡中的 D…

一種企業信息查詢系統設計和實現:xujian.tech/cs

一種企業信息查詢系統設計和實現&#xff1a;xujian.tech/cs 背景與定位 企業在對外合作、風控審查或市場調研時&#xff0c;常需快速獲取公開的工商信息。本文介紹一個企業信息搜索引擎&#xff0c;面向普通用戶與開發者&#xff0c;幫助快速定位企業名稱、統一社會信用代碼…

前端面試高頻算法

前端面試高頻算法 1 排序算法&#xff1b;1.1 如何分析一個排序算法1.1.1 執行效率3.1.2 內存消耗1.1.3 穩定性 1.2 冒泡排序&#xff08;Bubble Sort&#xff09;1.3 插入排序&#xff08;Insertion Sort&#xff09;1.4 選擇排序&#xff08;Selection Sort&#xff09;1.5 歸…

C++初階-模板初階

目錄 1.泛型編程 2.函數模板 2.1函數模板概念 2.2實現函數模板 2.3模板的原理 2.4函數模板的實例化 2.4.1隱式實例化 2.4.2顯式初始化 2.5模板參數的匹配原則 3.類模板 3.1類模板定義格式 3.2類模板的實例化 4.總結 1.泛型編程 對廣泛的類型法寫代碼&#xff0c;我…

「Mac暢玩AIGC與多模態02」部署篇01 - 在 Mac 上部署 Ollama + Open WebUI

一、概述 本篇介紹如何在 macOS 環境下本地部署 Ollama 推理服務,并通過 Open WebUI 實現可視化交互界面。該流程無需 CUDA 或專用驅動,適用于 M 系列或 Intel 芯片的 Mac,便于快速測試本地大語言模型能力。 二、部署流程 1. 環境準備 安裝 Homebrew(如尚未安裝):/bin…

JavaScript 中 undefined 和 not defined 的區別

在 JavaScript 的調試過程中&#xff0c;你是否經常看到 undefined 卻不知其來源&#xff1f;是否曾被 ReferenceError: xxx is not defined 的錯誤提示困擾&#xff1f;這兩個看似相似的概念&#xff0c;實際上是 JavaScript 類型系統中最重要的分水嶺。本文將帶你撥開迷霧&am…

django admin AttributeError: ‘UserResorce‘ object has no attribute ‘ID‘

在 Django 中遇到 AttributeError: ‘UserResource’ object has no attribute ‘ID’ 這類錯誤通常是因為你在代碼中嘗試訪問一個不存在的屬性。在你的例子中&#xff0c;錯誤提示表明 UserResource 類中沒有名為 ID 的屬性。這可能是由以下幾個原因造成的&#xff1a; 拼寫錯…

對鴻蒙 Next 系統“成熟論”的深度剖析-優雅草卓伊凡

對鴻蒙 Next 系統“成熟論”的深度剖析-優雅草卓伊凡 在科技飛速發展的當下&#xff0c;鴻蒙 Next 系統無疑成為了眾多科技愛好者與行業人士關注的焦點。今日&#xff0c;卓伊凡便收到這樣一個饒有趣味的問題&#xff1a;鴻蒙 Next 系統究竟需要多長時間才能完全成熟&#xff…

快速上手GO的net/http包,個人學習筆記

更多個人筆記&#xff1a;&#xff08;僅供參考&#xff0c;非盈利&#xff09; gitee&#xff1a; https://gitee.com/harryhack/it_note github&#xff1a; https://github.com/ZHLOVEYY/IT_note 針對GO中net/http包的學習筆記 基礎快速了解 創建簡單的GOHTTP服務 func …

AI-Browser適用于 ChatGPT、Gemini、Claude、DeepSeek、Grok的客戶端開源應用程序,集成了 Monaco 編輯器。

一、軟件介紹 文末提供程序和源碼下載學習 AI-Browser適用于 ChatGPT、Gemini、Claude、DeepSeek、Grok、Felo、Cody、JENOVA、Phind、Perplexity、Genspark 和 Google AI Studio 的客戶端應用程序&#xff0c;集成了 Monaco 編輯器。使用 Electron 構建的強大桌面應用程序&a…

Dify框架面試內容整理-Dify如何處理知識庫的集成?

Dify 在知識庫集成方面采用了“檢索增強生成(RAG)”的技術架構,核心實現思路如下: 一、知識庫集成的整體流程 Dify處理知識庫集成通常包括以下關鍵步驟: 文檔上傳↓

Laravel 模型使用全局作用域和局部作用域

一. 需要解決什么問題 最近Laravel 項目中遇到一個需求&#xff0c;我有一個客戶表&#xff0c;每個員工都有自己的客戶&#xff0c;但是自己只能看自己的客戶。 項目中&#xff0c;有很多功能需要查詢客戶列表&#xff0c;客戶詳情&#xff0c;查詢客戶入口很多&#xff0c;…

【Nova UI】十二、打造組件庫之按鈕組件(上):邁向功能構建的關鍵一步

序言 在上一篇文章中&#xff0c;我們深入探索了 icon 組件從測試到全局注冊的全過程&#x1f3af;&#xff0c;成功為其在項目中穩定運行筑牢了根基。此刻&#xff0c;組件庫的建設之旅仍在繼續&#xff0c;我們將目光聚焦于另一個關鍵組件 —— 按鈕組件。按鈕作為用戶與界面…

鴻蒙OSS文件(視頻/圖片)壓縮上傳組件-能夠增刪改查

一、鴻蒙實現處理-壓縮上傳整體代碼處理邏輯 轉沙箱壓縮獲取憑證并上傳文件 文件準備&#xff08;拿到文件流&#xff09;獲取上傳憑證&#xff08;調接口1拿到file_name和upload_url&#xff09;執行文件上傳&#xff08;向階段2拿到的upload_url上傳文件&#xff09;更新列表…

河道流量監測,雷達流量計賦能水安全智慧守護

在蜿蜒的河道之上&#xff0c;水流的脈搏始終與人類文明的興衰緊密相連。從農田灌溉的水量調配到城市防洪的精準預警&#xff0c;從生態保護的水質溯源到水資源管理的決策&#xff0c;河道流量監測如同大地的 “血管檢測”&#xff0c;是守護水安全的第一道防線。傳統監測手段在…

CSS3 基礎(邊框效果)

一、邊框效果 屬性功能示例值說明border-radius創建圓角border-radius: 20px;設置元素的圓角半徑&#xff0c;支持像素&#xff08;px&#xff09;或百分比&#xff08;%&#xff09;。值為 50% 時可變為圓形。box-shadow添加陰影box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.5)…

零基礎小白如何上岸數模國獎

零基礎小白如何上岸數模國獎 我自己本人第一次參加數模國賽順利上岸國獎&#xff0c;當然那段經歷也是比較痛苦了&#xff0c;差不多也是從當年四月開始接觸數學建模&#xff0c;第一次參加媽媽杯成績并不理想&#xff0c;后面不斷參加數模比賽進行模擬&#xff0c;最后順利上岸…