Revel框架上传文件需前端设enctype="multipart/form-data"、后端用c.FormFile()而非c.Params.Get()获取文件;调用c.SaveUploadedFile()保存,且启动时需ParseMultipartForm。

Revel 框架里上传文件,核心就两件事:前端按 multipart/form-data 发,后端用 c.Request.Form 或 c.FormFile 拿——但直接调 c.Params.Get() 会丢数据,这是新手最常踩的坑。
单文件上传:别用 c.Params.Get("file"),要用 c.FormFile()
Revel 的 c.Params.Get() 只处理 URL 查询参数或表单文本字段,对文件上传无效。multipart 表单里的文件必须走专门的解析路径。
-
c.FormFile("file")返回一个*multipart.FileHeader,含文件名、大小、Header 等元信息 - 拿到
FileHeader后,用c.Open打开流,或直接c.SaveUploadedFile(header, dstPath)存到磁盘 - 注意:必须确保路由是
POST,且 HTML 表单设置了enctype="multipart/form-data" - 如果服务启动时没调
c.Request.ParseMultipartForm(32 (比如设了 32MB 限制),大文件会报http: POST content length too large
多文件上传:靠 c.Request.Form["files"] 拿文件名列表,再逐个 c.FormFile
Revel 不像 Laravel 那样自动把 name="files[]" 解成数组;它把同名字段全塞进 c.Request.Form 的 []string 值里,但这些字符串只是文件名,不是文件本身。
- 前端
提交后,c.Request.Form["files"]是空的——因为浏览器根本不会把文件内容当字符串发 - 真正有效的是:浏览器把每个选中的文件都作为独立的 multipart 字段提交,字段名都是
files,所以后端要循环调c.FormFile("files")直到返回 error - 推荐写法:
for i := 0; ; i++ {<br> file, err := c.FormFile("files")<br> if err != nil {<br> break // 没更多 files 字段了<br> }<br> // 处理 file<br>} - 别依赖
input的multiple属性来判断数量——有些老浏览器或移动端可能只传第一个
MIME 类型与文件名安全:不能只信 header.Header.Get("Content-Type")
用户可以轻易伪造请求头里的 Content-Type,比如把恶意 exe 改成 image/jpeg。Revel 本身不校验 MIME,得自己加。
- 优先读
header.Header.Get("Content-Type"),但它只是客户端声明,不可信 - 更可靠的是读文件前几个字节(Magic Number):用
file, _ := c.Open(fileHeader),然后io.ReadFull(file, buf[:2])判断是否为0xff 0xd8(JPEG)等 - 文件名要净化:
filepath.Base(header.Filename)防路径穿越,再过滤掉..、/、控制字符 - 别直接用
header.Filename作存储名——它可能带中文、空格、特殊符号,也可能是../../etc/passwd
大文件或分片上传:Revel 默认不支持流式接收,需手动接管 c.Request.Body
Revel 在解析 form 时默认把整个请求体读进内存,超大文件容易 OOM。真要传几百 MB,得绕过 c.FormFile。
- 先调
c.Request.ParseMultipartForm(0)(设 0 表示不限制内存,让 multipart 解析器用临时文件) - 然后从
c.Request.MultipartForm.File["files"]拿[]*multipart.FileHeader,再用header.Open()流式读 - 或者更底层:直接读
c.Request.Body,按boundary手动解析 multipart 流(不推荐,除非你清楚 RFC 7578) - 注意:一旦调了
c.FormFile或c.Request.ParseMultipartForm,c.Request.Body就被 consume 掉了,不能再读
Revel 的文件上传没有封装层“自动解包”,它的灵活是以显式控制为代价的。最容易忽略的是:没在 handler 开头调 ParseMultipartForm 就直接用 FormFile,结果小文件能传、大文件 400;还有就是把 Form["files"] 当文件数据拿,结果永远是空切片。











