
1. 项目背景与核心需求在Web应用中处理大文件上传一直是开发者的痛点。传统表单上传在面对几百MB甚至几个GB的附件时经常会遇到超时、网络波动导致重传等问题。我们团队最近在开发一个企业文档管理系统时就遇到了用户需要上传超大设计稿文件的需求。经过技术选型最终确定采用WebUploader前端组件配合PHP后端服务的方案。这套组合能实现两个核心功能秒传当用户上传已存在的文件时通过文件指纹比对实现瞬时完成断点续传上传过程中断后可以从断点继续而不是重新开始2. 技术方案设计2.1 整体架构系统采用前后端分离设计[浏览器] - [WebUploader] - [Nginx] - [PHP-FPM] - [文件存储] - [Redis] (用于存储分片信息)2.2 关键技术点文件分片将大文件切割为2MB的块可配置指纹生成使用SparkMD5计算文件唯一标识状态记录Redis存储上传进度秒传验证文件指纹比对数据库3. 前端实现要点WebUploader需要特殊配置var uploader WebUploader.create({ auto: false, chunked: true, chunkSize: 2 * 1024 * 1024, server: /upload.php, formData: { uid: file.uid // 文件唯一标识 } }); // 计算文件MD5 uploader.on(fileQueued, function(file) { var spark new SparkMD5.ArrayBuffer(); var reader new FileReader(); reader.onload function(e) { spark.append(e.target.result); var md5 spark.end(); file.uid md5; }; reader.readAsArrayBuffer(file.source); });4. PHP后端实现4.1 文件接收处理// upload.php $uid $_POST[uid]; $chunk isset($_REQUEST[chunk]) ? intval($_REQUEST[chunk]) : 0; $chunks isset($_REQUEST[chunks]) ? intval($_REQUEST[chunks]) : 0; // 检查文件是否已存在秒传逻辑 if($redis-exists(file:complete:{$uid})) { $filepath $redis-get(file:complete:{$uid}); die(json_encode([status complete, path $filepath])); } // 临时存储分片 $tmpDir uploads/tmp/{$uid}; if (!file_exists($tmpDir)) { mkdir($tmpDir, 0777, true); } move_uploaded_file($_FILES[file][tmp_name], {$tmpDir}/{$chunk}.part); // 更新进度 $redis-hSet(file:progress:{$uid}, $chunk, 1);4.2 分片合并// merge.php $uid $_POST[uid]; $filename $_POST[name]; $tmpDir uploads/tmp/{$uid}; // 检查是否所有分片都上传完成 $total count(glob({$tmpDir}/*.part)); if($total ! $_POST[chunks]) { die(json_encode([status error, message 分片不完整])); } // 合并文件 $finalPath uploads/.date(Ym)./.uniqid()._.$filename; $out fopen($finalPath, wb); for($i 0; $i $total; $i) { $partFile {$tmpDir}/{$i}.part; $in fopen($partFile, rb); stream_copy_to_stream($in, $out); fclose($in); unlink($partFile); } fclose($out); rmdir($tmpDir); // 记录完成状态 $redis-set(file:complete:{$uid}, $finalPath);5. 性能优化实践5.1 内存控制在处理大文件时PHP默认配置可能导致内存耗尽。需要在php.ini中调整memory_limit 256M post_max_size 1024M upload_max_filesize 1024M max_execution_time 18005.2 分片大小选择经过测试我们发现2MB分片在大多数场景下表现最佳过小增加请求次数降低整体吞吐过大单个请求失败代价高5.3 并发控制WebUploader默认并发数为3对于内网环境可以适当提高WebUploader.create({ // ... threads: 5 // 内网可提升到5-8 });6. 安全防护措施6.1 文件校验合并完成后必须进行校验$serverMd5 md5_file($finalPath); if($serverMd5 ! $uid) { unlink($finalPath); die(文件校验失败); }6.2 目录穿越防护处理文件名时需要过滤特殊字符$filename preg_replace(/[^\w\.\-]/, , $_POST[name]);6.3 权限控制上传目录应配置正确的权限chown -R www-data:www-data uploads/ chmod -R 750 uploads/7. 实际踩坑记录Nginx超时问题client_max_body_size 1024m; proxy_read_timeout 1800s;Redis连接数 高峰期可能出现连接耗尽需要调整$redis new Redis(); $redis-pconnect(127.0.0.1, 6379, 2.5); // 使用持久连接文件锁竞争 多进程合并时可能冲突需要加锁$lock fopen(lockfile_{$uid}, w); if (flock($lock, LOCK_EX)) { // 合并操作 flock($lock, LOCK_UN); } fclose($lock);8. 监控与日志建议记录关键指标// 记录上传日志 $log sprintf([%s] %s %s %.2fMB %s\n, date(Y-m-d H:i:s), $_SERVER[REMOTE_ADDR], $uid, filesize($finalPath)/1024/1024, $filename ); file_put_contents(upload.log, $log, FILE_APPEND);9. 扩展优化方向分布式存储当单机存储不足时可集成OSS/S3进度查询API提供上传进度查询接口客户端加密敏感文件可在前端加密后再上传CDN加速对全球用户可使用CDN边缘节点上传这套方案在我们生产环境已经稳定运行2年单文件最大处理过38GB的设计稿日均上传量约1.2TB。关键是要做好分片策略和异常处理网络波动时自动重试的机制也很重要。