缩略图必须由后端用预装的 ffmpeg 提取关键帧生成,禁止前端或纯 go 库实现;需用文件哈希作 key、绝对路径输入、文件锁防并发冲突,并配合静态路由服务,同时注意局域网部署的路径与 cors 配置。

局域网内用 Gin 提供多媒体文件服务时,缩略图不能靠前端生成——浏览器没权限读取本地视频/音频帧,必须由后端提取关键帧并保存为 jpg 或 png。直接调用 ffmpeg 是最稳的方案,但要注意路径、权限、并发和缓存逻辑。
缩略图生成必须走 ffmpeg,别信纯 Go 库
Go 生态里有些封装 ffmpeg 的库(比如 go-ffprobe、goffmpeg),但它们本质仍是调用系统 ffmpeg 二进制。自己 exec.Command 调用更可控,避免中间层 bug 或版本兼容问题。
-
ffmpeg必须预装在运行 Gin 服务的机器上,且PATH可访问;验证方式:which ffmpeg - 不要用
github.com/mortehu/go-ffmpeg这类试图“纯 Go 实现解码”的库——不支持 H.265/AV1 等新编码,抽帧失败率高 - 关键参数固定用:
-ss 00:00:01 -vframes 1 -q:v 2 -y output.jpg,其中-ss越前越快,但太靠前可能黑帧;-q:v 2控制画质(1~31,值越小越清晰) - 输入路径必须是绝对路径,相对路径在 Gin 的 goroutine 中容易因工作目录变动而失效
缩略图接口要带文件指纹,避免重复生成
同一视频反复请求缩略图不能每次都跑 ffmpeg,否则 CPU 暴涨。用文件内容哈希(如 sha256)做 key,生成后的缩略图按 {hash}.jpg 存到指定目录,再通过 Gin 静态路由直接 serve。
- 不要用文件名或修改时间做 key——重命名或误 touch 就会重复生成
- 计算哈希时建议只读前 1MB(对大文件提速),配合
os.Stat().Size()+os.Stat().ModTime()做二级校验,覆盖极小概率冲突 - 缩略图存储目录需独立于上传目录,例如
/var/lib/thumbs/,并确保 Gin 进程有写权限 - Gin 路由示例:
router.Static("/thumbs", "/var/lib/thumbs"),前端直接<img src="/thumbs/abc123.jpg">
并发生成时加文件锁,否则多个请求打同一视频会写乱
两个请求同时对一个视频生成缩略图,可能产生竞态:一个进程刚创建空文件,另一个就往里写,结果图片损坏。不能依赖应用层锁(如 map + sync.Mutex),得用系统级文件锁。
- Go 标准库
syscall.Flock在 Linux/macOS 可用,Windows 需换golang.org/x/sys/windows的LockFileEx - 锁文件建议放在缩略图目录下,命名为
.lock_{hash},而不是锁源视频文件(避免阻塞上传或播放) - 锁超时设为 30 秒足够——正常
ffmpeg抽帧在 1~3 秒内完成,超时说明异常,应记录日志并返回 500 - 生成失败后务必删掉半成品缩略图,否则下次请求会直接 serve 损坏文件
局域网部署要注意路径映射和跨域策略
局域网设备(如手机、平板)访问 Gin 服务时,常出现路径不对或 CORS 拒绝。这不是缩略图逻辑问题,而是部署细节漏掉。
- 如果 Gin 绑定
0.0.0.0:8080,但用户用http://192.168.1.100:8080访问,所有静态资源路径必须基于该 host 构建,不能硬编码localhost - 缩略图 URL 要返回完整路径(如
https://192.168.1.100:8080/thumbs/xxx.jpg),而非相对路径,否则 iOS Safari 加载失败 - 局域网调试阶段可临时关 CORS:
router.Use(cors.New(cors.Config{AllowOrigins: []string{"*"}})),上线前必须限定 IP 段,例如["http://192.168.1.*"] - 移动端访问慢?检查是否启用了
gzip中间件——对小缩略图(router.Use(gzip.Gzip(gzip.DefaultCompression)) 改成条件启用
真正卡住人的不是 ffmpeg 命令怎么写,而是文件锁没做对导致缩略图偶尔花屏,或者局域网 IP 变了之后前端还死磕旧地址。这些点不写死在代码里,光靠文档根本防不住。











