beego 默认不支持大文件分片上传,因其自动调用 parsemultipartform 读空 request.body;必须在 controller 开头调用 parsemultipartform(0) 跳过解析,再流式读取原始 body。

Beego 默认不支持大文件分片上传——它会在解析请求时自动调用 ParseMultipartForm,把整个 request.Body 读空,导致后续分片数据拿不到。必须绕过默认行为,手动接管 raw body 流式读取。
为什么 Beego 的 GetFile 在分片场景下会失败
Beego 的 GetFile 方法底层依赖 r.MultipartReader(),而该方法会隐式触发 r.ParseMultipartForm(即使没显式调用)。一旦触发,r.Body 就被一次性读尽,后续再想按 chunk 大小读取原始字节流,只能得到 EOF。
- 这不是 bug,是 multipart 表单设计的必然行为:它假设你一次要收完整个表单(含文件+字段)
- 分片上传的典型请求是
Content-Type: application/octet-stream,根本不是 multipart,但 Beego 仍可能误判并提前解析 - 哪怕你只调了一次
f.GetFile("xxx"),整个 body 就已不可再用
如何禁用 Beego 自动 multipart 解析
必须在 Controller 方法开头、任何 Beego 文件/表单方法调用前,强制跳过 multipart 解析:
func (f *UploadController) UploadChunk() {
// ⚠️ 关键:第一行就禁用解析,传 0 表示不限制内存但跳过解析
f.Ctx.Request.ParseMultipartForm(0)
// 此后不能再调 f.GetFile、f.GetString、f.GetStrings 等
// 元信息统一从 URL query 或 header 获取:
uploadId := f.Ctx.Request.URL.Query().Get("upload_id")
chunkIndex := f.Ctx.Request.URL.Query().Get("chunk_index")
totalChunks := f.Ctx.Request.URL.Query().Get("total_chunks")
// 限流防攻击:单片最大 5MB
f.Ctx.Request.Body = http.MaxBytesReader(f.Ctx.ResponseWriter, f.Ctx.Request.Body, 5*1024*1024)
// 按预期大小精确读取(前端应传 expected_size)
expectedSize, _ := strconv.ParseInt(f.Ctx.Request.Header.Get("X-Expected-Size"), 10, 64)
reader := io.LimitReader(f.Ctx.Request.Body, expectedSize)
// 写入临时分片文件(注意 os.O_EXCL 防并发覆盖)
tmpPath := fmt.Sprintf("./uploads/%s_%s.bin", uploadId, chunkIndex)
f, _ := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
io.Copy(f, reader)
f.Close()
}
-
ParseMultipartForm(0)是唯一可靠方式;设成其他正数(如32 )仍会触发解析 - Beego 不提供类似 Gin 的
c.ShouldBind原生跳过机制,必须靠手动干预Request - 别信配置项
web.MaxMemory或MaxUploadSize——它们只对 multipart 场景生效,对分片无用
分片状态与合并逻辑怎么落地
Beego 本身不管理上传上下文,状态存储完全由你决定。关键不是选 Redis 还是本地磁盘,而是「查」和「判」不能出错:
- 每个
upload_id对应一个 map 或 DB 记录,存已接收的chunk_index列表(如[0,1,3,4]) - 合并前必须校验:是否所有索引都存在、是否重复上传同一片、是否
total_chunks匹配 - 合并建议用
os.OpenFile(..., os.O_APPEND)+io.Copy,避免全量加载到内存 - 合并完成后立即删临时分片,否则磁盘会撑爆(尤其高并发场景)
最易被忽略的是:Beego 的 Prepare() 方法里也可能隐式调用表单解析(比如某些中间件),所以禁用操作必须写在 handler 第一行,且不能依赖任何封装层。分片上传不是“改个配置就能跑”,而是主动放弃框架便利性,换回对 HTTP 原始流的控制权。











