
2026-08-11 · 阅读 5
存储桶S3协议和常用的文件上传方式
S3 API
三、S3 协议(Amazon S3 API)
S3 不是某个具体的上传协议,而是 Amazon 提出的一套对象存储 RESTful API 标准。如今几乎所有主流云厂商都兼容 S3 API:
| 云厂商 | 存储服务 | S3 兼容性 |
|---|---|---|
| AWS | Amazon S3 | 原生 |
| 阿里云 | OSS | 高度兼容 |
| 腾讯云 | COS | 高度兼容 |
| Cloudflare | R2 | S3 兼容 |
| 自建 | MinIO | 完全兼容 |
3.1 核心操作一览
S3 本质上就是一套 HTTP API,每个操作对应一个 HTTP 请求:
| 操作 | HTTP 方法 | 路径 | 说明 |
|---|---|---|---|
| 上传对象 | PUT | /{bucket}/{key} | 简单上传 |
| 下载对象 | GET | /{bucket}/{key} | 下载文件 |
| 删除对象 | DELETE | /{bucket}/{key} | 删除文件 |
| 获取元信息 | HEAD | /{bucket}/{key} | 获取大小/类型/ETag |
| 列出对象 | GET | /{bucket}?list-type=2 | 列出桶内对象 |
| 初始化分片 | POST | /{bucket}/{key}?uploads | 开始分片上传 |
| 上传分片 | PUT | /{bucket}/{key}?partNumber=N&uploadId=xxx | 上传单个分片 |
| 完成分片 | POST | /{bucket}/{key}?uploadId=xxx | 合并所有分片 |
3.2 两种主流认证方式
方式一:预签名 URL(Presigned URL)
服务器用 AccessKey 对一个具体的请求(如 PUT 到某个 key)进行签名,生成一个带签名参数的临时 URL,客户端拿到 URL 后直接向 S3 发起请求。
https://bucket.s3.amazonaws.com/images/avatar.jpg
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20240101/ap-southeast-1/s3/aws4_request
&X-Amz-Date=20240101T120000Z
&X-Amz-Expires=3600
&X-Amz-Signature=xxxxxxx ← 签名,防止伪造
优点:URL 有过期时间,泄漏后影响有限。
方式二:STS 临时凭证
服务器通过 AWS STS(Security Token Service)获取一组临时凭证(AccessKeyId + SecretAccessKey + SessionToken),下发给客户端,客户端用 SDK 直接操作 S3。
优点:一次授权可上传多个文件,更适合分片上传场景。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 客户端 │── 1.请求上传凭证 ───▶│ 后端服务 │── 2.生成签名/STS ───▶│ S3 服务 │
│ (前端) │◀── 3.返回凭证 ──────│ │◀────────────────── │ │
│ │── 4.客户端直传 ──────────────────────────────────────▶│ │
└──────────┘ └──────────┘ └──────────┘
核心思想:让文件从客户端直接传到存储服务,不经过你的服务器,节省带宽和服务器资源。
四、文件的五种主要上传方式
4.1 简单上传(Put Object)
一次 HTTP PUT 请求上传整个文件。
客户端 ──────── PUT /{bucket}/{key} ────────▶ S3
Body: 文件二进制数据
特点:
- 适合小文件(通常 < 5MB)
- 实现最简单,一行代码搞定
- 失败后必须整个文件重新上传
// 前端:简单上传
async function simpleUpload(file, presignedUrl) {
const res = await fetch(presignedUrl, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type },
})
return res.ok ? res.headers.get("ETag") : null
}
4.2 表单上传(POST with Policy)
使用 POST 方法,以 multipart/form-data 格式提交,携带 Policy 文档和签名。
特点:
- 适合 HTML 表单直接提交到 S3
- 使用 POST 而非 PUT
- 浏览器原生支持,无需 JavaScript
<form action="https://bucket.s3.amazonaws.com/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="key" value="uploads/${filename}">
<input type="hidden" name="AWSAccessKeyId" value="AKIAIOSFODNN7EXAMPLE">
<input type="hidden" name="policy" value="BASE64_ENCODED_POLICY">
<input type="hidden" name="signature" value="COMPUTED_SIGNATURE">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
4.3 分片上传(Multipart Upload)— 大文件首选
将文件切分为多个分片(Part),每个分片独立上传,最后合并。
第1步:POST /{bucket}/{key}?uploads → 返回 uploadId
第2步:PUT /{bucket}/{key}?partNumber=1&uploadId=xxx → 分片1
PUT /{bucket}/{key}?partNumber=2&uploadId=xxx → 分片2
PUT /{bucket}/{key}?partNumber=3&uploadId=xxx → 分片3
第3步:POST /{bucket}/{key}?uploadId=xxx → 合并所有分片
特点:
- 适合大文件(通常 > 5MB),最大支持 5TB
- 支持并行上传,速度更快
- 支持断点续传(失败的分片单独重传)
- AWS S3 建议超过 100MB 的文件使用此方式
// 分片上传核心逻辑
async function multipartUpload(file, bucket, key) {
const PART_SIZE = 5 * 1024 * 1024 // 每片 5MB
const totalParts = Math.ceil(file.size / PART_SIZE)
// 第1步:初始化
const { uploadId } = await s3Client.send(
new CreateMultipartUploadCommand({ Bucket: bucket, Key: key })
)
const parts = []
for (let i = 0; i < totalParts; i++) {
const start = i * PART_SIZE
const end = Math.min(start + PART_SIZE, file.size)
const blob = file.slice(start, end)
// 第2步:上传分片(可并行)
const { ETag } = await s3Client.send(
new UploadPartCommand({
Bucket: bucket, Key: key,
PartNumber: i + 1, UploadId: uploadId, Body: blob,
})
)
parts.push({ PartNumber: i + 1, ETag })
}
// 第3步:合并
await s3Client.send(
new CompleteMultipartUploadCommand({
Bucket: bucket, Key: key, UploadId: uploadId,
MultipartUpload: { Parts: parts },
})
)
}
4.4 预签名 URL 直传 — 前后端分离最常用
服务器生成一个带签名的临时 URL,前端拿到后直接上传到 S3,文件不经过后端服务器。
┌──────────┐ 1.我要上传 file.jpg ┌──────────┐
│ 前端 │ ────────────────────────▶│ 后端 API │
│ │ │ │
│ │◀──── 2.返回 Presigned URL │ │
│ │ └──────────┘
│ │ 3.PUT file.jpg 直传 │
│ │ ──────────────────────────────────────▶ S3
│ │◀──── 4.200 OK (ETag) ────────────────── │
└──────────┘
// 服务端(Node.js)生成预签名 URL
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
const s3Client = new S3Client({
region: "ap-southeast-1",
credentials: { accessKeyId: "xxx", secretAccessKey: "xxx" },
})
async function getUploadUrl(key, contentType) {
const command = new PutObjectCommand({
Bucket: "my-bucket",
Key: key,
ContentType: contentType,
})
return getSignedUrl(s3Client, command, { expiresIn: 3600 })
}
4.5 STS 临时凭证直传 — 小程序/移动端推荐
服务器通过 STS 获取临时凭证下发给客户端,客户端用 SDK 直接上传。
// 服务端:获取 STS 临时凭证
const sts = new STSClient({ region: "ap-southeast-1" })
const { Credentials } = await sts.send(new GetFederationTokenCommand({
Name: "user-upload",
Policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Action: ["s3:PutObject"],
Resource: [`arn:aws:s3:::my-bucket/uploads/${userId}/*`],
}],
}),
DurationSeconds: 3600,
}))
// 返回给客户端
return {
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
bucket: "my-bucket",
region: "ap-southeast-1",
}
// 客户端(小程序):用临时凭证直传
const s3 = new S3Client({
region: res.region,
credentials: {
accessKeyId: res.accessKeyId,
secretAccessKey: res.secretAccessKey,
sessionToken: res.sessionToken,
},
})
await s3.send(new PutObjectCommand({
Bucket: res.bucket,
Key: `uploads/${userId}/${Date.now()}.jpg`,
Body: fileBuffer,
ContentType: "image/jpeg",
}))
五、五种上传方式对比
| 上传方式 | 适用文件大小 | 是否经过服务器 | 实现复杂度 | 断点续传 | 典型场景 |
|---|---|---|---|---|---|
| 简单上传 | < 5MB | 可选 | 最低 | 否 | 头像、小图片 |
| 表单上传 | < 5MB | 否 | 低 | 否 | 传统网页表单 |
| 分片上传 | > 5MB | 可选 | 高 | 是 | 视频大文件 |
| 预签名 URL | 任意 | 否 | 中 | 需自实现 | Web 前端直传 |
| STS 直传 | 任意 | 否 | 高 | 原生支持 | 小程序/移动端 |
六、生产环境推荐架构
┌─────────────────────────────────────┐
│ S3 / OSS / R2 │
│ (对象存储服务) │
└──────────┬──────────────┬───────────┘
│ │
直传文件流 返回元数据
│ │
┌──────────┐ 1.请求上传凭证 ┌──────────┐ │
│ 客户端 │ ◀───────────────────│ 后端 API │◀───┘
│ (前端/小程序)│ ─── 3.返回凭证 ───▶│ │
│ │ ─── 4.直传文件 ──────────────────▶│ (S3)
│ │ ◀──── 5.上传成功 ────────────────│
└──────────┘ ┌──────────┐
│ 数据库 │
6.回调通知 ─────────────────────▶│ │
│ file_key │
│ file_url │
│ file_size│
└──────────┘
关键设计要点:
- 文件直传到存储服务,后端只负责签发凭证和记录元数据
- CORS 必须配置,否则浏览器会拦截跨域上传请求
- 上传回调:上传成功后,前端通知后端记录文件信息(key、size、type),后端也可以通过 S3 的事件通知自动触发
- 权限最小化:STS 凭证应限制到具体的路径前缀和操作类型