文件上傳看似簡單,但面對「多文件即時驗證」、「大文件分片上傳」、「重複上傳浪費頻寬」等場景時,傳統「表單一次性提交」模式勢必力不從心。本文系統梳理三大業務場景與對應的後端優化策略。
| 場景 | 特徵 | 痛點 | 核心策略 |
|---|---|---|---|
| 單文件無即時驗證 | 表單一次性提交 | 無 | 直傳處理 |
| 多文件需即時驗證/預覽 | 選擇後立即上傳,提交時再傳一次 | 重複上傳浪費時間與頻寬 | 首次上傳存臨時目錄,提交時移動 |
| 大文件分片/分批上傳 | 多次上傳累積超過單次限制 | 單次上傳大小限制 | 分片暫存,最終合併或僅提交元數據 |
$('.upload-real-file').on('change', function(){
if(!$(this).val()) return false;
var responseObj = $('#' + $(this).attr('response-id'));
$('#Form').ajaxSubmit({
url: '/aa/bb/uploadTmpApkTool',
dataType: 'json',
beforeSubmit: function(){ window.loading = layer.load(2); },
success: function(data){
layer.close(window.loading);
if(data.status == 1){
responseObj.html(data.apkInfoHtml);
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;
});
<?php
// 1. 接收文件
$subFile = $_FILES['apkToolFiles'];
// 2. 驗證:大小、副檔名、內容解析(如 APK 包名/版本/UMENG_CHANNEL)
$fileData = $this->_getFileData($subFile, $subKey);
$checkResult = $this->_checkFileData($fileData, [
'maxSize' => $config['FILE_MAX_SIZE'],
'savePath' => $config['TMP_CHILD_PATH'],
'extArr' => ['apk'],
'releaseName' => str_replace('.apk', '', $fileData['fileName'])
]);
// 3. 移動到臨時目錄(加隨機後綴避免衝突)
$filePath = $checkData['savePath'] . '/' . $fileData['fileName'] . '.tmp' . genRandStr(6);
$this->_moveUploadedFile($fileData['tmpName'], $filePath);
// 4. 業務驗證(如解析 APK 校驗渠道號)
$apkInfo = $this->_apkParser($filePath);
if($apkInfo['UMENG_CHANNEL'] != 'UMENG_CHANNEL_VALUE') {
@unlink($filePath); return ajaxReturn(['status'=>0, 'info'=>'渠道號不符']);
}
// 5. 構造臨時文件元數據回傳前端(隱藏域)
$tmpFileArr['file_info'] = [
'name'=>$subFile['name'][$subKey], 'type'=>$subFile['type'][$subKey],
'tmp_name'=>str_replace($config['TMP_CHILD_PATH'].'/', '', $filePath),
'error'=>$subFile['error'][$subKey], 'size'=>$subFile['size'][$subKey]
];
$fileInfo = htmlspecialchars(json_encode($tmpFileArr['file_info']));
$fileInfoHtml = "<input name='apkToolFileTmp[]' value='{$fileInfo}' type='hidden'/>";
// 6. 返回前端所需預覽信息
ajaxReturn(['status'=>1, 'apkInfoHtml'=>$apkInfoHtml, 'fileInfo'=>$fileInfoHtml, ...]);
?>
// 前端:禁用 file input,避免重複提交文件二進制
$('.upload-file-real').attr('disabled', 'disabled');
// 後端:僅處理隱藏域中的臨時文件元數據
// 讀取 apkToolFileTmp[] 陣列 -> 解析 tmp_name -> rename() 移動到正式目錄
// 完成業務邏輯入庫
用戶可能中途取消操作,導致臨時文件殘留。需部署 Crontab 定時腳本:
# 每天凌晨 3 點清理 24 小時前的臨時文件 0 3 * * * /usr/bin/php /path/to/clean_tmp.php >/dev/null 2>&1
<?php
$dir = '/path/to/tmp';
$expire = 86400; // 24h
foreach (new DirectoryIterator($dir) as $file) {
if ($file->isFile() && (time() - $file->getMTime() > $expire)) @unlink($file->getPathname());
}
?>
htmlspecialchars(json_encode(...)),否則引號截斷導致解析失敗genRandStr(6) 或 uniqid() 避免同名覆蓋這套「臨時落地 + 元數據傳遞 + 定時清理」模式,已在多個百萬級日活專案穩定運行,有效解決了大文件、多文件、分批上傳的工程難題。