文件损坏的根本原因是前后端分块逻辑与合并方式不匹配。需前端传filename、fileId、chunkIndex等元信息,后端按fileId隔离存储、校验MD5、幂等写入,并用NIO流式合并+最终MD5校验确保完整性。

前端用 File.slice() 切块时,为什么上传后文件损坏?
根本原因不是切片本身出错,而是分块逻辑和后端合并方式不匹配。浏览器的 File.slice() 返回的是 Blob,它默认按字节切,但若前端没传原始文件名、总大小、分片序号、唯一标识(如 fileId 或 md5),后端就无法确认顺序和完整性。
- 必须在每个分片请求中带上
filename、fileId、chunkIndex、totalChunks、chunkSize(可选)和md5(推荐) - 不要依赖
File.name做服务端存储路径——中文名、特殊字符、路径遍历风险都得过滤或哈希化 - 切片大小建议 2–5 MB:太小增加 HTTP 开销;太大不利于断点控制和内存管理(尤其移动端)
- 注意
File.slice()的第三个参数是contentType,不填则继承原始类型;若后端靠 MIME 类型校验,这里漏设会导致Content-Type: application/octet-stream被拒
Spring Boot 接收分片后,怎么安全存临时块并防重复写入?
不能直接把分片存成 part_0、part_1 这种裸名文件——并发上传、重试、多用户同名文件会冲突。核心是「以业务维度隔离 + 内容指纹校验」。
- 临时目录结构建议:
/upload/chunks/{fileId}/{chunkIndex},其中fileId是前端生成的 UUID 或文件内容 MD5 前 16 位 - 接收分片前先查该
fileId+chunkIndex是否已存在,存在则跳过写入(幂等);可用 Redis 记录已上传索引,比查磁盘快 - 务必校验单个分片的
Content-MD5请求头(前端计算并传入),和服务端读取后计算的 MD5 对比,不一致直接 400 - 避免用
MultipartFile.transferTo()直接落盘——它不保证原子性,异常中断可能留下半截文件;改用Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING)
合并分片时 RandomAccessFile 和 Files.write() 哪个更稳?
别用 RandomAccessFile 拼接大文件——它在高并发或 JVM 崩溃时容易留锁、文件句柄泄漏,且 Windows 下对大文件随机写性能差。Java 11+ 推荐纯 NIO 方式流式合并。
- 按
chunkIndex升序读取所有分片文件,用Files.newInputStream()打开,再用Channels.newChannel()获取ReadableByteChannel - 目标文件用
Files.newOutputStream(path, StandardOpenOption.CREATE_NEW)创建,确保不会覆盖已有成品文件 - 用
Channels.newChannel()包装目标OutputStream,然后循环调用channel.transferFrom(srcChannel, position, count)——内核态零拷贝,比BufferedInputStream快 3–5 倍 - 合并完立刻计算最终文件的 MD5,并和前端传来的总 MD5 对比;不一致就删掉成品文件,返回错误,不给前端“看似成功”的假象
断点续传失败,90% 是因为没管好 ETag 和 Range 头
浏览器原生支持 Range 请求,但前提是后端正确响应 Accept-Ranges: bytes 和返回 206 Partial Content。很多同学只做了上传断点,忘了下载断点——而客户端 SDK(如 axios、fetch)续传依赖这个。
立即学习“Java免费学习笔记(深入)”;
- 上传断点本质是「记录已传 chunkIndex 集合」,前端发起上传前先发个
HEAD /api/upload/status?fileId=xxx,后端返回已传索引数组 - 下载断点需要后端对成品文件支持
Range:Spring Boot 可用ResourceRegion+HttpHeaders手动构造 206 响应,别用ResponseEntity<resource></resource>默认行为(它不带Accept-Ranges) - 注意 Nginx 等反向代理默认禁用
Range,需显式配置underscores_in_headers on;和add_header Accept-Ranges bytes; - 不要在合并完成前就允许下载——哪怕只差一个分片,也要等
merge.status = DONE才开放Range接口,否则客户端拿到不完整数据会静默校验失败
真正难的不是切和合,是让每一步都有可验证的状态、可回退的操作边界、以及前后端对「完成」定义完全一致。比如前端认为上传完成 = 所有分片 200,而后端认为完成 = 合并成功 + MD5 校验通过 + 清理临时块 —— 这中间的 gap,就是断点失效、文件损坏、重复合并的根源。










