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

PHP 檔案上傳後端處理三大業務場景與優化策略

     編輯:PHP 綜合知識

檔案上傳是 Web 開發常見需求,但隨著業務複雜度提升,單純的表單提交已難以滿足即時驗證、大檔分片、重複上傳等場景。本文歸納三大典型業務場景,並給出「先上傳暫存、最後提交時移動」的核心優化思路,附完整前後端代碼示範。

場景一:單檔直傳,無即時驗證

最基礎的情況:使用者選擇單一檔案,隨表單一併提交。後端直接處理 $_FILES 即可,無額外優化空間。

場景二:多檔需即時驗證內容並即時顯示

使用者選檔後需立即上傳解析(如 APK 包名、版本號),並在頁面顯示資訊;最終提交時若再傳一次檔案,會浪費頻寬與時間。
優化策略:首次上傳後將檔案存為伺服器暫存檔,並將暫存路徑等元資料寫入隱藏欄位;最終提交時只傳遞元資料,後端直接移動暫存檔至正式目錄。

場景三:大檔分片/多次上傳,單次請求超過上傳限制

例如 100MB 檔案分 10 次上傳,每次 10MB。單次請求不超限,但合併後會超過 upload_max_filesize。同樣採用暫存策略:每片上傳後暫存,最後合併或僅記錄元資料。

核心實作代碼

1. 前端 JS(jQuery Form + Layer)

$('.upload-real-file').off().on('change', function(){
    if(!$(this).val()) return false;
    var responseObjId = $(this).attr('response-id');
    var responseObj = $('#' + responseObjId);
    $('#Form').ajaxSubmit({
        url: '/aa/bb/uploadTmpApkTool',
        resetForm: false,
        dataType: 'json',
        beforeSubmit: function(){ window.loading = layer.load(2); },
        success: function(data){
            layer.close(window.loading);
            if(data.status == 1){
                responseObj.html(data.apkInfoHtml);
                var parentContainer = responseObj.parent().parent(),
                    nameContainer = parentContainer.find('.file-name-container');
                nameContainer.html(data.apkName).attr('title', data.apkName);
                responseObj.find('.file-tmp').html(data.fileInfo); // 隱藏域存暫存資訊
                $(submitId).removeAttr('disabled');
            }else{
                layer.alert(data.info);
            }
        },
        error: function(){
            layer.close(window.loading);
            layer.alert('未知錯誤,請稍後再試!');
        }
    });
    return false; // 防止 dialog 自動關閉
});

2. 後端 PHP 處理暫存與解析

$apkConfig = $this->_getApkConfig();
$subFile = $_FILES['apkToolFiles'];
$apkName = $apkInfoHtml = "";
if(empty($subFile)) {
    $this->ajaxReturn(['status' => -4, 'info' => '請選擇要上傳的文件']);
}
foreach ($subFile['name'] as $subKey => $subVal) {
    if ($subFile['name'][$subKey]) {
        $fileData = $this->_getFileData($subFile, $subKey);
        $checkData = [
            'maxSize' => $apkConfig['FILE_MAX_SIZE'],
            'savePath' => $apkConfig['TMP_CHILD_PATH'],
            'extArr' => ['apk'],
            'releaseName' => str_replace('.apk', '', $fileData['fileName']),
        ];
        $checkResult = $this->_checkFileData($fileData, $checkData);
        if ($checkResult['status'] != 1) { $this->ajaxReturn($checkResult); }
        // 移動到暫存目錄,檔名加隨機後綴防衝突
        $filePath = $checkData['savePath'] . '/' . $fileData['fileName'] . '.tmp' . genRandStr(6);
        $this->_moveUploadedFile($fileData['tmpName'], $filePath);
        // 解析 APK 資訊
        $apkInfo = $this->_apkParser($filePath);
        if($apkInfo['UMENG_CHANNEL'] != 'UMENG_CHANNEL_VALUE') {
            @unlink($filePath);
            $this->ajaxReturn(['status' => 0, 'info' => "UMENG_CHANNEL 的值必須為 UMENG_CHANNEL_VALUE"]);
        }
        // 組裝暫存檔資訊供前端隱藏域使用
        $tmpFileArr['file_info'] = [
            'name' => $subFile['name'][$subKey],
            'type' => $subFile['type'][$subKey],
            'tmp_name' => str_replace($apkConfig['TMP_CHILD_PATH'] . '/', '', $filePath),
            'error' => $subFile['error'][$subKey],
            'size' => $subFile['size'][$subKey],
        ];
    } else {
        $this->ajaxReturn(['status' => 0, 'info' => "文件不能為空"]);
    }
    foreach ($apkInfo as $key => $val) {
        $apkInfoHtml .= "{$key}:{$val} \r\n";
    }
    $apkName = $fileData['fileName'];
    $version = $apkInfo['versionName'];
}
$fileInfo = htmlspecialchars(json_encode($tmpFileArr['file_info']));
$fileInfoHtml = "<input name=\"apkToolFileTmp[]\" value='{$fileInfo}' type=\"hidden\"/>";
$this->ajaxReturn([
    'status' => 1, 'info' => "上傳成功",
    'version' => $version, 'apkName' => $apkName,
    'apkInfoHtml' => $apkInfoHtml, 'fileInfo' => $fileInfoHtml
]);

3. 正式提交時只傳元資料,後端移動暫存檔

// 提交表單前禁用實體 file input,避免重複上傳
$('.upload-file-real').attr('disabled', 'disabled');

後端接收隱藏域 apkToolFileTmp[],解析 JSON 取得暫存路徑,直接 rename() 至正式目錄即可。

後續維護:定時清理暫存目錄

使用者可能中途取消操作,導致暫存檔殘留。建議寫一個 Cron 腳本,每日掃描暫存目錄,刪除建立時間超過 24 小時的檔案,並記錄日誌。

小結

「先暫存、後移動」模式有效解決重複上傳、大檔分片、即時驗證三大痛點。關鍵在於:前端上傳後拿到暫存資訊寫入隱藏域、後端驗證通過才落盤、正式提交時僅移動檔案、定時清理孤兒檔。掌握此模式,檔案上傳功能即能優雅應對複雜業務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved