必须绕过parsemultipartform和formfile,用multipart.newreader手动流式拆包:安全提取boundary、逐part消费、禁用o_append、用io.teereader边传边校验,避免oom与文件损坏。

别用 r.ParseMultipartForm,也别调 c.FormFile() —— 这俩会把整个文件读进内存,1GB 就占 1GB RAM,不是慢,是直接 OOM。
绕过自动解析,用 multipart.NewReader 手动流式拆包
Go 的 http.Request.Body 是原始字节流,只要没触发任何 multipart 解析逻辑,它就一直可读。关键在于:自己提取 boundary,然后交给 multipart.NewReader 按 part 逐个消费。
- 用
mime.ParseMediaType(r.Header.Get("Content-Type"))安全取boundary,别用strings.Split硬切 —— header 带引号或空格就崩 - 如果
Content-Type根本没传,直接返回400 Bad Request,不 fallback - 每个
part必须完整读完(比如用io.CopyN(part, io.Discard, maxPartSize)),否则后续NextPart()会错位甚至丢数据 -
part.FormName()和part.FileName()返回的是解码后字符串,但底层读取仍按字节流处理,别拿len(str)当字节数去截断
写文件时别碰 os.O_APPEND
断点续传场景下,os.O_APPEND 是隐形炸弹:它会让所有 Write() 忽略你之前 Seek() 的偏移,强制追加到末尾。结果就是你想写第 10MB,却在文件末尾新增一段,中间全是 \x00,文件直接损坏。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 正确打开方式:
os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644),不加O_TRUNC、不加O_APPEND - 打开后立刻
f.Seek(offset, io.SeekStart),并检查返回值是否等于offset—— 不等说明文件被并发修改或磁盘异常 - 并发写同一分片时,用
os.O_EXCL防覆盖,或加flock,或用唯一临时名 +os.Rename原子提交
上传同时算 MD5?用 io.TeeReader,别缓存
边上传边校验的核心是“只过一遍内存”。io.TeeReader 包装一个 io.Reader,每次 Read 时自动把数据喂给指定的 io.Writer(比如 md5.Hash),再原样返回给上游 —— 零拷贝,不缓冲,天然适配 HTTP 流。
- 示例中
hasher.Sum(nil)必须在io.Copy(dst, teeReader)完成后调用,提前调结果为空 - 若需限大小,把
teeReader再包一层io.LimitReader(teeReader, maxSize) - 别用
bytes.Buffer或io.ReadAll接整个 body —— 这等于放弃流式 - 分块上传合并后要验整体 MD5,不能拼各块哈希值;要么服务端合并完再跑一次
io.TeeReader + md5.New(),要么让前端算好传上来(如X-Content-MD5header)
最易被忽略的一点:io.Copy 默认 32KB 缓冲,在 NFS、Ceph 或云盘上 syscall 过多,吞吐暴跌 3–5 倍。写磁盘时务必换成 io.CopyBuffer(w, r, make([]byte, 1024*1024)),且别用 sync.Pool 复用这个 buffer —— 压测反而更慢。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










