gin 默认不支持上传进度追踪,因 parsemultipartform 一次性加载 multipart body 且不暴露流式接口;可行方案是客户端分片上传并维护已传分片列表,服务端提供状态查询接口。

Gin 本身不提供上传进度的自动追踪能力,所有「进度」都得靠客户端主动上报 + 服务端状态记录来拼凑。直接调用 c.FormFile 或 c.SaveUploadedFile 是完全黑盒的,你根本拿不到已读字节数。
为什么 Gin 默认没有上传进度?
Gin 的文件解析基于 http.Request.ParseMultipartForm,它会一次性把整个 multipart body 加载进内存或临时磁盘(取决于 MaxMultipartMemory 设置),过程中不暴露流式读取接口。这意味着:服务端在 c.FormFile 返回前,既不知道当前读到哪,也无法中断或监听进度。
- 哪怕你用
io.Copy手动读取multipart.File,也只适用于单分片场景,且无法跨请求感知整体进度 -
c.Request.Body在调用ParseMultipartForm后已被消耗,二次读取会返回空 - HTTP/1.1 没有标准协议字段能携带上传进度,浏览器 FormData API 也不暴露底层流
真正可行的进度方案:客户端驱动 + 分片状态查
断点续传下的「进度」本质是「已成功上传的分片数 / 总分片数」,而不是字节级实时百分比。这个值必须由客户端自己计算并维护,服务端只负责提供状态查询接口。
- 客户端上传前先发 GET 请求到
/upload/status?fileMd5=xxx,服务端返回类似{"uploadedChunks":[1,2,4],"totalChunks":10} - 客户端据此算出当前进度:
Math.round((uploadedChunks.length / totalChunks) * 100) - 每次分片上传成功后,客户端本地更新已上传列表,再发起下一分片请求
- 服务端无需额外存储「进度」,只需持久化每个
fileMd5对应的已接收分片索引(例如存 Redis 或本地 JSON 文件)
别踩的坑:试图 hook Body 读取做实时进度
网上有些方案建议用自定义 http.Request.Body 包装器来统计读取字节数,这在 Gin 场景下基本无效:
- 一旦调用
c.Request.ParseMultipartForm,原始Body就被 Gin 内部 consume 掉,你的包装器收不到数据 - 即使绕过
ParseMultipartForm、手动解析 boundary,也要处理 multipart 头部、编码、边界识别——复杂度远超收益 - 并发上传时,多个请求共享同一份文件 MD5,但进度需按分片维度隔离,单纯累加字节容易错乱
实际部署时最易忽略的点
进度数字本身不难算,但「一致性」才是关键:客户端认为已上传的分片,服务端必须 100% 确认落盘且可合并。常见断裂点包括:
- 分片写入临时目录后,服务端崩溃导致该分片丢失,但客户端已计入进度 → 必须用原子写(
os.Rename)或 fsync - 合并逻辑未校验所有分片文件大小是否匹配预期(比如最后一片可能不足 5MB)→ 合并前要检查
len(files) == totalChunks且每片 size 正确 - 没对
fileMd5做 URL 安全编码 → 特殊字符导致查询接口 400,客户端误判为「无历史记录」从头传











