必须先调用 r.parsemultipartform(32

Go HTTP 服务如何接收 multipart 文件上传
Go 标准库 net/http 原生支持 multipart/form-data,无需额外依赖。关键不是“怎么写路由”,而是别漏掉 r.ParseMultipartForm() —— 这步不调用,r.FormFile() 会返回 http.ErrMissingFile 或空文件。
常见错误:直接调用 r.FormFile("file") 而没先解析表单,或忽略 maxMemory 参数导致大文件直接读进内存撑爆进程。
r.ParseMultipartForm(32 表示最多 32MB 存内存,超限部分写临时磁盘文件- 上传字段名(如
"file")必须和前端<input type="file" name="file">的name严格一致 - 获取文件后务必用
defer file.Close(),否则句柄泄漏 - 保存路径需提前确保父目录存在,
os.MkdirAll(dir, 0755)别省略
如何安全地提供文件下载(避免路径遍历漏洞)
用 http.ServeFile() 或 http.ServeContent() 直接暴露文件系统路径是危险的。攻击者可能传 ../../etc/passwd 绕过限制。
正确做法是:只允许下载预设目录下的文件,并用 filepath.Clean() + 白名单校验双重防护。
立即学习“go语言免费学习笔记(深入)”;
一套面向小企业用户的企业网站程序!功能简单,操作简单。实现了小企业网站的很多实用的功能,如文章新闻模块、图片展示、产品列表以及小型的下载功能,还同时增加了邮件订阅等相应模块。公告,友情链接等这些通用功能本程序也同样都集成了!同时本程序引入了模块功能,只要在系统默认模板上创建模块,可以在任何一个语言环境(或任意风格)的适当位置进行使用!
- 先调用
filepath.Clean(filename)归一化路径,消除..和多余斜杠 - 检查清理后路径是否仍以允许的根目录开头,例如
strings.HasPrefix(cleanPath, "/uploads/") - 不要拼接路径后直接
os.Open(),应使用filepath.Join(rootDir, cleanPath)构造绝对路径 - 若需流式下载大文件,用
io.Copy(w, f)并设置w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
上传时如何校验文件类型与大小(不止靠前端)
前端 accept 和 JS 校验完全不可信。真实校验必须在服务端做,且不能只看 Content-Type(易伪造),要读取文件头(magic bytes)。
例如判断是否为 PNG:读前 8 字节,比对是否为 89 50 4E 47 0D 0A 1A 0A;PDF 则看前 4 字节是否为 %PDF。
- 用
file.Seek(0, 0)回到文件开头再读 header,否则可能读到空 - 上传大小限制应在
ParseMultipartForm()参数和业务逻辑里双重控制 - 对用户可控的文件名做清洗:
path.Base(filename)去掉路径,正则替换非法字符(如/ \ : * ? " |) - 生成唯一文件名(如
uuid.New().String() + filepath.Ext(orig)),避免覆盖或冲突
为什么用 io.Copy 而不是 ioutil.ReadAll 处理文件流
上传下载本质是 I/O 流操作。ioutil.ReadAll(或 io.ReadAll)会把整个文件读进内存,100MB 文件就占 100MB 内存 —— 并发高时直接 OOM。
io.Copy 是流式处理,默认 32KB 缓冲区,内存占用恒定,适合任意大小文件。
- 上传保存:
io.Copy(dstFile, srcFile),而非dstFile.Write(ioutil.ReadAll(srcFile)) - 下载响应:
io.Copy(w, f),配合http.ServeContent可自动处理 Range 请求(断点续传) - 若需进度追踪,可用自定义
io.Writer包装w,在Write()中更新计数器
真正容易被忽略的是:上传临时文件的生命周期管理。Go 的 multipart.File 是 *os.File,但底层临时文件不会自动删除 —— 必须显式 os.Remove(file.Filename),否则磁盘迟早写满。









