必须先调用 ctx.input.parseform() 才能获取上传文件,否则 ctx.input.file() 返回 nil;多文件用 ctx.input.getfiles() 获取列表,需校验大小、文件名和 mime 类型,put 请求需手动启用 multipart 支持。

Beego 中 ctx.Input.ParseForm() 必须在读取文件前调用
Beego 的文件上传依赖表单解析,但很多人会漏掉这一步,直接调用 ctx.Input.File(),结果返回 nil 或报错 http: no such file。根本原因是 Beego 默认不自动解析 multipart 表单——它只在显式调用 ParseForm() 时才把文件句柄加载进内存。
正确顺序是:ctx.Input.ParseForm() → ctx.Input.File("file") → file.Open()。如果跳过第一步,File() 永远拿不到文件元数据。
- 必须在 Controller 方法开头立即调用,不能放在条件分支里
- 即使你只上传一个文件,也得调用,Beego 不做隐式解析
- 调用后可多次使用
ctx.Input.File(),不会重复解析
处理多文件上传:循环调用 ctx.Input.File() 而非硬编码 key 名
前端用 <input type="file" name="files" multiple> 上传多个文件时,Beego 并不会自动把它们打包成 slice;而是把每个文件都注册为独立的 key(如 files[0]、files[1]),或者更常见的是——全部塞进同一个 key 下(取决于浏览器行为)。实际中更可靠的做法是让前端用相同 name(如 name="upload")提交多个 <input type="file">,或统一用 name="files" 并在后端遍历。
Beego 提供了 ctx.Input.GetFiles("files") 直接获取所有同名文件列表,比手动拼 key 更安全。
-
ctx.Input.GetFiles("files")返回[]*multipart.FileHeader,长度即上传文件数 - 每个
FileHeader都要单独Open(),拿到multipart.File才能读内容 - 注意:不要对同一个
FileHeader多次Open(),第二次会失败
保存文件时务必校验 FileHeader.Size 和 Filename
用户可能上传空文件、超大文件,或构造恶意文件名(如 ../../etc/passwd)。Beego 不做默认过滤,全靠你拦截。
FileHeader.Size 是真实字节数,可用于限制单文件大小;FileHeader.Filename 是客户端传来的原始文件名,不能直接拼路径。
- 用
filepath.Base()截取合法文件名,丢弃路径部分 - 检查
Size > 10 * 1024 * 1024(例如 10MB)就直接ctx.Abort(400, "file too large") - 用
mime.TypeByExtension()或file.Header做 MIME 类型校验,避免伪装图片的 webshell - 保存路径建议用 UUID 重命名,不依赖原始
Filename
Beego 2.x 中 ctx.Input.File() 在 POST 和 PUT 请求中的行为差异
Beego 默认只对 POST 请求自动启用 multipart 解析上下文;如果是 PUT 或 PATCH,ParseForm() 会静默失败,File() 返回 nil,且无错误提示。
解决办法是在路由注册时显式开启 multipart 支持:
beego.Router("/upload", &UploadController{}, "put:Upload")
// 改为:
beego.Router("/upload", &UploadController{}, "put:Upload").EnableJson(false)
更稳妥的方式是统一用 POST 处理上传,或在 Controller 的 Prepare() 方法里手动初始化 multipart:
- 重写
Prepare(),判断ctx.Input.Method == "PUT"且Content-Type含multipart/form-data - 然后调用
ctx.Input.ParseMultipartForm(32 (32MB 内存缓冲) - 再调用
ctx.Input.ParseForm()
这个细节很容易被忽略,尤其是前后端约定用 PUT 传资源时,服务端收不到文件却查不出原因。











