
最近在维护一个单位的老OA系统时被附件上传问题折腾得不轻。业务同事天天在群里反馈图纸、合同扫描件、项目报告动不动就是几十上百MB一点上传就转圈转着转着就报错甚至直接把浏览器搞崩。排查下来系统的前端用的是老牌的WebUploader后端是PHP走的是最朴素的“单个文件整体上传”模式。这个组合在几年前跑小附件完全没问题但面对超大附件传输效率和稳定性就双双露馅了。这篇文章就基于这次真实改造经历完整聊聊如何用WebUploaderPHP的分片传输方案把老OA系统的超大附件上传从“能不能传上去”优化到“既快又稳”。先说结论这个方案适合正在维护PHP老系统的开发同学。WebUploader虽然官方早已停止更新但它分片、并发、队列能力足够成熟PHP后端配合接收分片和合并文件也完全不复杂。整个改造不需要引入Node、不需要重构前端工程能在最短时间内让大附件上传体感发生质变。1. 为什么老OA系统的大附件上传会卡死1.1 问题现场与用户真实反馈先说几个我在项目中实际遇到的场景。有同事上传一份80MB的施工图纸系统直接转圈十几分钟最后弹了个“网络错误”的框等于白传。还有人传一份50MB的PDF合同扫描件浏览器标签页直接卡死只能强制关闭重来。最离谱的是有用户把一个200MB的监控视频往OA里拖结果服务端PHP直接报内存不足错误连带着把系统页面都搞成了白屏。这些问题的共性是附件越大失败率越高且失败后没有任何断点续传能力用户必须从头再传一遍。在一次内部统计里大于50MB的附件首次上传成功率只有不到70%这放在办公场景里是非常影响业务的。特别是一些需要走流程审批的紧急文件卡在上传环节后面的审核、归档全都不动业务部门怨声载道。1.2 卡死的三个核心瓶颈之所以会出现这种局面并不是某一处配置错了而是整个上传链路层层受限。把链路拆开看主要有三个瓶颈。第一层是网关或Web服务器的限制。当时系统前面挂了一层Nginx默认的client_max_body_size是1MB虽然运维部门调过但调到了100MB。按说够用可一旦附件超过这个值直接返回413错误用户根本不知道是服务器限制只看到“上传失败”。第二层是PHP运行时的限制。upload_max_filesize、post_max_size、max_execution_time、memory_limit这四个参数像四道紧箍咒。单个大文件整体上传时PHP要在一次请求里接收完整个文件内容post_max_size不够就截断memory_limit不够就直接崩max_execution_time不够就被强制杀死。这还只是单文件请求如果同时有几个人上传大文件PHP-FPM的进程直接被打满整个OA系统都会变慢。第三层是网络链路本身的脆弱性。内网虽然带宽不算小但一个100MB文件通过单连接传输中途只要有一次网络抖动、一个TCP包重传超时整个请求就断掉了。WebUploader在非分片模式下对单一大文件的重试表现很差基本就是全量重来。换句话说老OA系统的大附件上传本质上是在用单线程、单请求的方式去对抗大文件、弱网络的客观现实自然会卡死。1.3 为什么选WebUploader而不是换新框架在做方案选型时也考虑过换现代前端上传组件比如用XHR2原生的upload事件或者接第三方SDK。但仔细一评估就放弃了。这套OA系统的前端停留在jQuery时代没有一个现代化的构建流程引入新版组件库意味着要搭一套Webpack或Vite环境还要处理各种依赖兼容问题改动面巨大风险不可控。WebUploader的优势在于它足够“老”老到能完美兼容jQuery、兼容IE时代的各种坑。它原生自带分片、并发上传、队列管理、进度回调这些正好是我们需要的。更关键的是它的接入方式是简单的JSCSS引入就能用不需要打包工具对老系统的改造侵入性最小。通俗点说这不是选了一个最好的技术方案而是选了一个最合适的改造方案。2. 分片方案怎么设计才合理2.1 分片大小选多少确定用WebUploader做分片后第一个要拍板的问题就是分片大小。这个参数直接决定传输效率不能拍脑袋。分片过小比如1MB一个100MB文件要切成100片每片都带着HTTP请求头、上传接口逻辑、磁盘写入操作请求往返的开销会被极度放大传输效率反而不高。分片过大比如20MB虽然请求次数少了但一旦某个分片传输失败重传代价就很大而且单分片传输时间长网关超时的风险也会增加。实际项目里我用了一个简单估算公式分片大小 ≈ 内网单线程有效带宽 × 单分片期望耗时。假设内网有效带宽约8MB/s我期望单个分片在2到3秒内传完那分片大小就在16MB到24MB这里有个误区因为WebUploader是并发上传的单分片耗时可以放宽但还要考虑服务端PHP进程压力和接口处理速度。做了一轮实测对比后我最终把分片定为4MB。这个值比较中庸100MB文件切成25片请求次数可控单片传输时间短就算是弱网环境下失败重传的代价也完全不痛。分片大小100MB文件分片数单分片传输耗时10MB/s带宽适用场景2MB50约0.2秒弱网、服务器性能差4MB25约0.4秒一般内网推荐使用8MB13约0.8秒内网带宽好服务器处理快2.2 并发线程数设置多少WebUploader的threads参数控制同时上传的分片数量。这个参数不能一味调大。并发太高时前端同时发起多个AJAX请求这些请求会共享同一台PHP-FPM服务器的进程池。如果十几个并发请求同时打过来PHP-FPM的进程全部被上传接口占据其他OA页面的请求就会排队整个系统都会卡。根据服务器的硬件情况4核8G的机器我将threads设为3到5之间。实测下来3并发对服务器压力最小但100MB文件传输耗时大概要15秒。调到5并发后时间可以下降到10秒左右但PHP-FPM的CPU占用会上升约15%。考虑到OA系统还有其他业务最终标准配置定为“3并发分片4MB”在稳定性和速度之间取一个平衡点。还要注意一个细节浏览器对同一域名的并发连接数限制HTTP/1.1下通常是6个WebUploader内部走的是AJAX请求如果同时再打开其他页面很容易把连接数占满。设置并发数时一定要给其他业务留一点余量。2.3 全文件MD5校验到底做不做WebUploader默认会对每个分片做MD5校验这是为了确保分片数据传输完整性。但很多人在改造时容易加戏想在传输前对整个文件算一次MD5然后传给服务端做全文件比对——这在超大附件场景下是个坑。全文件MD5的计算是CPU密集型任务。一个100MB的文件在普通办公电脑上算完MD5可能要十几秒到几十秒这期间页面会卡顿用户会以为是浏览器死了。如果算到一半手滑关了页面一切重来。在分片模式已经保证每片完整性的前提下全文件MD5的意义被大大稀释了。我当时采取的办法是不做全文件MD5只依赖WebUploader的分片MD5校验合并后再做“大小校验首尾分片内容校验”。如果文件严重错乱或缺失合并后的大小和首尾内容一定对不上立刻就能发现。只有在分发特别重要的正式文件时才额外做一次服务端的全文件MD5但也是异步计算不能阻塞上传流程。2.4 断点续传的必要性大文件传输断点续传是最影响用户体验的功能。WebUploader官方实现断点续传依赖的是浏览器端的localStorage也就是把已上传分片的信息存在用户本地。但单位OA的使用场景很特殊很多人共用电脑、浏览器经常清理、系统还可能部署在用IE内核的国产浏览器上localStorage随时可能丢失一旦丢失断点续传就失效了。更稳的方案是“服务端分片续传”。也就是服务端保留临时分片文件前端在开始上传前向后端接口查询“这次上传已经有哪些分片”然后跳过这些已上传的分片只传缺失的部分。这个方案的实现并不复杂后端多提供一个查询接口即可。后面章节我会给出具体实现办法。但也要注意服务端临时分片不能无限期保留必须设置过期时间。设计上超过24小时未合并的分片要自动清理避免磁盘被无用的残留文件占满。3. 前端实操WebUploader关键配置与封装3.1 最小可用的WebUploader配置接入WebUploader的第一步是引入资源从官方CDN或本地静态目录载入js和css然后初始化实例。下面这段配置是经过实测可以直接用的每一步都有明确目的。var uploader WebUploader.create({ swf: /static/js/Uploader.swf, server: /api/oa/upload/upload_chunk.php, pick: #picker, accept: { title: Files, extensions: doc,docx,pdf,xls,xlsx,zip,rar,jpg,png, mimeTypes: *.* }, chunked: true, // 开启分片 chunkSize: 4 * 1024 * 1024, // 分片大小4MB threads: 3, // 并发上传数 fileVal: file, // 后端接收分片的文件字段名 formData: { uploadId: // 动态生成的uploadId下面会讲 }, fileSingleSizeLimit: 2 * 1024 * 1024 * 1024, // 单文件最大限制2GB duplicate: true, // 允许选择重名文件 auto: false // 先加入队列手动触发上传 }); uploader.on(beforeUpload, function(file) { // 给每个文件生成一个唯一uploadId var uploadId Date.now() _ Math.floor(Math.random() * 100000); uploader.options.formData.uploadId uploadId; uploader.options.formData.fileName file.name; uploader.options.formData.fileSize file.size; // 如果要用服务端断点续传在这里先请求后端查询uploadId对应的已上传分片 // 把已上传的分片编号列表传给WebUploader让它跳过这些分片 // 具体代码见下面的“断点续传实现” }); uploader.on(uploadSuccess, function(file, response) { // 分片都上传完了WebUploader会自动回调这个事件 // 在这里通知服务端执行合并操作 if (response response.status success) { $.post(/api/oa/upload/merge.php, { uploadId: uploader.options.formData.uploadId, fileName: file.name, fileSize: file.size, chunks: Math.ceil(file.size / uploader.options.chunkSize) }); } }); uploader.on(uploadError, function(file, reason) { console.error(上传出错, file, reason); });这段配置里auto:false是刻意关闭自动上传等用户点一个上传按钮再开始跑批量队列。对于一些需要用户先填写文件说明、再提交的场景这样更可控。3.2 自定义uploadId的设计WebUploader自带一个file.id是整个组件内部的全局递增ID但它对服务端来说没有实际意义也无法用于断点续传。所以我在beforeUpload事件里重新生成了一个uploadId格式是“时间戳随机数”。这个ID是整个上传过程的唯一标识后续每个分片请求都会带上它服务端根据它来组织临时分片。这个uploadId在断点续传场景下还有一个妙用如果用户删掉文件重新选择同一个文件beforeUpload会重新生成一个新的uploadId旧的分片就无法复用了。此时可以加一层“文件指纹匹配”用文件名文件大小最后修改时间生成一个粗略指纹相同指纹就延续之前的uploadId不同就新建。这个做法不需要MD5计算代价极小但实现了近似断点续传的效果。3.3 分片命名的坑服务端接收分片后保存临时文件的名字不能直接用原始文件名。这是我在项目里踩过的坑。中文文件名经过PHP接收后编码可能是GBK也可能是UTF-8不同系统间的处理方式还不一致文件名里如果带了路径分隔符或特殊字符轻则文件存错位置重则可引发安全风险。正确的做法是服务端收到分片后统一用uploadId加分片序号来命名也就是{uploadId}_{chunkIndex}.part。整个过程完全不依赖原始文件名原始文件名只在合并阶段写入最终文件的元信息即可。这样不仅安全后期排查分片残留时也一目了然哪个用户传的、传到哪一片看文件名就知道。3.4 合并触发与重试机制WebUploader的分片上传完成后会自动触发uploadSuccess事件但这并不代表整个上传流程结束。真正的文件合并需要后端二次接口来触发。这里有一个细节uploadSuccess事件在分片上传成功后立即触发此时最后一个分片可能刚写完为了防止合并时文件还没落盘完整我个人习惯在uploadSuccess里加一个短暂延时比如500ms再请求合并接口。合并请求本身也要处理失败重试。如果合并接口因为服务端繁忙返回超时前端不能干等需要有一个重试按钮或自动重试逻辑。我当时的做法是合并失败时前端弹出一个提示同时保留uploadId用户点“重试合并”前端重新调用一次合并接口并不会重新上传分片。这个机制救了无数次急因为在文件传输完成后卡在合并环节是最冤的情况。4. 后端PHP怎么扛住分片压力4.1 接收分片的接口实现前端配置完成后后端就是重头戏。接收分片的接口要做三件事鉴权、落盘、响应结果。首先接口里必须校验用户登录状态不能让未登录的人往服务器传文件。其次需要判断本次分片是否合法包括分片序号是否在合理范围内、分片大小是否超过分片上限等防止有人伪造请求刷爆磁盘。核心代码如下?php // upload_chunk.php session_start(); // 鉴权确保用户已登录 if (empty($_SESSION[user_id])) { http_response_code(401); exit(json_encode([status error, message 请先登录])); } $uploadId isset($_POST[uploadId]) ? preg_replace(/[^0-9a-zA-Z_]/, , $_POST[uploadId]) : ; $chunkIndex isset($_POST[chunk]) ? intval($_POST[chunk]) : -1; $chunks isset($_POST[chunks]) ? intval($_POST[chunks]) : 0; if ($uploadId || $chunkIndex 0 || $chunks 0) { exit(json_encode([status error, message 参数不合法])); } // 限制分片大小超过8MB直接拒绝 if ($_FILES[file][size] 8 * 1024 * 1024) { exit(json_encode([status error, message 分片大小超限])); } // 临时目录按uploadId隔离避免单目录文件过多 $tempBase /data/oa_upload_temp/; $tempDir $tempBase . $uploadId . /; if (!is_dir($tempDir)) { mkdir($tempDir, 0755, true); } $destFile $tempDir . $chunkIndex . .part; if (!move_uploaded_file($_FILES[file][tmp_name], $destFile)) { exit(json_encode([status error, message 分片保存失败])); } // 返回当前已接收的分片列表便于前端断点查询 $received array_map(function ($file) { return intval(pathinfo($file, PATHINFO_FILENAME)); }, glob($tempDir . *.part)); exit(json_encode([status success, received $received]));这里最关键的是用uploadId创建独立目录把所有分片都放在同一ID下面。这样做安全性最好目录内文件数量不会无限膨胀而且后续合并时只需扫描这一个目录。4.2 合并分片的最优姿势合并分片是后端性能最敏感的一环。如果处理不当很容易把PHP进程内存和CPU抬到极限。不少新手会直接用file_get_contents把每个分片读入内存再拼接成一个大字符串一次性写入目标文件——这个写法在几十MB文件下还能凑合但到了200MB、500MB就直接内存溢出了。正确的合并方式是用流式读写一次只读一小块往目标文件里追加PHP自己只占极小内存。参考代码如下?php // merge.php session_start(); if (empty($_SESSION[user_id])) { http_response_code(401); exit(json_encode([status error, message 请先登录])); } $uploadId isset($_POST[uploadId]) ? preg_replace(/[^0-9a-zA-Z_]/, , $_POST[uploadId]) : ; $fileName isset($_POST[fileName]) ? trim($_POST[fileName]) : ; $fileSize isset($_POST[fileSize]) ? intval($_POST[fileSize]) : 0; $chunks isset($_POST[chunks]) ? intval($_POST[chunks]) : 0; $tempDir /data/oa_upload_temp/ . $uploadId . /; // 检查目录里是否有完整的分片序列 $received []; for ($i 0; $i $chunks; $i) { $partFile $tempDir . $i . .part; if (!file_exists($partFile)) { exit(json_encode([status error, message 缺少分片: . $i])); } $received[] $partFile; } // 最终存储目录按月份归档 $saveDir /data/oa_attachments/ . date(Ym) . /; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } // 拼接一个安全的存储文件名这里用时间戳随机数 $ext strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $allowedExt [doc, docx, pdf, xls, xlsx, zip, rar, jpg, jpeg, png]; if (!in_array($ext, $allowedExt)) { exit(json_encode([status error, message 文件类型不允许])); } $savePath $saveDir . date(YmdHis) . _ . mt_rand(1000, 9999) . . . $ext; // 流式合并fopen fread fwrite逐块写入 $destHandle fopen($savePath, wb); if (!$destHandle) { exit(json_encode([status error, message 无法创建目标文件])); } $totalSize 0; foreach ($received as $partFile) { $srcHandle fopen($partFile, rb); if (!$srcHandle) { fclose($destHandle); exit(json_encode([status error, message 分片打开失败])); } while (!feof($srcHandle)) { $buffer fread($srcHandle, 8192); fwrite($destHandle, $buffer); $totalSize strlen($buffer); } fclose($srcHandle); } fclose($destHandle); // 合并后做大小校验防止传输出错 if ($totalSize ! $fileSize) { unlink($savePath); exit(json_encode([status error, message 文件大小校验失败])); } // 清理临时分片目录 array_map(unlink, glob($tempDir . *.part)); rmdir($tempDir); // 入库记录返回访问路径 file_put_contents(/data/oa_upload_temp/upload_log.txt, date(Y-m-d H:i:s) . uploadId:{$uploadId} file:{$fileName} size:{$totalSize} path:{$savePath}\n, FILE_APPEND); exit(json_encode([status success, path $savePath]));这段代码有几个细节值得注意。fread的缓冲块设成8192字节是吸取了经验教训这个值对磁盘IO性能相对友好。合并后校验总大小是必须的因为分片上传过程中可能出现分片丢失或错序大小对不上就立刻删除目标文件防止把损坏文件入库。4.3 内存、超时与并发控制在PHP层面分片模式天然把单个请求的负载降了下来。以前post_max_size必须设置到1GB才能传大文件现在分片模式下单次请求最多传一个4MB的分片加上一些请求头post_max_size设为8MB就绰绰有余。同理upload_max_filesize也可以降到8MB或10MB这对服务器是极大的释放。真正需要注意的是合并接口。合并大文件是一个相对耗时的操作虽然流式读写已经大幅减少了内存占用但CPU和磁盘IO消耗依然可观。我在代码里加入了set_time_limit(0)避免PHP在合并200MB文件时被默认的30秒超时掐掉。同时memory_limit保持256MB即可因为流式读写基本不吃内存。并发这块PHP-FPM的进程池要够用但也不宜盲目调太多。对于4核8G的服务器pm.max_children设为20到30之间比较合理。每个上传接口处理时间短一旦分片上传完毕进程立即释放。并发5个上传任务同时进行占用的进程数大概是15个左右其余业务请求依然有资源可用。4.4 临时目录与垃圾清理策略分片临时目录如果不做清理磁盘迟早被占满。很多人在改造时最容易忽略这个环节。用户在传大附件时中断上传、关闭浏览器、断电都会在temp目录留下若干半成品分片。我在项目中遇到过一次一个月的量积累下来临时目录吃掉了整整36GB磁盘空间直接把业务服务器磁盘撑满了。清理策略我放在了一个定时任务里每天凌晨执行一次删除最近24小时未被修改过的临时目录。判断依据是分片文件的mtime超过24小时没有新分片写入的目录视为“僵尸上传任务”整个目录直接删除。这个策略实现简单效果也最直接。后来还补了一条用户主动取消上传时前端调用一个cancel接口服务端当场清理该uploadId的所有临时分片避免垃圾堆积。5. 效率优化的几个进阶手段5.1 合并时的IO节奏优化流式合并虽然正确但如果分片数量特别多、文件特别大还会遇到另一个问题大量的小块fread和fwrite在Linux默认的IO调度下会产生很多小型的随机写入操作最终文件的写入效率并不高。一个经验做法是调节读缓冲的大小从8192字节提升到65536字节甚至1MB。实测在机械硬盘环境下1MB的缓冲块比8KB的缓冲块合并速度能提升30%到50%。当然如果用的是SSD差距没那么明显但缓冲块越大意味着每次系统调用能搬运更多数据对减少CPU开销也有帮助。5.2 校验逻辑分级处理合并时全量校验所有分片的MD5在大文件场景下也是不小的开销。前面提到过我日常只做大小校验这个校验成本几乎为零。但如果业务要求高可靠性可以做“分级校验”默认只校验大小和首尾分片的MD5如果系统检测到近期上传失败率偏高可以临时开启全分片MD5校验作为故障期间的加强手段。这样平时性能最优需要时又能调高可靠性。注意分片MD5计算在WebUploader前端已经做了一遍服务端如果再做全量MD5相当于一进一出两次CPU开销容易拖垮服务器务必谨慎启用。5.3 存储层渐进式升级改造初期合并后的文件直接存本地磁盘这是最省事的方案。但本地磁盘有两个隐患一是单块磁盘容量有限附件归档多了容易扛不住二是如果这台服务器故障所有附件都会丢。单位OA在下一次改造时可以考虑把存储迁移到共享存储或对象存储但这一步完全不必和分片传输改造绑在一起。类似渐进式改造的经验是先在应用层把上传接口和存储路径抽象一层将“保存路径”设计成可配置的这样后面换成对象存储时改动点会非常小。老系统的改造就怕大动干戈把核心业务逻辑搞乱。分的越细风险越小。5.4 日志埋点与分析排查效率问题时日志是最直接的线索来源。我在改造时加了一个极简的文本日志每次上传完成记录一行上传ID、文件名、文件大小、分片数、传输耗时、合并耗时、最终路径。这些数据积累到一定量后可以做一次简单的统计看看大部分文件的耗时分布以及哪些环节最慢。有一次性能优化就是从日志里发现的端倪某段时间内合并耗时突增但文件大小并没有显著变化。查下来发现是共享存储所在磁盘上有大量其他项目在跑IO挤占了我们合并操作的数据读写。通过日志定位到性能拐点配合存储侧调整队列问题很快得到解决。不要小看一个文本日志很多生产环境的问题只有靠数据才能定位。6. 常见问题与排查技巧实录6.1 问题速查表现象可能原因排查手段解决方案分片上传到100%卡住不动合并接口超时或报错看Nginx/PHP-FPM日志手动请求merge接口merge代码加set_time_limit(0)前端增加重试合并大文件传着传着被拒绝网关上还有client_max_body_size限制检查整个链路的代理层配置调大参数或确认分片模式下单请求体没超限合并后文件打不开分片序号排序错误或分片缺失检查临时分片目录中文件数量合并代码严格按chunkIndex升序合并上传接口偶尔500临时目录权限不对或磁盘满检查存储挂载和写权限为临时目录单独划分空间并加监控上传过程中系统变慢PHP-FPM进程被上传任务占满使用top查看php-fpm进程数调低threads数或限制同时上传人数清理临时文件总是失败目录权限或PHP进程用户不一致查看cron日志用系统级定时任务调用CLI脚本别走Web请求用户反馈“断点续传无效”localStorage被清或浏览器换机询问使用场景改用服务端查询已传分片实现续传6.2 踩坑过程分片错乱如何处理有一段时间合并出来的文件偶尔会出现中间段落内容错位、但大小却完全一致的情况。排查了很久最后发现问题出在一个隐性逻辑上某台老旧办公机的浏览器并发上传时部分分片的请求被代理层做了重试同一个分片被服务端接收了两次而且两次请求的响应顺序不同导致服务端分片文件被第二次写入覆盖成了其他分片的内容。当时已经把分片命名为{chunkIndex}.part按道理不会被覆盖错误。深入查找后发现服务端move_uploaded_file在处理重复请求时因为临时目录里已经存在同名文件存在竞态问题。解决办法是在接收分片时先检查同名文件是否已存在如果已存在且大小一致直接跳过写入并返回成功不做二次覆盖。这个“幂等接收”逻辑非常关键它能容忍网络层重试保证一个分片只会被写入一次。6.3 大附件上传的终极安全防线老OA系统做附件上传改造安全始终不能放松。除了前面提到的登录校验、目录隔离、禁止原始文件名之外还有两个细节要特别注意。第一分片不能只信任前端传的chunks接口里要限制总大小。比如单文件最大2GB那么chunkIndex不能超过2GB / chunkSize。否则有人可以伪造几百个分片请求把磁盘写满。第二合并后的文件要用finfo检测真实文件类型而不是简单信任扩展名。常见做法是把不合规的MIME类型全部拒绝。国企内部办公系统虽然不像公网系统那样天天被攻击但安全评审对文件上传模块的审查一向严格这些点提前补齐后面等评审时就不用返工。7. 实测数据与调优记录7.1 改造前后的直观对比以我们最近处理过的一个案例为例。环境是某单位内网办公网交换机为千兆但多数电脑网卡是百兆实际有效带宽约8MB/秒到10MB/秒服务器为4核8G的老机器磁盘为SATA SSD。用80MB的PDF文件做测试指标改造前整体上传改造后分片上传上传耗时平均13分钟常超时报错平均12秒左右失败率约30%低于3%服务器CPU占用上传时单核接近100%三线程并发时约40%用户操作体感页面长时间假死无法做其他事可同时处理其他页面有实时进度这个对比虽然和服务器带宽、硬件有关系但足以说明问题。分片传输不只是“能传了”而是把整个上传过程从“阻塞型”变成了“异步型”用户不再需要死死盯着进度条等待。7.2 调优过程中的几点个人体会第一不要一上来就追求最大速度。最初我把并发调到5分片调到8MB测试时大文件确实传得快但PHP-FPM经常被打到高负载其他OA页面打开都变慢。后来把并发回调到3虽然传输慢了一点点整体稳定性却好得多。系统是给人用的单点性能最优不代表整体最优。第二一定要在真实网络环境里测试。开发环境是千兆光纤直连测不出弱网下的表现。我把测试电脑接到一台百兆交换机上故意模拟低带宽场景才发现了分片重试会导致重复写入的隐患也才明白为什么前同事以前一直抱怨大附件总是传着传着就失败。第三WebUploader官方文档虽然并不多但针对它的兼容性坑、事件机制网上有足够多的实践总结。遇到问题优先搜索不要轻易去改它内部的源码。前期项目组有人试图给WebUploader扩展更多功能最后发现升级组件版本反而更容易导致不可控的回退。最后再分享一个小技巧分片目录的命名建议按uploadId做一层哈希比如取前两位字符再嵌套一层子目录避免几万个上传任务同时堆在一个目录下造成文件系统查询效率下降。这个优化在做海量附件归档时效果非常明显但平时几十个并发任务时感觉不出来。用不用取决于系统的日均上传量有多大。