c.formfile()导致“没进度”是因为其内部一次性读取整个request.body,不暴露读取过程;需手动用progressreader包装body并统计进度,前端配合content-length和轮询获取进度。

为什么 c.FormFile() 一调用就“没进度”
因为 c.FormFile() 是 Gin 的快捷封装,它内部会一次性把整个 Request.Body 读完再解析 multipart,过程中不暴露任何字节读取痕迹。你看到的“上传完成才进 handler”,本质是请求体早已被框架悄悄吞掉——进度条自然永远卡在 0%。
必须手动接管 Request.Body 并注入进度统计
核心不是改 Gin,而是绕过它的自动解析逻辑,用带状态的包装器替换原始 Body:
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 提前从
c.Request.Header.Get("Content-Length")拿总大小(注意:前端需发该 header,否则 fallback 为 -1) - 写一个
ProgressReader,嵌入原c.Request.Body,在Read(p []byte)里累加已读字节数,并更新sync.Map中以uploadID为 key 的状态 - 用这个
ProgressReader初始化multipart.NewReader(),再逐个调NextPart()解析文件和字段 - 绝对不要调
c.ParseMultipartForm()或c.MultipartForm(),它们会跳过你的包装器
前端怎么配合轮询查进度
后端进度状态存好了,前端就得主动问。典型做法是:
- 上传前先 POST /upload/init 获取唯一
uploadID(可带在表单 hidden 字段或 URL query) - 发起真正的 multipart 上传时,确保 header 包含
Content-Length(fetch默认不发,得用XMLHttpRequest或手动计算 blob size) - 另起一个定时器,GET /upload/progress?uploadID=xxx,返回 JSON:
{"uploaded":123456,"total":9876543} - 别用
setInterval盲等,建议指数退避(如 100ms→300ms→1s),避免上传结束前刷爆接口
容易被忽略的三个硬伤
进度条能动不等于稳定可用,这三个点不处理,上线后必出问题:
- Nginx 层必须配
client_max_body_size和proxy_read_timeout,否则大文件上传会在反向代理层静默中断,前端进度停在 90%,后端连 handler 都没进 -
ProgressReader.Read()被并发调用时,累加操作必须加锁或用atomic.AddInt64,否则进度值会跳变甚至回退 - 上传中途断连,
sync.Map里的状态不会自动清理,得配 TTL 或由前端上传成功后显式 DELETE /upload/clean?uploadID=xxx
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










