1. 需求拆解:JSP项目里的文件夹断点续传到底难在哪
去年年底我还在维护一个基于Servlet + JSP的老系统,运营部突然提了个需求:网页里要能一次上传整个文件夹,而且网络断了之后重新选择文件夹,已经传过的文件不许再传一遍。听到“断点续传”“文件夹”这两个词同时出现在JSP项目里,我第一反应是这事没那么简单。JSP时代的上传方案大多数是单文件、整文件上传,一套成熟的组件往往依赖Spring Boot或者Vue这类新生态,想在老项目里直接套用并不容易。
这个需求的本质,是把“文件上传”拆成三层问题:第一,浏览器里如何拿到一个文件夹并遍历里面的所有文件;第二,文件体积大、数量多,如何把每个文件切成小块,传失败了只重传失败的小块;第三,整个上传中断之后,用户重新进入页面,已经传过的分片怎么跳过,而不是从头再来。这三个问题单独看都不算新技术,但组合在一起,并且要落在JSP/Servlet这套老技术栈上,就需要把原理吃透之后自己拼装。
如果你也在JSP项目里被这个需求卡住,或者正在给老系统做上传模块改造,这篇文章应该能帮上忙。我会把常见方案、前后端核心代码、分片记录表设计,以及我踩过的坑挨个讲清楚。都是可以直接复用的思路,不一定需要引入重型框架。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案选型:自研Servlet、第三方组件、云存储怎么选
先说结论:JSP网页实现文件夹断点续传,市面上大体有三条路可走。第一条是纯自研,前端用JavaScript切分文件,后端用Servlet接收分片,再自己写合并逻辑;第二条是前端集成现成的上传组件,比如WebUploader、Plupload、Resumable.js,后端仍然用Servlet或者单独写一个上传接口;第三条是如果项目有改造空间,直接上Spring Boot + Redis做分片记录,把文件落到MinIO或者云存储上。三条路没有绝对的优劣,关键是看你的项目现状。
老JSP项目强依赖Tomcat这类Servlet容器,部署环境往往也是内网老机器,升级Spring Boot的成本不只是改代码那么轻松。所以我的建议是:如果你的项目短期内不可能换框架,优先走自研或者第三方组件路线;如果项目刚好在重构,或者有网关、配置中心这类基础设施了,直接走Spring Boot + MinIO的方案会省心非常多。
2.1 自研Servlet方案:老项目成本最低的选择
自研方案的核心就是自己实现“分片上传 + 分片状态记录 + 合并文件”。前端拿到文件夹后遍历出所有文件,每个文件按固定大小切片,比如每片2MB,切完的文件逐片上传到Servlet接口。Servlet这边把接收到的分片先存在临时目录,等到所有分片收齐了,再按照分片序号依次读取并合并成完整文件。
这个方案最大的好处是依赖少,一个Servlet类加一张数据库表就能跑起来。缺点也很明显:你需要自己处理并发、乱序、重复分片、合并失败恢复这些边界问题。像“用户上传到一半关了浏览器,过了两小时重新打开,怎么跳过已传分片”这种需求,如果没有一张记录表,就只能靠前端每次上传前主动询问后端哪些分片已经存在。
所以做自研方案时,我强烈建议把分片记录表作为核心,而不是只写一个接收接口。表里记录文件标识、分片序号、分片状态,前端上传每个分片之前先查询一次,已存在的分片直接跳过,这样才能真正实现“断点续传”。
2.2 第三方组件方案:前端可能只需要半天接入
如果你不想手写整套路逻辑,可以试试前端组件。WebUploader是百度出的老牌上传组件,虽然已经停止维护,但用在老项目里依然稳定,很多人骂它文档不全,实际用下来核心功能都在。Plupload也有年头了,支持分片、拖拽、队列管理。Resumable.js更轻量,专注断点续传,后端只需要实现对应的接口即可。
我拿WebUploader举个例子:它自带分片、并发控制、进度条,你只需要在后端写一个接收接口。前端配置里设置chunked: true,组件会自动把大文件切成多片上传,每片上传时会带上chunk参数。后端接收后把这些分片保存到临时目录,前端全部传完后再调用一个合并接口。
这个方案的价值在于,前端的切分、断点跳过、进度展示这些繁琐工作组件都替你做了。缺点是老组件对现代浏览器的支持有天花板,WebUploader在Chrome最新版上偶尔有样式兼容问题,但功能不受影响。如果你对前端交互要求不高,只求快速交付,这个方案比完全自己写要快得多。
2.3 云存储与Spring Boot方案:新项目直接一步到位
如果项目允许改造,我强烈建议看Spring Boot + Redis + MinIO这套组合。MinIO本身是兼容S3协议的对象存储,它支持Multipart Upload分片上传,换句话说,MinIO天然支持将一个文件分成多个分片上传,然后合并。很多人在网上问“MinIO支持断点续传吗”,严格说MinIO提供的是分片上传能力,S3协议里每个上传任务会生成一个uploadId,你只要保存好这个uploadId,中断后就可以拿着它继续传剩余分片,这就是真正的断点续传能力。
Spring Boot实现断点续传秒传有两种常见方案。一种是基于Redis记录每个文件的分片上传状态,每次上传分片前先查Redis,已传过的分片直接返回成功;当所有分片都传完后,服务器端异步合并文件,并把合并后的文件信息写入数据库。另一种是不做分片,直接用RandomAccessFile按照文件偏移量写文件,前端通过js读取文件指定位置的数据,一次性拖到服务器对应位置,这种方式实现断点续传更直观,但对并发处理要求较高,因为多人同时写同一个文件会很危险。
3. 核心细节解析:前端如何遍历文件夹、切分文件、实现续传
聊完方案,我从最常用的自研Servlet方案开始拆解。这个方案分为前端和后端两部分,前端要解决三件事:获取文件夹、遍历目录树、切分文件并上传。
3.1 获取文件夹:webkitdirectory的基本用法
浏览器端想获取文件夹,靠的是input标签的webkitdirectory属性。这个属性最初是Chrome私有API,后来Firefox和Edge也支持了,但Safari的支持情况不稳定。老的IE肯定不支持,所以项目如果必须兼容IE,文件夹上传功能基本做不了,只能退化成手动多选文件。
html复制<input type="file" id="folderPicker" webkitdirectory multiple />
加了webkitdirectory之后,你在文件选择框里就能选整个文件夹,选中后input.files返回的是一个扁平的文件列表,而不是带目录树的结构。每个File对象上会带一个webkitRelativePath属性,比如docs/2024/协议.docx,这个属性就是文件在文件夹里的相对路径,前端依赖它还原目录结构。
这里有个容易踩的坑:有的开发者误以为File对象会有文件本地的绝对路径,但浏览器出于安全限制,绝对不会把真实路径暴露给网页。你拿到的永远是相对路径。网上搜“jsp代码谷歌浏览器获取保存文件路径”这类问题,基本都是这条路走不通的。
3.2 递归遍历目录树:保留文件夹层级
如果文件夹里还有多级子目录,input.files已经把每层路径都拼在webkitRelativePath里了,所以前端不需要额外递归。但如果你是通过拖拽上传拿到目录结构,就需要用DataTransferItem的webkitGetAsEntry()方法手动递归。这里我直接给一个兼顾两种情况的逻辑。
javascript复制function getAllFiles(input) {
const files = [];
const fileList = input.files;
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
const relPath = file.webkitRelativePath;
files.push({
file: file,
relPath: relPath,
fileName: relPath.substring(relPath.lastIndexOf('/') + 1)
});
}
return files;
}
这一步其实很简单,难点在于后端要知道每个文件应该落到哪个目录。所以前端上传时,除了传文件本身,还要把relPath一起传给后端。后端按relPath里的目录层级创建文件夹,还原原始结构。
3.3 切片:把大文件拆成小块再上传
文件夹里单个文件动辄几百MB,如果不切片,网络一抖动整个文件就得重传。切片操作的核心是File.prototype.slice(),这个API所有现代浏览器都支持。
javascript复制const CHUNK_SIZE = 2 * 1024 * 1024; // 每片2MB
function splitFile(file) {
const chunks = [];
let start = 0;
let index = 0;
while (start < file.size) {
const end = Math.min(start + CHUNK_SIZE, file.size);
chunks.push({
index: index,
blob: file.slice(start, end)
});
start = end;
index++;
}
return chunks;
}
切片大小怎么定?我建议2MB到5MB之间。片太大,单片传输时间长,重传成本高;片太小,网络请求数量剧增,服务端IO压力大。内网环境可以调大到10MB,公网环境建议用2MB。这个参数直接影响上传体验,实测下来即使公网环境2MB分片加并发上传,速度完全能接受。
3.4 文件指纹:实现秒传和续传的核心
所谓“断点续传”在Web场景里说直白点就是:前端重新选择文件夹后,服务端能判断某个文件是否已经传过一部分,已传的分片直接跳过。怎么判断?靠文件标识。最可靠的是给文件内容计算一个Hash值,比如MD5或SHA-1。
大型文件整体计算Hash很慢,一个2GB的文件用老办法算MD5可能要几十秒,所以一般用spark-md5的增量计算方式。spark-md5是社区常用的库,支持分片增量计算Hash。这里有一个优化技巧:不用等所有分片都算完才开始上传,可以先把前面几个分片算出来的Hash作为临时标识,或者直接采用“文件大小 + 相对路径 + 最后修改时间”作为轻量标识。生产环境我见过很多项目就这么做,虽然理论上极端情况下可能冲突,但在内网系统里完全够用。
真正需要计算文件级Hash的是“秒传”需求:服务端已经存在相同内容的文件,客户端直接返回“上传完成”,省去整个上传过程。要判断文件内容相同,就需要完整的文件指纹。这里我建议计算Hash的过程放在后台异步执行,前端显示一个“正在分析文件”的loading状态,分析完成后才开始上传队列。避免浏览器假死。
3.5 前端上传队列与续传判断
前端将文件列表和分片队列整理好之后,真正的上传逻辑需要做两件事:查状态和传分片。查状态是请求后端接口,把文件Hash传过去,后端返回已存在的分片序号列表,前端过滤掉这些分片后再开始传。传分片时用FormData携带文件块和相关参数。
javascript复制async function uploadFile(fileInfo, uploadedChunks) {
const chunks = splitFile(fileInfo.file);
const total = chunks.length;
let offset = 0;
while (offset < total) {
if (uploadedChunks.includes(offset)) {
offset++;
continue;
}
const formData = new FormData();
formData.append('fileHash', fileInfo.fileHash);
formData.append('relPath', fileInfo.relPath);
formData.append('chunkIndex', offset);
formData.append('totalChunks', total);
formData.append('file', chunks[offset].blob, fileInfo.fileName);
await axios.post('/upload/chunk', formData);
offset++;
}
await axios.post('/upload/merge', {
fileHash: fileInfo.fileHash,
relPath: fileInfo.relPath,
totalChunks: total,
fileName: fileInfo.fileName
});
}
这里用await串行上传是最稳妥的方式,但速度太慢。真实系统里建议做一个并发控制,比如同时最多3个分片并发上传。并发数量不宜过大,否则服务端同时接收的分片连接太多,内存和临时文件句柄会压力很大。我在实际项目里把并发控制在3到5,内网环境速度已经非常理想。
4. 核心细节解析:JSP后端Servlet怎么接分片、记状态、合并文件
前端做完分片,后端的工作就更关键了。老项目里这一步通常用Servlet实现,下面是我整理出来的接口设计和实现逻辑。
4.1 分片接收接口:把分片写到临时目录
后端接收分片的接口,用Servlet的Part或者InputStream读取即可。如果项目里用的是Servlet 3.0以上,可以直接用request.getPart("file")拿文件。保存分片的目录建议和最终存储目录分开,先放到一个临时目录,比如/data/upload_tmp/{fileHash}/{chunkIndex}.part。等所有分片收齐后再合并,避免合并过程中一个分片损坏导致整个文件不可用。
java复制@WebServlet("/upload/chunk")
@MultipartConfig
public class UploadChunkServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileHash = request.getParameter("fileHash");
String relPath = request.getParameter("relPath");
int chunkIndex = Integer.parseInt(request.getParameter("chunkIndex"));
Part part = request.getPart("file");
String tmpDir = "/data/upload_tmp/" + fileHash;
File dir = new File(tmpDir);
if (!dir.exists()) {
dir.mkdirs();
}
File chunkFile = new File(tmpDir, chunkIndex + ".part");
try (InputStream in = part.getInputStream();
FileOutputStream fos = new FileOutputStream(chunkFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
// 记录分片状态到数据库
UploadRecordService.markChunkUploaded(fileHash, chunkIndex);
response.getWriter().write("{\"code\":0}");
}
}
这段代码的核心是把分片落盘,并且更新数据库里的分片记录。如果同一片分片被重复上传,直接覆盖即可,因为内容一样,不会产生问题。但要注意防止恶意用户上传超大分片或者伪造参数,内部系统可以放宽,对公网系统还是要在Servlet里做大小和参数合法性的校验。
4.2 分片状态记录:一张表搞定断点续传
分片记录表是整个断点续传的地基,表设计得合理,后续功能都好做。我常用的一张表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| file_hash | varchar(64) | 文件内容Hash,唯一标识文件 |
| file_name | varchar(255) | 文件名 |
| rel_path | varchar(1000) | 文件在文件夹内的相对路径 |
| total_chunks | int | 总分片数 |
| uploaded_chunks | text | 已上传分片序号集合,如"0,1,2,5" |
| file_size | bigint | 文件大小 |
| status | tinyint | 0上传中 1已完成 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
这里有个细节:uploaded_chunks字段直接存已上传的分片序号集合,用逗号拼接。为什么不用一张分片表单独记录每一片的明细?因为大多数情况下分片数量是几十到几百,用文本字段足够,查询更新更快,避免IO频繁。如果文件量大到分片数上千,可以考虑换成分片明细表,但我在实际项目中用文本字段就已经很稳定了。
查询接口判断一个文件的分片情况,其实就两条SQL:根据file_hash查记录,如果status=1直接返回“已完成”;如果没有记录,返回uploaded_chunks的集合。前端拿到这个集合,就知道哪些分片不用传了。
4.3 合并文件:按顺序读取分片写入完整文件
所有分片上传完毕后,前端会调用合并接口。后端合并的逻辑也简单:读取临时目录下所有.part分片,按序号从小到大依次写入目标文件。
java复制@WebServlet("/upload/merge")
public class UploadMergeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileHash = request.getParameter("fileHash");
String relPath = request.getParameter("relPath");
String fileName = request.getParameter("fileName");
int totalChunks = Integer.parseInt(request.getParameter("totalChunks"));
String targetFile = "/data/upload/" + relPath;
File target = new File(targetFile);
if (!target.getParentFile().exists()) {
target.getParentFile().mkdirs();
}
String tmpDir = "/data/upload_tmp/" + fileHash;
try (FileOutputStream fos = new FileOutputStream(target)) {
for (int i = 0; i < totalChunks; i++) {
File chunkFile = new File(tmpDir, i + ".part");
if (!chunkFile.exists()) {
// 分片缺失,合并失败
response.getWriter().write("{\"code\":1,\"msg\":\"chunk missing\"}");
return;
}
Files.copy(chunkFile.toPath(), fos);
}
}
// 删除临时分片目录
deleteDir(new File(tmpDir));
// 更新状态为已完成
UploadRecordService.markFileFinished(fileHash);
response.getWriter().write("{\"code\":0}");
}
}
合并接口有几个关键点。第一,合并前一定要做完整性校验,检查每个分片序号是否存在,缺失一个就返回错误,让前端重传缺失分片。第二,合并时建议同步计算文件Hash,合并完成后再和服务端记录的file_hash比对,防止某一片在上传过程中损坏。第三,合并完成后把临时分片目录整个删掉,腾出磁盘空间,这个步骤漏了的话,上传几次大文件磁盘就满了。
4.4 文件夹维度的整体状态
如果你只是做单文件断点续传,上面这些已经够了。但标题里的关键词是“文件夹”,所以还需要考虑文件夹维度。一个文件夹可能包含几十个文件,每个文件都有自己的file_hash和状态。用户上传到一半退出,重新选择同一个文件夹,前端需要遍历所有文件并逐个调用查询接口,每个文件返回“已完成”或者“未传完”。
为了让体验更好,可以增加一个“文件夹ID”的概念。比如前端在首次上传文件夹时调接口申请一个uploadTaskId,后端记录该任务涉及的文件夹相对路径,以及文件夹下所有文件的file_hash集合。查询时只需要带上uploadTaskId,后端一次性返回这个文件夹的进度概览,减少大量请求。内网几十个文件差别不大,但如果文件上千,这个优化非常明显。
5. 完整实操过程:一个文件夹从选择到上传成功
下面把整个流程串起来过一遍,方便你照着抄。我以“审批附件文件夹”为例,里面可能有多个子目录和几十个文件。
5.1 操作步骤
- 用户在JSP页面点击“选择文件夹”,input触发change事件。
- 前端获取所有文件,计算每个文件的Hash(异步执行)。
- 前端调用
/upload/check接口,传入文件列表信息,后端返回每个文件是否已完成,以及未完成文件的分片状态。 - 前端过滤掉已完成文件,把未完成的文件加入上传队列,开始并发上传分片。
- 每个分片上传成功后,前端更新进度条,进度条可以精确到片数。
- 单个文件所有分片传完后,前端调用
/upload/merge接口,后端合并文件。 - 文件夹下所有文件都完成合并后,前端调用
/upload/complete接口,后端更新整个文件夹任务状态。 - 如果中途断网或用户关闭页面,用户重新进入页面后再次选择文件夹,check接口会返回已传分片,直接续传。
这套流程听着多,其实核心就是check、chunk、merge、complete四个接口。前端的难点是组织好文件和分片队列,后端的难点是把状态记录做对。
5.2 接口设计速查表
| 接口 | 方法 | 参数 | 返回 |
|---|---|---|---|
| /upload/check | POST | fileHash列表 | 文件状态、已传分片集合 |
| /upload/chunk | POST | fileHash、relPath、chunkIndex、file | 接收结果 |
| /upload/merge | POST | fileHash、relPath、totalChunks、fileName | 合并结果 |
| /upload/complete | POST | taskId | 文件夹任务状态 |
实际开发时,这四个接口就已经足够支撑一个完整的文件夹断点续传功能。我甚至见过有人把check合并到chunk接口里,每次上传分片前自动校验,但这样会增加无效请求,不建议在大规模场景下这么干。
5.3 JSP页面的上传进度展示
JSP页面里进度条可以用最简单的div加宽度百分比实现,不引第三方库。前端在并发上传时,每成功一个分片,就把对应文件的进度更新到页面。文件夹整体进度等于“已完成分片数 / 总分片数”。
html复制<div class="progress-wrap" data-file="2024协议.docx">
<div class="progress-bar" style="width:0%"></div>
<span class="progress-text">0%</span>
</div>
进度条有个细节:不要用一条总进度算所有文件,因为单个大文件会拖垮整体进度。文件夹上传最好每个文件一条进度条,底部再显示一个整体百分比。这样用户能直观看到哪些文件还在传,哪些已经传完。
5.4 验证续传效果
做完之后怎么验证是真的续传而不是重新上传?我推荐一个简单的测试方法:上传一个大文件到一半,手动停掉Tomcat,然后重新启动,再打开页面选择同一个文件夹。正常情况应该看到前端直接跳过已传分片,进度从之前的位置继续。如果发现有分片被重复上传,说明check接口没有正确返回已传分片,排查点一般在前端把fileHash计算错了或者后端记录表的uploaded_chunks没有及时更新。
6. 常见问题与排查技巧实录
功能做完了,实际运行中会有各种幺蛾子。我把自己遇到过的和网友常问的问题整理了一下,做了个速查表。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 浏览器无法获取文件真实路径 | 浏览器安全策略禁止网页访问本地文件路径 | 使用webkitRelativePath作为文件标识,而不是绝对路径 |
| 文件名中文乱码 | JSP默认编码非UTF-8 | 在Servlet中设置request.setCharacterEncoding("UTF-8"),前端FormData不要手动编码 |
| 合并后的文件打不开,损坏 | 分片合并顺序错乱,或某一片上传不完整 | 按chunkIndex升序合并;合并前先比对总片数;上传时对每个分片记录并校验大小 |
| 断点续传不生效,重新上传 | fileHash计算不一致或check逻辑缺失 | 确保前后端Hash算法一致;上传前必须调用check接口查询状态 |
| 上传大文件内存溢出 | 分片一次性读取到内存 | 使用Part.getInputStream()流式读取,禁止把整个Part转成byte[] |
| 文件夹层级丢失,文件全平铺在根目录 | 前端没有传relPath或后端没有创建目录 | 后端根据relPath逐级创建目录,不能直接拼接字符串 |
| 老浏览器不支持webkitdirectory | 浏览器不识别文件夹选择属性 | 降级为多选文件,或者提示用户使用Chrome/Edge |
| 多个文件同名但路径不同,互相覆盖 | 只按文件名做Hash标识 | 文件唯一标识改用fileHash + relPath的组合 |
6.1 最容易出问题的坑:fileHash的稳定性
很多人做断点续传时,前端计算的fileHash和后端自己生成的ID对不上,导致续传永远不生效。这个坑的根源往往是前端用了spark-md5的增量算法,但因为文件读取方式不对,算出来的Hash和直接对整个文件算MD5不一样。我的建议是,用一个固定的工具函数来算Hash,保证同一文件多次计算得到同样结果,并且前端和服务端对Hash的算法保持一致。如果服务端合并后不算Hash,那就以前端传上来的Hash为准。
6.2 并发上传导致服务端文件句柄过多
并发上传3-5个分片时一般没问题,但如果你把并发数调到10以上,服务端同时打开的文件流会很多,Tomcat默认连接数有限,可能出现连接超时。我试过把并发调到8,Tomcat老版本直接报Too many open files。解决方法是限制前端并发,并且在后端用NIO或者线程池来控制处理连接。我最终把并发控制在3,稳定性和速度都满意。
6.3 临时目录空间规划
分片先落临时目录,再合并到正式目录,意味着磁盘占用会翻倍。一个10GB的文件夹上传,临时目录和正式目录各占10GB,磁盘至少要预留20GB。这个空间规划经常被忽略,系统上线后跑几次大文件上传,磁盘不够了才追悔莫及。建议部署时单独给上传临时目录挂一块磁盘,或者定期写个清理任务把超过24小时没合并的分片文件删除。
6.4 文件夹选择的交互体验优化
原生的webkitdirectory选择文件夹后,用户重新选择同一个文件夹,input的change事件不会触发。这是因为file input选中相同路径时,value没有变化。解决方式是每次选择后手动把input.value清空,或者给input添加onclick时重置value。这个细节很小,但如果不处理,用户中断续传后想重新选择文件夹会发现页面“没反应”。
6.5 大文件夹的性能问题
文件夹里文件数量特别多时,前端一次性把所有文件全部读入内存会卡页面。优化的办法是做队列限流,不要让所有分片一次性进上传队列,而是维护一个“待上传分片队列”,队首最多放指定数量的分片,传完一片再从队列里拉一片。后端接口也要考虑幂等性,同一个分片重复上传时直接返回成功,不要每次都报错。
7. 最后分享一点实操心得
这套自研方案我前前后后改了三个版本才稳定下来。第一版只做了分片上传,没有状态记录,中断后重新传居然还要从头来,那时客户吐槽说“这跟没做断点续传有什么区别”。第二版加了数据库记录,但hash算法写得不严谨,经常出现一个文件两种hash,导致秒传时灵时不灵。第三版才把所有逻辑理顺:前端统一用spark-md5的增量算法,后端合并后做校验,所有分片记录落到一张表里,临时目录定期清理。上线后跑了几十次10GB级文件夹上传,基本没再出线。
如果你现在接手的是老旧JSP项目,我的建议是不要想着一步到位做完美,先按分片上传的逻辑跑通一个小文件验证,再逐步加上状态记录、文件夹维度、并发控制。千万别急着把所有文件一次性塞进内存分片,否则测试环境没问题,生产环境一压就崩。
另外,如果团队里前端能力比较弱,付费购买或者集成成熟的WebUploader组件比自研省事得多。自研适合想沉淀一套基础能力、或者项目安全要求高的场景,如果只是业务快速交付,用组件更划算。希望这篇内容能帮你少踩几个坑,把文件夹断点续传稳稳落地。
