ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java Web实现大附件分片直传:从切片到合并的完整方案

Java Web实现大附件分片直传:从切片到合并的完整方案

“老师上传一个30MB的Word教案,转圈半分钟后浏览器报‘连接已重置’。”这是我接手教育行业某教务系统项目后,听到最多的一句抱怨。教育场景里的Word教案附件,远不是几页纯文本那么简单——插入高清图、教材扫描页、录课截图、思维导图之后,体积轻松突破几十MB。而这种文件恰好卡在传统Web上传方案的尴尬区间:比几KB的文本大得多,又没到需要走独立对象存储的程度。

我当时的方案是:通过Java Web插件实现Word教案大附件的浏览器端分片直传。核心思路不复杂——前端用浏览器能力把大文件切成若干分片,逐片上传到Java后端接口,后端边收边写、最后合并还原。整个过程不依赖任何第三方存储服务,不要求老师安装客户端,只用一个普通Java Web项目就能扛住大附件上传压力。

这篇文章就把这个方案的完整落地过程拆开讲:为什么必须分片、前端怎么切、Java后端接口怎么设计、参数怎么算、上线后踩了哪些坑。如果你也在做教育行业系统,或者手里有类似“浏览器上传大文件”的Java Web项目,这文章里的步骤和配置基本可以照搬。

1. 为什么大附件不能走传统表单上传

很多教科书写都不会告诉你的现实是:HTTP协议本身的body大小并没有限制,真正卡住大文件上传的是Web容器、反向代理、浏览器内存和应用层代码。传统做法是把整个文件放到一个multipart/form-data请求里提交,这在文件小的时候没毛病,文件一旦上了几十MB,隐患接二连三暴露。

首先遭殃的是服务器内存。Java后端用Commons FileUpload或Spring的MultipartFile接收大文件,容器通常会把文件先写入临时目录,表面上没占内存,但框架在处理过程中会把流对象加载到内存做检测,遇到高并发那就是内存溢出的前奏。我们线上出现过一次Tomcat直接OOM,排查发现就是老师集中提交教案那段时期,几个50MB左右的文件同时上传,每个吃掉了几百MB堆内存。

其次是超时和断点问题。文件越大,传输耗时越长,中间任何一次网络波动、浏览器tab切走、nginx超时,整个上传就废了。老师办公区的Wi-Fi向来不稳定,30MB文件传到一半断掉,再点一次又要全部重来,体验可想而知。更致命的是很多教育系统部署在校内机房,带宽有限,网关和代理服务器还有自己的超时策略,大包被静默掐断的情况特别常见。

分片直传之所以是正解,本质上是把“一个不可控的大请求”拆成“多个可控的小请求”。每个分片只有几MB,单次请求时间短,失败后只需重传那一个分片,不必全量重来。而且分片天然支持并行,多个分片同时传,能充分利用带宽,比串行传一个大文件快不少。

“直传”这两个字也很关键。有些方案会把文件先传到某个临时中转服务,再由后端程序搬运到目标服务器——这多一层中转就多一个故障点。我们这里的直传,是指浏览器直接跟Java Web后端的分片接口对话,后端每收完一个分片就落盘,减少链路上的“搬运工”角色。所有逻辑都在应用自身处理,对教育行业这种服务器资源有限的场景最务实。

2. 浏览器端分片的核心逻辑与前端实现

2.1 先理清楚分片上传的完整链路

动手写代码之前,先把整条链路画清楚。前端做的事情是:读取文件、切片、并发上传分片、通知后端合并。后端做的事情是:初始化上传、接收分片、校验完整性、合并文件。一共三个接口就能支撑整条流程:

  1. 初始化接口:前端把文件名、文件大小、预计分片数告诉后端,后端创建一个上传任务,返回一个uploadId。
  2. 分片上传接口:前端把每个分片的二进制数据连同uploadId、分片序号传给后端,后端保存分片。
  3. 合并接口:前端传完所有分片后调合并接口,后端把所有分片拼成完整文件落盘。

这三个接口构成了分片直传的最小可用闭环。我倾向于额外加一个“查询已上传分片”的接口,用途是前端断线重连时能知道哪些分片已经传过了,只补传缺失的部分。这个接口在分片方案里性价比极高,代价只是后端保存一下已接收分片的序号列表。

2.2 前端切片怎么切才靠谱

