revel 框架不支持大文件切片上传与断点续传,需手动实现分片接收、状态记录、幂等写入及合并;默认 params.files 依赖 parsemultipartform,会导致内存暴涨、超时或 413 错误。

Revel 框架本身不内置大文件切片上传或断点续传逻辑,必须手动实现分片接收、状态记录、幂等写入与合并流程。直接用 revel.Params.Files 接收整个文件会触发内存暴涨、超时中断、Nginx 413 错误——这不是配置问题,是架构错误。
为什么 Revel 的默认文件接收不能用于大文件
Revel 的 Controller.Params.Files 底层依赖 Go 标准库的 ParseMultipartForm,它会把整个 multipart body 读进内存或临时磁盘(取决于 MaxMemory 设置),对 GB 级文件来说:
- 若
MaxMemory设得高(比如 100MB),单请求吃掉大量 heap,GC 压力陡增,高并发下极易 OOM - 若设得低(默认 32MB),超出部分落磁盘,但 Revel 不暴露底层
multipart.Reader,你无法流式读取并分段处理 - 没有上传进度标识,无法判断“哪一片传过了”,断点续传无从谈起
- 所有分片请求共用同一个
File字段名(如chunk),后端无法区分 index、total、md5 等元数据
必须自己接管 HTTP body 流,绕过 Revel 的文件解析
核心做法:禁用 Revel 的自动 multipart 解析,在 AppInit 中设置 revel.InterceptFunc 或直接在 action 里用 c.Request.Body 手动读流。关键点:
- 前端必须通过 query 参数传递元数据:
?file_id=abc123&index=5&total=200&md5=def456,不要塞进 form data - 后端用
io.LimitReader(c.Request.Body, chunkSize)限制单次读取长度,避免读爆 - 分片存储路径建议为:
/tmp/uploads/{file_id}/{index},用os.O_CREATE | os.O_WRONLY | os.O_EXCL确保幂等(防止重复写同一片) - 写完立即
os.Chmod(path, 0600),避免未合并文件被意外访问
check 接口怎么设计才能支持真正断点
前端每次上传前必须先 GET /api/upload/check?file_id=abc123,后端返回已成功接收的分片 index 列表。这个接口不能只查数据库——要校验文件是否存在且大小正确:
- 查
/tmp/uploads/abc123/目录下所有文件,过滤出0–199范围内的数字命名文件 - 对每个文件调用
os.Stat,确认 size ≥ 预期分片大小(最后一片可略小),否则视为损坏,不计入已传列表 - 返回 JSON:
{"uploaded":[0,1,2,5,6,...],"total":200},前端据此跳过已传分片 - 别用 Redis 存 uploaded list——Revel 多实例部署时,Redis 是共享的,但文件系统不是;必须以磁盘文件为准,否则合并时会缺片
merge 合并时最容易丢片或卡死的三个地方
当 uploaded.length === total,前端调用 POST /api/upload/merge?file_id=abc123,后端开始合并。这里最常出问题:
- 没按 index 数值排序就遍历文件:Linux 下
filepath.Glob("*.bin")返回顺序不确定,必须sort.Ints(indices)后再读 - 用
os.OpenFile(..., os.O_APPEND)追加写:某一分片写到一半进程挂了,残留的半截内容会被追加到最终文件末尾,导致损坏;应改用os.Create创建新文件,逐片io.Copy - 合并完没删临时目录:
os.RemoveAll("/tmp/uploads/abc123")必须放在 defer 之后、校验 MD5 成功之前;否则校验失败时目录已删,无法重试 debug
MD5 校验必须在合并后立刻做——不是校验每个分片,而是校验最终文件。如果和前端传来的 file_id(即全文件 MD5)不一致,说明某一片写入时被截断或磁盘满,整次合并作废,临时目录保留供排查。











