分片直传不能绕过服务端签名,因OSS要求init_multipart_upload必须由服务端用AccessKey和Policy生成合法签名,前端自行构造会返回403或InvalidAccessKeyId;且uploadPart需服务端签发的Authorization头,OSS v4签名涉及canonicalizedResource等复杂计算及动态Header,必须由Gin服务端完成。

分片直传为什么不能绕过服务端签名
因为OSS不接受未经鉴权的分片上传请求,init_multipart_upload必须由服务端用AccessKey和Policy生成合法签名,前端直接调用会返回403 Forbidden或InvalidAccessKeyId错误。即使前端拼出uploadId和partNumber,没有服务端签发的Authorization头,每个uploadPart请求都会被拒绝。
常见错误是前端试图自己构造签名——OSS v4 签名涉及canonicalizedResource、signedHeaders、payloadHash等复杂计算,且需动态生成X-Oss-Date和X-Oss-Security-Token(使用STS临时凭证时),Gin服务端必须承担这部分逻辑。
- 不要在前端硬编码
accessKeyId/accessKeySecret,哪怕做混淆也极不安全 - 不要复用长期AK/SK,必须通过
AssumeRole获取临时凭证(Expiration控制有效期) - Policy中必须显式声明
conditions:限制bucket、key前缀、content-length-range(如[1048576, 524288000])
回调接口必须能处理 multipart/form-data + raw body 混合请求
OSS上传完成后的回调(Callback)不是普通JSON POST,而是将回调参数以multipart/form-data格式发送,并在body中嵌入原始回调数据(含bucket、object、etag、size等)。Gin默认的c.PostForm无法解析这种结构,直接读c.Request.Body又会被BindJSON等中间件提前消费。
正确做法是:在注册回调路由前,禁用Binding中间件,并手动解析:
func callbackHandler(c *gin.Context) {
// 关键:避免Body被其他中间件读取
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1024*1024)
body, _ := io.ReadAll(c.Request.Body)
// OSS回调body是raw JSON字符串,但Content-Type却是 multipart/form-data
// 所以需要先按boundary提取,再json.Unmarshal
var callbackData struct {
Bucket string `json:"bucket"`
Object string `json:"object"`
Size int64 `json:"size"`
ETag string `json:"etag"`
}
json.Unmarshal(body, &callbackData)
// 处理业务逻辑:更新数据库、触发转码、发消息队列...
}
- 务必设置
http.MaxBytesReader限制body大小,防止恶意超大回调耗尽内存 - 不能依赖
c.ShouldBindJSON,它会尝试解析Content-Type并失败 - 回调URL必须是公网可访问地址(内网IP或localhost会被OSS拒绝)
分片上传状态不能只靠前端维护
前端记录已上传分片索引(如[1,2,4])只是辅助手段,真正可靠的断点续传依据是OSS服务端的ListParts结果。Gin服务端在每次上传分片前,必须调用bucket.ListParts(objectName, uploadId)查询当前已成功上传的PartNumber列表,否则会出现“前端认为已传、OSS实际失败”的脏状态。
典型场景:网络抖动导致某分片uploadPart超时,但OSS其实已接收;前端重试时跳过该分片,后续CompleteMultipartUpload因缺少该part而失败。
- 每次
uploadPart前必须查ListParts,不能缓存结果 -
uploadId需持久化存储(如Redis或DB),超时未完成的上传要定期AbortMultipartUpload清理 - 前端传来的
partNumber仅作校验,最终以OSS返回的Etag和ListParts为准
本地临时文件完全没必要存
分片直传的本质就是绕过服务端中转——文件从浏览器直传OSS,Gin只负责签发凭证、接收回调、管理元数据。如果在Gin服务端还保存一份分片文件(比如写到/tmp/chunks/),不仅浪费磁盘IO和带宽,还会引入竞态条件(多实例部署时无法共享临时目录)。
真正需要服务端落地的只有两件事:uploadId与objectKey的映射关系、回调后触发的业务动作(如入库、通知)。其余全部交给OSS原生能力。
- 删除所有类似
os.WriteFile("chunk_1")的代码 - 不要用
c.FormFile接收分片,那是传统表单上传模式 - 前端应使用
XMLHttpRequest或fetch直接POST到OSS endpoint,Gin只提供签名和回调入口
ListParts调用时机,这两个地方出错不会立刻报错,但会在大流量或网络异常时集中暴露。


