前端切片非常简单,现代浏览器都支持File对象,直接用slice方法切割即可。以某次改造为例,教学平台上的教案附件普遍在20MB到80MB之间,我把分片大小设定为5MB,一个80MB的文件切出来16个分片,数量适中,既不产生过多请求,单个分片也不至于大到重传困难。

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB function createChunks(file) { const chunks = []; let start = 0; while (start < file.size) { const end = Math.min(start + CHUNK_SIZE, file.size); chunks.push({ file: file.slice(start, end), index: chunks.length, currentSize: end - start }); start = end; } return chunks; }

切片本身是纯前端计算,不占用服务器资源。这里有个细节:切片不能光切,还要给每个分片编上序号(index),这个序号是后端合并文件的排序依据。如果前端不传序号,后端合并时只能用文件名排序,但文件名排序对分片编号是“凑巧”,规规矩矩传序号最稳。

2.3 并发上传怎么写才不容易崩

分片准备好了,接下来要决定怎么传。用串行方式逐个传,一个分片一个请求,稳定但速度慢,而且没有发挥分片方案的优势。直接在代码里一次性把所有分片并发发出去,又容易压垮后端,尤其教育系统部署的服务器配置往往一般,接口并发能力有限。

折中做法是控制并发数。我实际用的是“3个分片同时传”的策略。数不大但效果明显,一个80MB文件分16片,按这个并发大概6个批次就能传完,加上网络传输时间,整体表现比串行快不少。

这个并发数的选择背后有依据:浏览器对同一域名的并发连接数有限制,HTTP/1.1协议下六大主流浏览器默认就是6个并发,超过的请求会排队。同一个页面既要传分片,可能还要加载其他资源,留出一些连接余量,前端取3这个值是从实测结果里确认过的——再提高并发数,对总耗时的改善极其有限,反而开始出现服务器连接等待和超时。

并发控制用简单的计数就能实现,不必引入复杂的异步框架。核心思路是:维护一个待上传队列,始终保证正在上传的分片数等于并发上限,任何一个分片传完,立即从队列里取下一个分片补位。

async function uploadChunksWithLimit(chunks, limit, uploadId, onProgress) { const queue = [...chunks]; let activeCount = 0; while (queue.length > 0) { if (activeCount >= limit) { await new Promise(resolve => setTimeout(resolve, 100)); continue; } const chunk = queue.shift(); activeCount++; uploadOneChunk(uploadId, chunk) .finally(() => { activeCount--; onProgress(chunk.index); }); } }

写这段代码时最需要注意的,是在finally里减少activeCount——我第一次写时放在then里,一旦分片上传报错,activeCount永远不肯减,队列直接堵死。这种小坑,不实际跑一遍线上场景根本发现不了。

2.4 进度计算和失败重试

进度条是教育场景里老师唯一看得见的反馈,一定得做。计算逻辑非常简单:以字节为单位统计已成功上传的分片大小之和,除以文件总大小,再乘100。由于分片大小固定,最后一小片往往不足5MB,所以后端返回成功信息时最好带上分片的实际字节数,前端累加这个数最准确,而不是一律按5MB累加。

失败重试策略是我踩过几次坑才调顺的。分片上传失败有各种原因:Wi-Fi闪断、代理超时、服务器临时出错。我的处理是:每个分片允许最多重试3次,失败后用指数退避策略,第一次等1秒,第二次等3秒,第三次等7秒。连续失败就提示前端“当前网络不稳定,请检查后继续”,并把已上传的分片进度保留下来,等老师恢复网络后继续。

这种基于“断点续传”思路的设计,是分片直传方案比传统上传体验好得多的根本原因。老师不用因为一次网络抖动就把整个教案重新传一遍,已传完了80%的资料,剩下的20%只需要补一下网络环境就能续上。

3. 后端接口设计:Java Web如何接住分片

3.1 初始化接口:生成uploadId并记录元数据

前端在切片前会先调用初始化接口,这个接口承担两件事:给本次上传生成唯一标志(uploadId),并把文件的基本信息记录下来。我们用Spring Boot实现,结构非常清晰:

