必须调用 r.ParseMultipartForm 才能解析 multipart/form-data 文件,r.FormFile 会返回 nil 否则;r.ParseForm 无效;参数是内存缓冲上限而非总大小限制。

上传前必须调 r.ParseMultipartForm,否则文件字段全为空
Go 的 multipart 解析是懒加载的,不主动调用就什么也拿不到。很多人写完 r.FormFile("video") 却返回 nil, http: no such file,不是前端没传,是根本没触发解析。
注意:r.ParseForm() 对 multipart/form-data 完全无效;参数值不是“总请求大小限制”,而是内存缓冲上限——比如设 10 (10MB),小字段和头信息放内存,超的部分自动落临时文件。
- 设为
0会 fallback 到默认 32MB,语义模糊,别这么干 - 多文件上传必须用
r.MultipartForm.File["videos"]切片,r.FormFile只取第一个 - 解析后务必检查切片长度:
if len(files) == 0,不然range直接跳过,逻辑中断无提示
io.Copy 流式写入磁盘或对象存储,禁用 io.ReadAll
大视频文件(如 200MB MP4)用 io.ReadAll(r.Body) 或 bytes.Buffer 缓存,几乎必然 OOM。真实生产中常见 panic 日志:runtime: out of memory。
正确路径是流式直写:从 hdr.Open() 拿到 io.ReadCloser,用 io.Copy 直接写入 *os.File 或 MinIO/aliyun-oss 的 Writer 接口。
立即学习“go语言免费学习笔记(深入)”;
- 配合
http.MaxBytesReader限流,防恶意放大攻击:limitReader := http.MaxBytesReader(w, r.Body, 500(500MB 上限) - 需校验 SHA256?用
io.TeeReader(limitReader, hash),边读边算,避免二次遍历 - 写入前必须用
filepath.Join(uploadDir, safeFilename)拼路径,手动清理../、强制重命名(如加 UUID 前缀)
数据库存二进制字段,gorm:"type:bytes;serializer:gob" 是硬性要求
直接声明 VideoData []byte `gorm:"column:video_data"` 而不指定序列化器,GORM 会 fallback 到 JSON 序列化——把原始字节转成 base64 字符串,体积膨胀 33%,且无法被 PostgreSQL 的 BYTEA 或 MySQL 的 MEDIUMBLOB 原生索引。
字段类型必须显式对齐底层数据库:
- PostgreSQL/MySQL:
gorm:"type:bytes;serializer:gob"→ 使用 Go 原生二进制序列化 - SQLite:
gorm:"type:BLOB"必须显式写,否则当 TEXT 处理 - 字段超 64KB?MySQL 需设为
MEDIUMBLOB或LONGBLOB,否则静默截断
异步处理不能靠裸 go func() { process() }(),必须先落盘再投队列
视频转码、封面提取、元信息分析这些 CPU 密集操作,如果只用 go func() 启动协程,进程崩溃、OOM、重启后任务全丢。这不是延迟问题,是数据丢失。
可靠链路是:上传完成 → 保存到本地磁盘或对象存储 → 写入 Redis Stream / Kafka / Asynq → 消费者拉取 → 处理成功后 XACK 或 CommitOffset。
- 消费者失败时不 ACK,让中间件重投;超时未 ACK 的由死信队列接管
- 批量处理时,
time.Ticker必须在数量触发发送后调ticker.Reset(timeout),否则倒计时错乱导致空发或延迟失准 - 关键错误日志(如转码失败)不能走异步通道,应直写带
os.O_SYNC的专用日志文件
os.Stat 性能可能下降一倍——这些细节不测压、不上线,根本发现不了。


















