Portfolio
← 返回博客列表
存储桶S3协议和常用的文件上传方式

2026-08-11 · 阅读 5

存储桶S3协议和常用的文件上传方式

S3 API

三、S3 协议(Amazon S3 API)

S3 不是某个具体的上传协议,而是 Amazon 提出的一套对象存储 RESTful API 标准。如今几乎所有主流云厂商都兼容 S3 API:

云厂商存储服务S3 兼容性
AWSAmazon S3原生
阿里云OSS高度兼容
腾讯云COS高度兼容
CloudflareR2S3 兼容
自建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│
                                 └──────────┘

关键设计要点

  1. 文件直传到存储服务,后端只负责签发凭证和记录元数据
  2. CORS 必须配置,否则浏览器会拦截跨域上传请求
  3. 上传回调:上传成功后,前端通知后端记录文件信息(key、size、type),后端也可以通过 S3 的事件通知自动触发
  4. 权限最小化:STS 凭证应限制到具体的路径前缀和操作类型