@PostMapping("/upload/init") public Result initUpload(@RequestBody UploadInitRequest request) { // request.fileName, request.fileSize, request.chunkTotal String uploadId = UUID.randomUUID().toString().replace("-", ""); UploadTask task = new UploadTask(); task.setUploadId(uploadId); task.setFileName(request.getFileName()); task.setFileSize(request.getFileSize()); task.setChunkTotal(request.getChunkTotal()); task.setReceivedChunks(new HashSet<>()); task.setChunkSize(request.getChunkSize()); redisTemplate.opsForValue().set(getTaskKey(uploadId), JSON.toJSONString(task), 24, TimeUnit.HOURS); return Result.ok(uploadId); }

实现里有两个要点。第一,uploadId不要用自增数字,直接用UUID,避免用户恶意猜测别人的上传任务编号。第二,元数据要放在Redis里而不是内存Map里,因为教育系统的Web应用往往会部署多节点,内存Map意味着用户第二次请求可能打到另一台机器上,Redis天然跨节点,哪台机器都能查到任务状态。

有人会问:初始化接口返回的uploadId前端要不要存?当然要存,而且要用localStorage持久化,而不是只放在页面内存。原因很简单——如果老师不小心关了浏览器标签页,再回来时localStorage里的uploadId还在,前端可以拿这个ID查一下已上传情况,接着传;如果只是放在内存里,页面一关全部信息丢失,前面传的分片全成了孤儿数据。

3.2 分片接收接口:核心取舍与落盘方式

这个接口是整个方案的心脏。这里有个每个Java工程师都要面对的抉择:用MultipartFile接收分片,还是用Part接收。

MultipartFile是Spring封装好的API,用起来最顺手,一个参数就能拿到文件名、大小、流。但它有一个潜在问题:Spring处理MultipartFile时如果配置不当,可能会把整个请求直接读入内存。Part是Servlet原生接口,更底层,文档和排错资料少一点,但对大文件上传场景更透明可控。

我最终选择的是Part。理由很直接:分片只是整个文件的一部分,Part接口可以按流式方式处理,不经过Spring MultipartResolver的完整解析过程,内存压力小。实际代码长这样:

@PostMapping("/upload/chunk") public Result uploadChunk(@RequestParam("uploadId") String uploadId, @RequestParam("chunkIndex") Integer chunkIndex, @RequestPart("file") Part file) throws IOException { UploadTask task = getTaskFromRedis(uploadId); if (task == null) { return Result.error("上传任务不存在或已过期,请重新创建"); } // 分片序号已在元信息中,可以直接作为文件命名依据 String chunkFileName = createChunkFileName(uploadId, chunkIndex); File chunkFile = new File(chunkDir, chunkFileName); try (InputStream in = file.getInputStream()) { Files.copy(in, chunkFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } // 记录已成功接收的分片序号 task.getReceivedChunks().add(chunkIndex); updateTaskInRedis(task); return Result.ok(); }

接收分片时要注意三个坑。

第一个是分片落盘目录的规划。临时分片目录和最终文件目录要分开,最终文件目录是正式的教案存储位置,临时分片目录则定期清理。目录权限也要设置好,避免把分片直接写到应用部署目录下,防止路径穿越等安全风险。

第二个是幂等性问题。同一个分片可能因为前端重试而发送两次,后端必须做到“同一个分片重复提交不产生副作用”。最简单的做法是:保存分片前先检查对应文件是否已存在并且大小正确,如果已存在直接返回成功,不需要重复写入。

第三个是文件名不要用用户原始文件名。分片临时文件一律按uploadId和chunkIndex组合命名,比如“${uploadId}_${chunkIndex}.part”。理由很现实:用户原始文件名可能包含中文、空格、特殊字符,不同操作系统下处理路径的规则不一致,容易在不该出错的地方出错。

3.3 合并接口:把分片拼回完整Word教案

所有分片传完后,前端调用合并接口。合并接口的逻辑不算复杂:按分片序号从0到chunkTotal-1依次读取临时分片文件,逐个写入目标文件。

@PostMapping("/upload/merge") public Result merge(@RequestBody MergeRequest request) { String uploadId = request.getUploadId(); UploadTask task = getTaskFromRedis(uploadId); if (task == null) { return Result.error("上传任务不存在或已过期,请重新创建"); } if (task.getReceivedChunks().size() != task.getChunkTotal()) { return Result.error("分片不完整,已收到" + task.getReceivedChunks().size() + "/" + task.getChunkTotal()); } File targetFile = new File(finalDir, generateFinalFileName(task.getFileName())); try (FileOutputStream fos = new FileOutputStream(targetFile)) { for (int i = 0; i < task.getChunkTotal(); i++) { File part = new File(chunkDir, uploadId + "_" + i + ".part"); try (FileInputStream fis = new FileInputStream(part)) { byte[] buffer = new byte[8192]; int len; while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); } } part.delete(); } } // 删除Redis中的上传任务元数据 redisTemplate.delete(getTaskKey(uploadId)); return Result.ok(targetFile.getAbsolutePath()); }

分片完整性校验是必须在合并接口里做的。不能只依赖前端发誓“我传完了”,那在忙碌的图书馆网络环境里就是个定时炸弹。后端throughRedis里的receivedChunks集合,比对实际接收到的分片数量和总额分片数是否一致,不一致直接拒绝合并,让前端去补传缺失分片。

文件是否损坏还涉及另一层校验——MD5。我们采用的是前端计算文件整体MD5,在初始化接口时提交给后端,后端合并完成后重新计算MD5并做比对。Word教案这类文件需要保证字节级一致,MD5校验能堵住绝大多数偶发的数据损坏问题。

但要注意一个现实的性能问题:上百MB的文件算整体MD5,前端需要几秒到几十秒,后端合并文件后算MD5又要一次全文件读取。第一次上线时,我们为了省事,让后端在合并过程中就边读取边计算Hash,其实是在遍历文件时多次读写磁盘,性能反而不如合并结束后单独计算一次。后来我调整为先快速合并、再校验,校验通过后立刻删除临时分片,体验和性能都有改善。

3.4 Redis键设计与过期策略

Redis里存储的元数据要精细设计,不能把所有信息塞进一个JSON字符串就完事,虽然初始化接口里我为了简洁确实这么写了,实际项目里我会建议拆成两个键:

  • 字符串键:存任务元信息(文件名、大小、分片总数),用uploadId做后缀。
  • 集合键:存已接收的分片序号,用来做幂等判断和断点续传查询。

集合键的存在价值在于:判断某个分片是否已上传时可以O(1)查询,而不用把整个JSON从Redis里捞出来后反序列化。校园网环境下老师重传分片是高频操作,这个性能差异会直接体现到响应速度上。

过期时间设置也需要单独考虑。我把临时分片文件和Redis元数据都设置了24小时过期。为什么是24小时?因为老师可能在第一节课开始上课前就开始上传教案,下课后才重新打开页面继续传——中间隔了好几小时。如果过期时间太短,老师很可能遇到“你传的文件已经找不到了”的尴尬。同时也不能太长,分片文件在磁盘上越积越多,清理成本高。24小时是我配合运维部门观察后的折中值。

4. 关键参数计算、服务器调优与全链路验证

4.1 分片大小怎么定:一个偏经验的计算模型

分片大小是分片直传方案里最容易被忽略的参数,但它直接影响整体体验。有一个粗算方式:把期望单个分片上传耗时控制在3秒以内,分片大小约等于“可用带宽乘以3秒”。

教育行业校园内的上传带宽按2MB/s(约16Mbps)算的话,3秒能传6MB。所以5MB分片对校内网络正合适。如果服务器部署在公网上,上传链路可能只有几百KB/s,就得把分片设小到1MB左右,否则一个分片要传十几秒。比较合理的取值区间是1MB到10MB,但不要低于512KB——分片太小会导致请求数量激增,nginx日志全刷爆,数据库和Redis压力也跟着上来。

文件总大小和分片数量的乘积也需要留意。统一按5MB分片,一个100MB文件会产生20个分片,3个并发传输大概7轮完成,体验可以接受。如果一个文件只有5MB,不要去切分片,直接按普通表单上传更省事——分片本身有额外开销,小文件硬套分片反而更慢。

4.2 前后端配置调整清单与原理

接入分片直传后,最先暴露出来的问题往往不是业务代码,而是Web服务器的默认限制。我整理了一份在教育行业通用部署环境里几乎必调的配置,每项都吃过亏:

配置项默认值调整建议原因
nginx client_max_body_size1m200m单请求body大小限制,分片本身通常不大,但要给合并请求留足空间
nginx send_timeout60s300s传输大分片时超过60秒容易被掐断
Tomcat maxSwallowSize2MB-1 或 200MB默认2MB导致multipart解析时报413
Tomcat connectionTimeout20000ms60000ms避免慢网络下连接超时
Spring multipart.max-file-size1MB200MBSpring Boot默认只支持1MB文件
Spring multipart.max-request-size10MB210MB默认10MB限制整个请求体

这些配置不是越高越好。我把multipart.max-request-size设为210MB,是因为单次请求最大是单个分片(5MB)加上少量表单字段,按理说默认值都不该卡住,但为了兼容某些前端库把多个分片打包提交的情况,预留了空间。对应的,如果你确定前端永远严格单分片上传,这个值还可以更保守。

有个被很多人忽略的Tomcat细节:maxSwallowSize指的是“丢弃请求体时最多吞掉多少字节”。当后端抛出异常、未读取完整个请求体时,Tomcat会尝试把剩余数据读掉以便复用连接。如果maxSwallowSize太小,Tomcat直接返回413。这个值在分片上传场景必须调大,否则异常处理时就会出现看不懂的“connection reset”。

4.3 全链路验证:用真实环境模拟教师上传

功能写完后,我花了小半天做了完整的全链路验证。场景是这样的:模拟教师账号,使用一台普通办公电脑(非开发机),浏览器访问系统,从本地上传一份带有大量截图、总大小约60MB的Word教案。

首先用浏览器开发者工具的Network面板看了分片请求的耗时分布。初始化接口立即返回;分片请求每个大约1到1.5秒完成;3个并发下总耗时在15秒左右;最后调合并接口耗时约2秒——这是后端遍历分片写文件的时间。整个流程稳定无卡顿,进度条平滑走到了100%。

然后做了一个更恶心的测试:在分片传了三分之一时,手动断开网络。等10秒后重新连接,刷新页面。此时前端通过查询接口发现只有8个分片成功上传,自动从第9个分片继续传。整个续传过程对用户完全透明,没有弹出任何异常报错。这个测试结果让我定了心——教育场景下教师网络再不稳定,体验至少是兜底的。

最后是并发测试。模拟20个账号同时上传教案,服务器进程的CPU和内存都还在正常范围。但Redis连接数涨得比较明显,因为每个分片上传都要更新任务进度。后来我把Redis写入从“每收一片就写一次”改成“每收一片在本地内存累计,5秒后集中写一次”,连接压力立刻降了下来。这个优化在分片多、并发高时特别有效。

5. 常见问题排查与避坑实录

分片直传功能上线之后,被一线反馈的问题其实比我预想的多。有些问题一眼就能定位,有些需要反复排查。我整理了几个高频问题的排查实录,帮后来人少走弯路。

5.1 上传到一半后端报413 Request Entity Too Large

这是第一个值得说的问题。出现这个报错,大部分人的第一反应是调大nginx的client_max_body_size。这个方向对,但不完全对——如果你用的是Tomcat,它还有一个独立的maxSwallowSize在管着multipart请求。即使nginx放行了,Tomcat拒绝时依然会报413。

排查顺序我建议是:先看nginx access log确认请求有没有到后端,再看Tomcat catalina日志有没有multipart解析异常,最后检查Spring Boot的multipart配置。这三层配置任何一个没放开,分片上传都会被卡在半路。

我们在第一版部署时就是只改了nginx,Tomcat和Spring Boot的配置没动,结果测试时10MB以上的分片全被413弹回。后来把三个配置一次性调到表里的数值,这个问题才彻底消失。

5.2 前端明明传完所有分片,合并接口却报“分片不完整”

这个问题让我排查了半天。前端console显示每个分片都返回了200,后端Redis里却缺了某些分片序号。查到最后发现是前端并发上传时的回调顺序问题:分片A和分片B同时发出,B先到达,后端先落盘并记录了B的序号,然后网络抖动让A重试了一次,重试成功后A再次落盘,但Redis里的记录在某一时刻被更新覆盖了。

问题根源在Redis更新不是原子的。我用的是先读出任务JSON、添加序号、再写回去这个流程,高并发下两次写操作可能互相覆盖。解决方案是把“已接收分片序号”单独拆到Redis的Set里,用SADD操作原子地加入序号,完全不涉及读取再写入。这样即便同一分片重复提交,set的特性也天然保证幂等。

5.3 合并后的Word教案打不开

合并接口跳了成功,文件也在目标目录生成了,但老师反馈下载后Word提示文件损坏。单独排查这个问题时,我第一个怀疑的是合并顺序是否被破坏。检查代码后确认索引顺序是对的,问题出在写入缓冲上:写入时用了8KB的buffer,但最后一轮读取返回的字节数不足8KB,如果没有用返回的len作为写入长度,而是直接写满buffer,就会在文件末尾塞进一串无效的零字节。

这个bug的教训是:Streaming API操作中,read返回的真实长度必须逐字节严谨处理,不能默认每次都能读满缓冲。Java的Files.copy和IOUtils.copy内部已经处理好这些事,但如果自己手写拷贝循环,就得特别小心。我后来统一改成Files.copy拷贝分片内容到目标文件的输出流,问题再没出现过。

5.4 临时分片文件大量堆积

上线运行两周后,运维发现磁盘空间告急。看临时分片目录,几百MB的part文件堆积成山。原因是:老师上传到一半关掉页面,Redis元数据里没有对应的清理任务,定时清理的Job只清理那些“超过24小时没更新”的任务,但分片文件本身没有和任务关联上,导致孤儿文件无人认领。

解决方案分两步。一是在定时清理Job里,把“分片文件修改时间超过2小时”作为删除条件,而不是依赖Redis里的任务状态——不管任务本身是不是正常结束,只要分片文件长时间没被访问,就直接清掉。二是调整合并接口,无论合并成功还是失败,都尽量把临时分片文件删掉,只有删除失败时才留给定时Job兜底。

5.5 文件上传成功后,教案列表里查不到记录

这是最隐蔽的一个问题。文件本身已经合并好,放在正确的目录里,但教案列表页面查不到。排查到最后,发现是业务表和文件存储之间的关联没做事务处理:文件合并和数据库记录插入各自独立执行,文件合并成功、数据库插入抛出异常时,不会自动回滚文件操作。

这类问题的根因是“文件系统与数据库之间天然缺乏原子性”。我的处理方式是先插入数据库记录但标记为“上传中”,再合并文件,合并成功后把记录状态改为“已完成”。这样即使任何一步失败,至少能看到一个“上传失败需重试”的中间状态,不会出现文件存在却无迹可寻的情况。

问题现象根因解决方案
413 Request Entity Too Largenginx/Tomcat/Spring配置未同步调大三层配置逐层放开
已传完分片仍提示不完整Redis写入非原子操作分片序号改用Set集合SADD
合并后Word文件损坏写缓冲时未按实际读数统一用完整拷贝方法
磁盘空间被part文件塞满孤儿分片无人清理定时Job按文件修改时间清理
文件成功但列表无记录文件系统与数据库无事务引入“上传中”状态标记

6. 从分片直传到教学场景体验优化

除了最核心的上传功能,我在这个项目里还做了一些周边优化,虽小但老师感知明显。第一,在进度条上增加“已上传XX MB / 总大小XX MB”的文字提示,不只用百分比。实际上,很多老师不太理解百分比,但很清楚自己那份教案传了多少兆。第二,上传完成后自动校验Word文件能否被后端读取到真实大小,避免合并成功但文件实际是空的这种异常情况。

还有一个小功能值得分享:上传前的前端轻量检查。Word教案的后缀名可以伪造,但文件头很诚实。前端读文件的前几个字节,判断是否为OLE2或OOXML格式的标识,不是的话直接提示“文件格式不正确”,省去后端校验的额外开销。这个功能实现成本极低,却帮业务部门挡了不少垃圾数据。

分片直传方案在这些周边功能的配合下,基本成了教育行业Java Web项目里大附件上传的“标准答案”。后来我又把这个方案平移到另一个项目,把分片大小从5MB调成2MB,把后端从单体改成了多节点部署,Redis的元数据天然支持跨节点,整个方案几乎没改代码就直接复用了。

最后再分享一个实战中的小建议:分片直传方案上线后,不要急着删掉传统表单上传的入口,至少保留一个“小文件直接传”的开关。因为分片流程虽然稳,但多余的网络交互对小文件来说确实不划算。一个几百KB的小教案,直接POST整个文件,一秒钟搞定,何必也走一遍初始化、分片、合并的流程?我在系统里就是按照文件大小自动判断走哪条路线,体验和性能都做到了最优。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进