
工程建筑行业这两年有个特别现实的问题图纸越来越大一个完整的建筑方案设计包动辄几百MB涉及到BIM模型的甚至能到几个GB。可很多工程企业的内部管理系统还停留在传统的单文件上传模式一张几十MB的CAD图纸传半天传到一半断网直接从头再来体验非常糟糕。超大文件分块上传就是来解决这个问题的——把大文件切成若干块逐块上传后端接收后再按序合并成完整文件。这篇文章我会结合Java后端开发的实际经验把分块上传在工程建筑场景下的设计思路、核心代码、参数调优和踩坑记录完整梳理一遍给正在做工程行业网页开发的同行一个可直接落地的参考。1. 工程建筑行业的文件上传场景与核心痛点1.1 一个设计院的真实场景我去年接触过一个设计院的项目他们内部有一个图纸管理网页系统用户每天要上传大量施工图、竣工图和设计变更单。正常情况下单张图纸几十MB到一两百MB这还好说真正麻烦的是那些成套交付的图纸。设计院在项目结束时需要交付整套PDF文件一个压缩包下来500MB到1GB是很常见的事。更夸张的是BIM模型文件一个完整的三维模型动辄2GB以上。在这个系统上线之前他们的做法是让设计人员把大文件放到一个FTP服务器上再在网页系统里填一个链接地址。文件虽然能传但是文件不在系统统一管理范围内版本容易乱而且FTP目录权限管理也很麻烦一个不小心就把别人的文件覆盖了。内部系统如果用网盘或者企业微信来传文件流转链路又断掉了系统里没有对应记录后续的审核、归档、版本管理全成了空谈。网页上传在这类场景下的核心矛盾是文件太大传统的HTTP请求一次性提交全部数据任何一点网络波动都可能导致整个请求失败。工程行业的设计人员经常在工地现场办公工地的网络环境大家都懂4G信号不稳定WiFi经常拥塞一个500MB的文件在弱网环境下想一口气传完几乎不可能。就算是在办公室的专网里文件大了以后长时间占用一个连接服务端代理超时、浏览器请求超时这些问题都会冒出来。1.2 分块上传到底解决了什么分块上传的思路其实很朴素就像搬家一样一个大柜子搬不进电梯那就拆成板件一块一块搬上去到了楼上再按图纸拼装起来。文件传输入同理前端把大文件按固定大小切成很多个小块每个小块单独发起一次HTTP请求后端先把每个小块保存到临时目录等所有小块都到齐了再按顺序把这些小块合并成完整的原始文件。这样做至少带来三层直接收益。第一层是容错能力强了。原来传一个500MB的文件只要中间断一次整个请求就废了前端只能从头再来。分块之后任何一个块失败了只需要重传那一个块已经传好的块不需要动。第二层是请求体变小了单次请求的数据量从几百MB降到了几MB不会触发服务器的请求体大小限制也不会因为长时间占用连接而被超时杀掉。第三层是能直观展示上传进度前端可以统计已完成的块数除以总块数把一个进度条展示给用户。工程建筑行业的文件还有一个特殊性文件往往需要归档和追溯。分块上传过程中服务端可以记录每个块的接收状态哪块到了哪块没到一目了然。这个状态记录不仅用于断点续传还能在文件校验时提供依据万一合并后的文件MD5校验不过可以定位到具体是哪个块出了问题这个能力是传统整体上传完全不具备的。1.3 分块上传与断点续传、秒传的关系很多人容易把分块上传和断点续传混为一谈其实它们是两个层面的事情。分块上传是传输机制断点续传是用户体验策略秒传是文件去重策略三者可以叠加使用。分块上传解决的是“大文件怎么传”的问题它把一次大请求拆成了多次小请求。断点续传解决的是“传了一半怎么接着传”的问题它需要前端记住哪些块已经成功上传了下次打开网页时跳过这些块。秒传解决的是“同样的文件需不需要重复传”的问题它靠的是计算文件的唯一标识如果服务器上已经存在相同文件直接返回上传成功根本不用传数据。在工程行业系统里这三个功能我建议一次性全做掉。设计院的项目文件经常在多个项目之间复用同一个标准图集、同一个模板文件会被反复上传。如果做了秒传每次能省掉几分钟到几十分钟的传输时间用户的体感提升非常明显。而且这三者的技术底座是一样的都需要一个能够唯一标识一次上传任务的ID通常是文件哈希或UUID所以实现分块上传的同时把另外两个功能一起做了成本很低。2. 分块上传的整体架构与方案选型2.1 前端切片、后端拼装的完整链路分块上传的完整链路可以分为五个环节。第一个环节是文件标识。前端在用户选择文件后生成一个唯一标识这个文件的key这个key会作为本次上传任务的ID贯穿始终。工程场景下我建议直接用“文件大小最后修改时间文件名”做一个组合标识简单可靠不需要等待哈希计算。第二个环节是任务初始化。前端在真正上传前先调用后端查询接口把这个标识传给后端后端查一下这个文件是不是已经有部分块传过了。如果全部传过就直接返回“已完成”如果部分传过就返回“已存在的块编号列表”如果没有就返回“从0开始”。第三个环节是切片上传。前端用File对象的slice方法把文件切成固定大小的块然后按照后端返回的缺失列表逐个上传每个块。每个上传请求里除了二进制数据本身还要带上任务标识、当前块的序号、总块数这几个参数。第四个环节是合并。所有块上传完成后前端调用后端合并接口后端检查块完整性按序号读出所有块的数据拼装成完整文件。第五个环节是清理与确认。合并成功后后端的临时目录要清掉同时返回最终文件的存储路径或访问URL前端把结果写入业务表单整个流程结束。这个链路里有一个关键点切片这个动作必须在前端做不能让后端来切。因为HTTP请求是一个完整的数据流服务端拿到文件的时候整个文件已经进来了根本起不到分块传输的作用。分块的思想在于把“端点之间的传输数据量”降下来而不仅仅是服务端分片存储。2.2 三种常见实现方案的对比我在实际项目中见过三种分块上传的主流实现方式它们各有适合的场景。第一种是用现成的分块上传组件比如WebUploader、Plupload、Resumable.js这些。这类组件把前端的切片、重试、进度展示都封装好了后端只需要按约定接口接收即可。优点是上手快半天就能跑通一个demo。缺点是这些库很多年不更新了而且遇到WebUploader这种依赖flash的在现在的浏览器环境下已经跑不起来了。工程行业的系统通常部署在内网浏览器版本可能比较老旧选型时一定要确认兼容性。第二种是自己写前端逻辑用axios加上File.slice手动实现切片后端用Java自研接收接口。这种方案没有第三方库的兼容性顾虑代码量也不大前后端都掌握在自己手里出问题好排查。我非常推荐工程行业系统用这个方案因为建筑企业的内网环境千奇百怪有些电脑还是老旧的Windows 7配IE浏览器自研方案可以把兼容性问题控制在最小范围。第三种是用云存储的分片上传SDK比如阿里云OSS或腾讯云COS的上传接口。这种方式适合公网应用上传速度快自带断点续传。但工程企业很多系统要求数据保存在本地或者已经采购了自己的服务器和存储设备不太适合直接上云。而且内网系统要对接公网云存储等于数据出走了内网在很多企业是有合规风险的。选型建议很简单如果是给外部客户做互联网产品直接上云SDK如果是工程企业内部系统自己写前端切片加Java后端接收是最稳的。我后面讲的核心代码就是自研方案。2.3 工程文件场景该选哪种还有一个容易被忽略的问题工程行业的文件不仅仅是“大”还很“杂”。既有Word、Excel这类Office文档又有PDF、DWG、Revit模型、倾斜摄影OSGB数据还有压缩包。这些文件在分块上传时前端切片的逻辑完全一样但在存储侧要考虑访问方式。图纸和文档通常需要在线预览所以上传完成后最好存到一个和业务系统隔离的文件存储区并生成访问代理。BIM模型这类文件体积巨大但打开频率不高可以存到NAS或者对象存储里不在业务系统本地目录里堆着。分块上传本身不关心文件是什么类型它就是把一个字节流完整地送到服务端所以架构上要把“传输”和“存储”解耦上传模块只负责把文件分块传到临时区并合并存储位置的选择交给上层业务根据文件类型去决定。另外工程文件中文件名特别容易重复一个项目的图纸可能叫“总平面图.dwg”另一个项目也这么叫。分块上传时如果用原始文件名直接落盘冲突率非常高。我在设计存储目录时一律使用任务IDUUID建目录文件名入库时只作为元数据保存物理文件名用UUID重命名。这样做不仅在传输阶段不会冲突也为将来做版本管理留了余地。3. 后端核心实现Java接口与合并逻辑落地3.1 接口设计一个“报数”式上传模型后端接口的设计思路可以用一个简单的比喻来理解前端是一个快递员后端是仓库管理员。快递员不能把整个大件一次性拖进仓库而是拆成小包裹每送到一个包裹就报一次“单号、这是第几件、总共几件”管理员把这些包裹放进对应的货架上。等到最后一个包裹送到快递员喊一声“齐了”管理员再把货架上的包裹按顺序拼成完整的大件。对应的Java接口有三个就够。第一个是查询接口根据文件标识查询上传状态返回已上传的块编号集合GetMapping(/upload/status) public Result getUploadStatus(RequestParam(fileKey) String fileKey) { String uploadDir getUploadDir(fileKey); File dir new File(uploadDir); ListInteger uploadedChunks new ArrayList(); if (dir.exists()) { File[] files dir.listFiles(); if (files ! null) { for (File f : files) { uploadedChunks.add(Integer.parseInt(f.getName())); } } } return Result.ok(uploadedChunks); }第二个是分块上传接口接收一个分块的二进制数据以及任务标识、块序号、总块数PostMapping(/upload/chunk) public Result uploadChunk(RequestParam(file) MultipartFile chunk, RequestParam(fileKey) String fileKey, RequestParam(chunkNumber) Integer chunkNumber, RequestParam(totalChunks) Integer totalChunks) { String uploadDir getUploadDir(fileKey); File dir new File(uploadDir); if (!dir.exists() !dir.mkdirs()) { return Result.error(创建上传目录失败); } // 用文件分块目录是否存在来判断当前块是否已上传避免重复写 File chunkFile new File(dir, String.valueOf(chunkNumber)); if (chunkFile.exists() chunkFile.length() chunk.getSize()) { return Result.ok(该块已存在); } // 将临时目录的命名策略设计为“任务ID 分块序号” // 合并阶段只需按序号排序读取不需要数据库记录 try (InputStream in chunk.getInputStream(); FileOutputStream out new FileOutputStream(chunkFile)) { byte[] buffer new byte[8192]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } } catch (IOException e) { return Result.error(分块写入失败); } return Result.ok(); }第三个是合并接口检测所有分块是否到位然后顺序读入合并PostMapping(/upload/complete) public Result completeUpload(RequestParam(fileKey) String fileKey, RequestParam(totalChunks) Integer totalChunks, RequestParam(originalName) String originalName) { String uploadDir getUploadDir(fileKey); File dir new File(uploadDir); // 先校验分块是否齐全 for (int i 1; i totalChunks; i) { File chunkFile new File(dir, String.valueOf(i)); if (!chunkFile.exists()) { return Result.error(缺少分块: i); } } // 生成最终存储路径定义文件存储规则UUID前缀防止重名 String ext getExtension(originalName); String targetFileName UUID.randomUUID().toString().replace(-, ) ext; String targetPath getStorageDir() File.separator targetFileName; // 执行合并 boolean success mergeChunks(dir, targetPath, totalChunks); if (success) { // 重要合并成功后清理临时目录避免磁盘被分块占满 deleteQuietly(dir); // 返回业务侧需要的文件信息 FileInfoVO vo new FileInfoVO(); vo.setFilePath(targetFileName); vo.setFileName(originalName); vo.setFileSize(new File(targetPath).length()); return Result.ok(vo); } return Result.error(合并失败); }这三个接口的设计非常规整。查询接口负责断点续传的判断上传接口负责接收分块合并接口负责完成最后一步。三个接口共用一个fileKey这个fileKey就是前端生成的文件标识。3.2 分块合并的正确姿势分块合并是整个流程里性能压力最大的一个环节很多新手在这里踩坑。最容易犯的错误是把每个分块都读进内存再写出来。一个500MB的文件切成50个10MB的块循环读取时如果用的是byte[]一次性读入内存开销极大并发上传时直接把JVM堆撑爆。正确的做法是用Java NIO的FileChannel做零拷贝传输让操作系统直接把块文件的数据搬运到目标文件里不经过JVM内存private boolean mergeChunks(File chunkDir, String targetPath, int totalChunks) { try (FileChannel outChannel FileChannel.open(Paths.get(targetPath), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { for (int i 1; i totalChunks; i) { File chunkFile new File(chunkDir, String.valueOf(i)); try (FileChannel inChannel FileChannel.open(chunkFile.toPath(), StandardOpenOption.READ)) { inChannel.transferTo(0, inChannel.size(), outChannel); } } return true; } catch (IOException e) { log.error(合并失败, e); return false; } }这段代码的关键在于逐块追加每个分块按序号顺序被传输到输出通道末尾文件从第一个字节到最后一个字节完全按原始顺序拼装。对于工程图片这种二进制格式顺序一旦错乱文件就损坏了。排序逻辑在代码里就是“序号从1到totalChunks”所以前端在给分块编号时一定要从1开始而不是从0开始这点前后端要约定死。FileChannel.transferTo的效率比普通流复制高很多因为它利用操作系统的sendfile机制省去了用户态和内核态之间的多次数据拷贝。我实测过在普通机械硬盘上合并一个400MB的文件用普通流需要六七秒用FileChannel只需要一两秒差距非常明显。对于GB级文件这个差距会扩大到几十秒所以直接用FileChannel是必须的。3.3 临时目录管理与文件清理策略分块上传会产生大量的临时文件如果只顾上传不管清理用不了多久服务器磁盘就会被塞满。我见过一个项目就是上线后没人管临时目录半年后磁盘告警才发现upload目录下有几百GB的垃圾分块。清理策略分三个层次来做。第一个层次是合并成功后立即清理。上面的代码里已经写了合并完成后用deleteQuietly递归删除整个任务目录。这个是最基本的。第二个层次是上传失败或超时的孤儿分块清理。网络环境恶劣时用户可能传了一半就关掉了浏览器这些分块永远不会被合并也不会被触发删除。我建议写一个定时任务每天凌晨扫描临时目录删除创建时间超过24小时且没有被合并的任务目录。注意要只删临时目录不碰正式存储目录。第三个层次是磁盘空间保护。可以在分块上传接口里检查临时目录所在磁盘的剩余空间如果剩余空间小于某个阈值比如只有5GB了直接拒绝新上传并提示管理员清理。工程文件经常几个GB一个这个检查非常有必要。我通常把临时目录和正式存储目录放到不同磁盘或者至少不同目录分区这样即使临时目录被撑爆也不会影响正式文件的读写。4. 前端配合要点与断点续传实战4.1 切片大小怎么定切片大小是一个很值得说道的参数。太小了问题多一个100MB的文件如果按256KB切就是400个块每个块一次请求光HTTP握手和请求头就浪费大量的时间和带宽。太大了又失去分块的意义一个块20MB在弱网环境照样容易超时。工程建筑行业的大文件场景我推荐切片大小在5MB到10MB之间。给出一个具体的参考值内网传输带宽在100Mbps以上10MB的块比较合适外网或者工地弱网环境建议用5MB。可以用一个最简单的估算公式单个块传输时间控制在2到8秒之间算合理。按5MB块大小计算在10Mbps带宽下理论传输时间是4秒在50Mbps带宽下不到1秒整体体验都不错。前端切片的代码非常短const CHUNK_SIZE 5 * 1024 * 1024; const chunks []; let start 0; let index 1; while (start file.size) { const end Math.min(start CHUNK_SIZE, file.size); chunks.push({ index: index, blob: file.slice(start, end) }); start end; }切片之后不要一次性把所有块都丢给并发请求。浏览器对一个域名的并发连接数是有限制的一般是6个左右。如果一次性发50个请求它们会在浏览器队列里排队不仅没有提速反而占满了连接页面上的其他资源请求全部被阻塞。我建议把并发数控制在3到5个这个数量既能充分利用带宽又不会把服务器打满。4.2 并发控制与进度计算并发控制用简单的Promise队列就能实现不需要引额外的库async function uploadInQueue(chunks, concurrency 4) { let cursor 0; const workers []; for (let i 0; i concurrency; i) { workers.push(runWorker()); } async function runWorker() { while (cursor chunks.length) { // 这里要加锁防止多个worker取到同一个块 const currentIndex cursor; cursor; const chunk chunks[currentIndex]; await uploadChunk(chunk); // 更新进度 const percent cursor / chunks.length * 100; updateProgress(percent); } } await Promise.all(workers); }进度计算有两种方式。一种是简单按块计算已成功上传的块数除以总块数。一种是按字节计算把每个已上传块的真实大小累加除以文件总大小。按字节算更准确因为最后一个块可能会比其他块小按块数算的百分比会在最后一段跳变。工程文件动辄几百MB差几个百分点用户能明显感觉到建议按字节算。上传时表单数据要用FormData加上必要的参数async function uploadChunk(chunk) { const formData new FormData(); formData.append(file, chunk.blob, file.name); formData.append(fileKey, fileKey); formData.append(chunkNumber, chunk.index); formData.append(totalChunks, chunks.length); // 注意axios默认不会有超时但为了不无限等下去建议设置一个合理超时时间 await axios.post(/upload/chunk, formData, { timeout: 30000 }); }上传失败的重试可以直接在这个函数里做单个块连续失败3次才中断整个上传并提示用户检查网络。工程场景里网络抖动很常见一抖就断上传用户体验会很差。4.3 断点续传和秒传的实现思路断点续传的前提是后端查询接口能告诉前端“哪些块已经传过了”。用户重新打开页面选择同一个文件后前端用同样的规则生成fileKey然后调查询接口拿到已上传块列表在切片时直接跳过这些块。关键点是fileKey的生成规则必须一致。我用的规则是文件大小加上最后修改时间再加上文件名拼成一个字符串后做一次简单的哈希。之所以不直接算整个文件的MD5是因为大文件的MD5计算非常耗时。一个1GB的文件在前端算MD5可能要几十秒用户会以为页面卡死了。弱标识虽然不能做到100%精确但如果两个文件的名称、大小、修改时间完全一样在工程内网场景下几乎可以断定是同一个文件。秒传是在第一次查询时做的。如果后端发现同一个fileKey对应的合并后文件已经存在直接返回一个“已完成”的状态前端就不用再传任何分块了。为了可靠后端在合并后可以记录一个映射表把fileKey和最终文件的路径存下来。下次上传遇到同一个fileKey先查映射表数据库里已经有了就直接返回。这个映射表同时承担了版本管理的职责可以保留文件上传时间、上传人等信息。5. 工程场景专属优化与避坑指南5.1 服务端参数调优清单先说说Spring Boot里分块上传的配置。很多新手不知道Spring Boot的multipart默认单文件最大才1MB上传分块一旦超过这个值直接被拒绝报错信息是“The request was rejected because its size exceeds the configured maximum”。在application.yml里要这样调spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB注意max-file-size限制的是单个文件大小max-request-size限制的是整个请求的大小。分块请求里除了文件本身还有表单字段所以request-size要稍微大于file-size两个都设成20MB比较稳妥。还有一个容易忽略的地方如果前端并发数是4每个请求10MB那么Tomcat里4个请求同时占用的连接和IO是40MB这不体现在配置文件里但会影响线程池。我一般把Tomcat的max-threads设置在200左右工作队列能容纳的量也要相应评估。如果系统前面有Nginx还要改Nginx的配置client_max_body_size 20m; proxy_request_buffering off;proxy_request_buffering off这个参数很关键。如果不关闭Nginx会先缓冲整个请求体再转发给后端这样分块上传就白做了Nginx直接把你每块10MB的数据缓冲成一个大流最后还是会整体发给后端。另一个容易被忽视的地方是Nginx和Tomcat操作系统的连接超时。分块上传虽然单块小但整个文件上传过程很长如果Nginx的proxy_read_timeout设置成默认的60秒用户上传一个1GB文件过程中最后一次心跳间隔超过60秒连接就会被Nginx掐断。建议至少设置成300秒。5.2 文件校验与一致性问题分块上传的校验是分层的。第一层是单块校验第二层是合并后整体校验。单块校验比较简单记录每个块的字节数合并前检查每个块的大小与前端上报的是否一致。如果某个块的大小对不上说明传输过程中数据有损坏。合并后的整体校验有两种做法。一种是计算整个合并后文件的MD5和前端在上传前预览阶段算好的MD5进行对照。这是最强一致性保证但由于大文件前端算MD5耗时适合在上传完成后的异步阶段做。另一种是更轻量的检查文件大小是否等于前端上报的总大小加上分块大小校验工程内网场景基本够用。我在工程行业系统里见过一个很隐蔽的问题前端切片时用file.slice(start, end)end超出文件大小是没问题的slice会自动截断但有些老版本浏览器的slice实现有bug最后一次切片会得到一个空blob。这个问题表现为合并出来的文件比原始文件大了一个块的大小。解决方法是切片时显式判断end不能超过file.size我的切片代码里写的就是Math.min(start CHUNK_SIZE, file.size)这个min不能省。数据一致性还有一个并发层面的问题同一个用户重复提交上传任务或者多个用户上传同一个fileKey的文件会导致后端的合并逻辑并发执行。我在合并接口里加了一个简单的方法级锁以taskId为粒度做同步避免两个线程同时读同一批分块然后各写各的。如果系统规模大建议引入分布式锁内网中小系统用本地锁就够了。5.3 大文件上传的运维细节工程行业的文件存储必须考虑磁盘空间规划。一台普通服务器通常只有几百GB空间一个项目可能就能吃掉几十GB不做规划很快就满了。我给设计院做的方案是把文件存储单独挂一块4TB的NAS盘用软链接把系统里的存储目录映射到NAS挂载点这样Web应用和文件存储分离升级系统时不会误删文件。文件清理和备份策略也得考虑。工程图纸是重要的资产轻易不能删。但是临时分块文件必须严格的清理我在前面已经讲过。正式文件要按项目归档制定保留策略比如项目验收后转入冷存储。分块上传完成后如果正式文件已经存在于存储区并且fileKey相同可以把之前合并的旧文件标记为历史版本而不是覆盖删除这样就有了简单的版本管理能力。另外要做好操作日志。工程行业系统经常要接受审计谁在什么时候上传了什么文件必须有迹可循。分块上传这个动作虽然是技术行为但最终的合并产物是一个业务文件应该在合并成功的日志里记录操作人、文件名、大小、fileKey写进业务操作日志表。6. 常见问题与排查技巧实录6.1 高频问题的定位思路我整理一下在多个工程上传系统里反复出现的问题和排查方向。第一个高频问题是“页面提示请求体过大”。这个看后端日志几乎都是Tomcat或Spring的MaxUploadSizeExceededException。排查思路是检查Spring的max-file-size和max-request-size是否配置检查Nginx的client_max_body_size是否配置两边的限制都要大于单个分块的大小。第二个高频问题是“上传到一半连接被重置”。这个在工程弱网环境下太常见了。排查思路是先确认是不是物理网络闪断再看服务端日志有没有SocketTimeoutException。如果有超时异常调大Tomcat的连接超时和Nginx的proxy_read_timeout。另外建议前端增加自动重试单块失败后延迟1秒重试连续失败再中断。第三个高频问题是“合并后文件可以打开但内容缺失”。这个八成是分块顺序的问题。检查前端切片的index是否从1开始检查后端合并时是否按文件名升序读取。如果文件名是数字直接用数字比较排序不要用字符串排序因为字符串排序会变成1、10、100、2这样的顺序。第四个高频问题是“合并时JVM内存溢出”。这个多半是把分块读进了内存而不是用FileChannel。排查后端的合并代码是否借用了byte[]一次性输出把普通流替换成FileChannel就好。6.2 一套可以抄的排查步骤最后分享一套我自己排查分块上传问题的标准流程遇到报错直接按顺序来。第一步看现象。用户说“传不了”和“传一半失败”是完全不同的问题。传不了大概率是配置限制传一半失败大概率是网络或超时。第二步查前后端日志。前端浏览器F12看网络请求哪个块返回了错误状态码错误信息是什么。后端看Tomcat日志有没有异常堆栈。两边对齐时间点定位是哪个环节出的问题。第三步复现。自己找一个和用户场景相近的大文件按同样的大小和块数传一次。如果自己复现不了让用户提供浏览器版本、网络环境、文件大小这些信息对复现很重要。第四步清理现场。把所有临时分块清掉重新传一次排除是旧分块文件损坏导致的合并异常。很多时候问题就出在残留的旧分块上合并时读了一个损坏的块。这套流程我用了很多年虽然不复杂但能覆盖绝大多数分块上传的问题。工程行业系统很吃稳定性用户可不会因为你用的是分块上传就容忍失败把排查流程沉淀下来团队维护起来效率会高很多。最后再分享一个小技巧分块上传上线后建议在后台加一个上传监控页面实时展示正在进行中的上传任务数、每个任务的进度、临时目录的占用空间。工程文件大上传时间长运维人员能看到实时状态很多潜在问题在用户反馈之前就被发现了。根据我的经验这个监控页面的价值往往比想象中大得多。