c.SaveUploadedFile() 会覆盖同名文件是因为其内部使用 os.Create(),以 O_CREATE|O_TRUNC 模式打开文件,存在即清空重写;避免覆盖应采用 UUID 或哈希生成唯一文件名,并用 os.OpenFile 配合 O_EXCL 确保原子性。

为什么 c.SaveUploadedFile() 会直接覆盖同名文件
c.SaveUploadedFile() 内部调用的是 os.Create(),它默认以 O_CREATE | O_TRUNC 模式打开目标路径——只要文件存在,就清空重写。这不是 bug,是设计如此。你没做任何干预时,用户传两个叫 report.pdf 的文件,第二个会无声覆盖第一个。
避免覆盖的三种可靠做法
核心原则:不依赖原始文件名,也不信任前端传来的任何命名信息。
- 用
filepath.Base(file.Filename)截掉可能含../的路径部分,防止路径遍历 - 生成唯一标识:推荐
uuid.New().String() + "-" + cleanName,或基于文件内容哈希(如sha256.Sum256前8字节) - 保存前检查目标路径是否存在:用
os.Stat(dstPath) == nil判断,存在则跳过或加序号(如report_1.pdf),但注意并发场景下仍可能竞争,所以哈希/UUID 更稳妥
别用 c.SaveUploadedFile() 做重命名控制
这个方法不暴露底层 *os.File 句柄,也没提供“如果存在则跳过”选项。一旦你需要防覆盖、限大小、验类型、设权限,就必须绕开它:
- 改用
file.Open()获取multipart.File - 用
os.OpenFile(dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)——O_EXCL确保不会覆盖已有文件 - 若
O_EXCL返回os.ErrExist,说明已存在,此时可换名重试或直接拒绝 - 记得
defer src.Close()和defer dst.Close(),漏掉会导致 fd 泄露
真正麻烦的是并发上传同名文件
即使你加了 UUID,如果多个请求同时上传完全相同的文件(比如同一张图反复点提交),它们的哈希一致,最终还是会写到同一个路径。这时靠文件系统锁不现实,建议:
- 上传前先查数据库或 Redis 缓存中该哈希是否已存在,存在则跳过保存,只返回已有 URL
- 或者接受“覆盖语义”,但记录每次上传的元数据(用户 ID、时间、IP),方便审计
重命名不是终点,而是起点;覆盖风险背后,其实是业务上对“文件唯一性”的定义是否清晰——这点最容易被忽略。


















