
本文详解如何在不暴露 aws 凭据的前提下,通过 go sdk 为大型文件生成分段上传所需的预签名 url(含 uploadid 获取、各 part 预签名、完成/中止上传),并提供可直接集成的生产级代码示例。
本文详解如何在不暴露 aws 凭据的前提下,通过 go sdk 为大型文件生成分段上传所需的预签名 url(含 uploadid 获取、各 part 预签名、完成/中止上传),并提供可直接集成的生产级代码示例。
在构建安全、可扩展的云存储架构时,常需将大文件(如视频、备份包)上传至 Amazon S3,同时严格隔离敏感凭证——外部服务(如前端、第三方系统)不应持有任何 AWS Access Key 或 Secret。AWS 原生支持 预签名 URL(Presigned URL),但需注意:S3 的 multipart upload 并不支持单个 URL 完成全部操作;它要求三阶段协同:① 创建分段上传并获取 UploadId;② 对每个 part 单独预签名;③ 最终由服务端完成或中止上传。Go SDK(v1 和 v2)均完全支持该模式,关键在于分步预签名 + 服务端协调。
以下基于 AWS SDK for Go v1(github.com/aws/aws-sdk-go)给出完整实现逻辑(v2 SDK 同理,仅导入路径与结构体名略有差异):
✅ 步骤一:生成分段上传预签名 URL 列表
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func (s *S3Service) GenerateMultipartPresignedURLs(key string, partCount int) ([]string, error) {
// 1. 创建分段上传,获取 UploadId
createOut, err := s.s3.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
// 可选:指定 ContentType、ServerSideEncryption 等
})
if err != nil {
return nil, err
}
uploadID := aws.StringValue(createOut.UploadId)
// 2. 为每个 part 生成独立预签名 URL
urls := make([]string, 0, partCount)
for i := 1; i <= partCount; i++ { // PartNumber 从 1 开始(非 0)
req, _ := s.s3.UploadPartRequest(&s3.UploadPartInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
PartNumber: aws.Int64(int64(i)),
UploadId: aws.String(uploadID),
})
// 预签名有效期建议设为 1–24 小时(根据客户端网络环境调整)
url, err := req.Presign(2 * time.Hour)
if err != nil {
return nil, err
}
urls = append(urls, url)
}
return urls, nil
}⚠️ 注意事项:
PartNumber必须从1开始连续递增(不可为 0);- 每个 URL 仅对对应 part 有效,且需在签名过期前完成上传;
- 建议将
uploadID与key一并返回给客户端(用于后续完成请求),或存入数据库关联追踪。
✅ 步骤二:客户端上传各 part(HTTP PUT)
外部服务收到 URL 列表后,对每个 part 执行标准 HTTP PUT 请求(无需 AWS SDK):
curl -X PUT \ --data-binary "@part1.bin" \ "https://my-bucket.s3.amazonaws.com/my-key?partNumber=1&uploadId=abc123..."
响应头中必须提取 ETag(即该 part 的 MD5 校验值),并在完成请求时提交所有 ETag + PartNumber 映射。
✅ 步骤三:服务端完成或中止上传
客户端上传全部 part 后,调用回调接口,传入 key、uploadID 及各 part 的 ETag 列表:
type CompletedPart struct {
ETag *string
PartNumber *int64
}
func (s *S3Service) CompleteMultipartUpload(
key, uploadID string,
completedParts []CompletedPart,
) (string, error) {
_, err := s.s3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
UploadId: aws.String(uploadID),
MultipartUpload: &s3.CompletedMultipartUpload{
Parts: completedParts,
},
})
if err != nil {
return "", err
}
return "https://" + s.bucketName + ".s3.amazonaws.com/" + key, nil
}
// 若失败或超时,主动中止以避免产生存储费用
func (s *S3Service) AbortMultipartUpload(key, uploadID string) error {
_, err := s.s3.AbortMultipartUpload(&s3.AbortMultipartUploadInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
UploadId: aws.String(uploadID),
})
return err
}? 关键总结:
- ✅ 安全合规:全程无需向客户端暴露任何 AWS 凭据;
- ✅ 弹性可控:服务端掌握
UploadId生命周期,可主动中止无效上传;- ✅ 可监控可重试:建议将
UploadId、key、状态、时间戳持久化,便于审计与断点续传;- ⚠️ 成本提醒:未完成的 multipart upload 会持续计费(默认 3 天自动清理,但建议主动
Abort);- ? 兼容性:该方案适用于所有 S3 兼容对象存储(如 MinIO、Ceph RGW),只需替换 endpoint 即可。
通过以上三步闭环设计,即可在 Go 应用中稳健支撑 TB 级文件的分段上传,兼顾安全性、可观测性与云原生最佳实践。

















