golang服务端需先校验part_number连续性(1..total_parts),再逐片比对x-part-hash,拒绝超界或非整数序号;必须用os.open后重算sha256并核对size,禁用内存哈希;合并前加redis锁防并发冲突,成功后才清理分片。

分段上传后如何验证所有分片已按序接收?
关键不是“有没有收到”,而是“有没有漏、有没有乱、有没有重复”。Golang 服务端必须基于客户端提交的 part_number 和 content-md5(或 sha256)做两级校验:先按序检查编号连续性,再逐片比对哈希。跳过编号校验直接拼接,哪怕 MD5 全对,也可能因前端重传导致序号错位(比如 1/3/3/4 → 实际缺第 2 片但有两份第 3 片)。
实操建议:
- 接收每个分片时,用
map[int]*PartMeta缓存元信息,PartMeta至少含part_number、size、hash、uploaded_at - 上传完成后,检查
len(cache)是否等于声明的总片数total_parts,且cache的 key 是否为1..total_parts连续整数 - 拒绝接受
part_number 或 <code>> total_parts的请求,返回400 Bad Request
为什么不能只依赖文件名或 URL 路径判断顺序?
HTTP 请求无序到达是常态——TCP 重传、CDN 节点调度、客户端多线程并发上传都会打乱到达时间。如果仅靠 /upload/xxx/part-3 这类路径推断顺序,或用 time.Now() 记录入库时间排序,必然出错。真实场景中,part-1 可能比 part-5 晚 2 秒写入磁盘。
实操建议:
- 强制客户端在每个分片请求 body 或 header 中携带
X-Part-Number: 3和X-Part-Hash: sha256_xxx - 服务端解析时立即校验
X-Part-Number类型和范围,非整数或超界直接http.Error(w, "invalid part number", http.StatusBadRequest) - 避免用
os.Create(fmt.Sprintf("part_%d", n))命名临时文件——改用唯一 ID(如 UUID)+ 数据库存储映射,防止恶意构造文件名覆盖
合并前如何验证分片内容未被篡改或截断?
MD5 已不安全,但很多旧协议仍用;生产环境必须用 sha256。注意:校验必须在写入磁盘后重新读取计算,不能只校验内存中的 []byte —— 因为写入过程可能因磁盘满、权限问题导致静默截断(例如期望写 5MB,实际只落盘 2MB)。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实操建议:
- 分片接收完立即用
os.Open()+io.Copy(hash.Hash, file)重新计算哈希,与请求头中的X-Part-Hash比对 - 同时检查文件实际 size:
fi, _ := file.Stat(); if fi.Size() != expected_size { return error } - 合并时用
io.MultiReader()按part_number升序组装 reader,而非os.OpenFile()循环打开——减少 open fd 数量,也规避文件被意外删除的风险
并发上传下如何防止同一文件的多个合并任务冲突?
用户可能误点多次“上传”,或网络抖动触发前端自动重试,导致同一 upload_id 出现多个合并请求。若不加控制,可能产生竞态:A 任务刚合并完 part-1~4,B 任务又把 part-1~3 再拼一次,最终文件损坏。
实操建议:
- 用 Redis 的
SET upload_id:status merging EX 300 NX做合并锁,失败则返回409 Conflict并提示“合并中,请勿重复提交” - 合并成功后,用
DEL upload_id:parts清空分片元数据,避免残留脏数据干扰下次上传 - 为防锁失效(如进程崩溃),所有合并操作需幂等:先检查目标文件是否存在且 size 匹配预期总长,存在则跳过合并直接返回 success
最易被忽略的是分片存储路径的清理时机——必须在合并成功且校验最终文件哈希通过后才删临时分片,否则断点续传无法恢复。临时文件保留时间建议设为 24 小时,用后台 goroutine 定期扫描过期项。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










