Kratos服务端不直接接收文件,应通过预签名URL机制让前端直传MinIO/OSS。其核心是暴露/oss/policy接口返回签名策略,前端用FormData直传,Kratos全程不触碰文件流,仅做轻量凭证分发。

Kratos服务端不直接收文件,这是原则
Kratos微服务做文件上传,第一件事就是放弃「接收 multipart/form-data 后再转发」的思路。这种做法会让服务变成文件中转站:吃内存、占带宽、拖慢响应、难以横向扩展。所有主流对象存储(OSS、MinIO、S3)都支持预签名 URL 机制,Kratos 应该只负责签发,不碰原始文件流。
用 ossClient.GeneratePresignedPostPolicy 签发临时上传凭证
阿里云 OSS 的 Go SDK 提供了 GeneratePresignedPostPolicy 方法,它返回一个包含 host、policy、signature、OSSAccessKeyId 等字段的 map,前端可直接用它构造表单提交到 OSS。
- 必须设置
Content-Length-Range条件,否则 OSS 拒绝上传(常见错误:InvalidPolicyDocument) -
key字段要带路径前缀(如"uploads/${filename}"),但不能含通配符以外的变量;OSS 不解析 `${filename}`,只是透传给回调或前端拼接 - 过期时间建议 ≤ 15 分钟(
expiration设为time.Now().Add(15 * time.Minute)),太长有泄露风险,太短前端来不及上传 - 若需回调通知,得在 Policy 中显式添加
callback字段,并确保 OSS 控制台已配置合法的回调地址
在 Kratos HTTP Handler 里暴露 /oss/policy 接口
别用 gRPC 做上传凭证分发——gRPC 不适合传策略参数,且前端调用成本高。直接写一个 HTTP handler,返回 JSON 格式的预签名数据即可:
func (s *OSSService) GetOSSPolicy(ctx http.Context) error {
bucket := "your-bucket-name"
expiration := time.Now().Add(15 * time.Minute)
policy := oss.PostPolicy{
Expiration: expiration.Format(time.RFC3339),
Conditions: []oss.Condition{
{"bucket", bucket},
{"content-length-range", 0, 104857600}, // 0 ~ 100MB
{"key", "uploads/"},
},
}
result, err := s.ossClient.GeneratePresignedPostPolicy(policy)
if err != nil {
return errors.BadRequest("oss.policy_fail", err.Error())
}
return ctx.JSON(http.StatusOK, result)
}
注意:result.host 是 https://<bucket>.<region>.aliyuncs.com</region></bucket> 形式,不是 oss-cn-hangzhou.aliyuncs.com;前端必须用 host 做 form action,否则跨域失败。
前端用 FormData 直传 OSS,不经过 Kratos
拿到 policy 响应后,前端构造 FormData 并 fetch 提交,关键点是:
- form 的
action必须是result.host,不是 Kratos 地址 - 所有 policy 字段(
key、OSSAccessKeyId、policy、signature)都要作为 hidden input 加入 form - 文件字段名固定为
file(OSS 默认),不能改;若后端需要自定义字段名,得用 callback 或 postObject 方式 - 上传成功后,OSS 返回 204 或重定向,前端应从
result.key拼出最终 URL(如https://<bucket>.<region>.aliyuncs.com/<key></key></region></bucket>)存入业务库
这整条链路里,Kratos 只参与一次轻量 HTTP 请求,其余全是前端和 OSS 的直连。一旦出现上传失败,问题一定出在 policy 过期、key 冲突、CORS 配置或前端拼接逻辑上,跟 Kratos 无关。


















