Skip to content

对象存储基本用法(Bucket / Object / 常用操作)

对象存储的用法在各厂商间高度同构:核心概念(bucket、object、key)一致, 常用操作(列桶、建桶、列对象、上传、下载、签名 URL、删除)一一对应。 本文提炼通用用法;厂商差异见 供应商对比(Vendors), 签名 URL 原理见 签名 URL(Signed URL)

核心概念

Bucket(桶)

  • 对象存储的顶层命名空间,容纳一组对象
  • 名称在同一厂商内全局唯一(不同厂商之间可以重名)
  • 默认私有(private):匿名无法读取;公开桶(public)才可无鉴权访问
  • 删除约束:空桶才能删除,非空桶删除会失败 —— 先删对象再删桶

Object(对象)与 Key

  • 对象 = Key + 数据(Data)+ 元数据(Metadata,如 Content-Type)
  • Key 是完整路径(如 demo/r2-client/hello.txt);对象存储里没有真正的 目录 / 文件夹 —— "目录"只是 Key 的前缀约定(prefix),列对象时按前缀 过滤即可实现"列目录"

Region 与 Endpoint

  • Endpoint 是服务入口地址,多数厂商由 region 派生(如 OSS https://oss-cn-hangzhou.aliyuncs.com)或由账号 ID 派生(如 R2 https://<ACCOUNT_ID>.r2.cloudflarestorage.com
  • region 是否必需、如何派生,各厂商不同(详见 供应商对比

概念关系总览:一个 Bucket 容纳若干对象,对象通过 Key 定位;带 / 的 前缀看起来像"目录",实际上是 Key 的命名约定。

flowchart TD
  B["Bucket:my-bucket
(命名空间,默认私有)"] B --> P1["demo/(前缀)"] B --> P2["reports/(前缀)"] P1 --> F1["hello.txt
key = demo/hello.txt"] P1 --> P3["images/(前缀)"] P3 --> F2["logo.png
key = demo/images/logo.png"] P2 --> F3["q1.csv
key = reports/q1.csv"]

各厂商 SDK 对照:S3 系(R2 / MinIO / AWS)用 @aws-sdk/client-s3ListBuckets / HeadBucket / CreateBucket / ListObjectsV2 / PutObject / GetObject / DeleteObject / DeleteBucket 命令;OSS 用 ali-osslistBuckets / list / put / get / delete;Supabase 用 @supabase/supabase-jsstorage.listBuckets() / getBucket() / createBucket() / from(bucket).list() / upload() / download() / remove()

典型操作流程(与各原型 demo 的执行顺序一致):

flowchart TD
  A["① 列出 Bucket"] --> B["② 确保 Bucket 存在
(不存在则创建)"] B --> C["③ 列出对象(按前缀)"] C --> D["④ 上传 Put"] D --> E["⑤ 下载 Get"] E --> F["⑥ 签名 URL(GET 下载 / PUT 直传)"] F --> G["⑦ 删除(对象 → 空桶)"]

1. 列出 Bucket

// S3 系(@aws-sdk/client-s3)
const { Buckets } = await client.send(new ListBucketsCommand({}));
  • 该操作需要"列出所有桶"的权限 —— R2 的 API Token 需 Admin Read & Write, Object 级 Token 会 403(见 供应商对比
  • OSS 的 listBuckets 需传 {}(传 null 会崩,SDK 实现细节)

2. 确保 Bucket 存在(不存在则创建)

通用模式:先探测(HeadBucket / getBucket),404 表示不存在 → 创建。

// S3 系:HeadBucket 404 则 CreateBucket
try {
  await client.send(new HeadBucketCommand({ Bucket }));
  // 已存在,直接使用
} catch (err) {
  if (is404(err)) await client.send(new CreateBucketCommand({ Bucket }));
}
  • 创建后 bucket 默认私有,即可直接使用
  • 探测失败要区分 404(真的不存在) 与其他错误(网络 / 鉴权)—— 后者 不能当"不存在"处理,否则会掩盖真实故障
flowchart TD
  A["探测:HeadBucket / getBucket"] --> C{"Bucket 存在?"}
  C -- "是" --> U["直接使用
(预先存在的桶:结束后不删除)"] C -- "否(404)" --> E["CreateBucket 创建
(默认私有)"] E --> U C -- "其他错误
(网络 / 鉴权)" --> X["抛出异常
不能当作不存在"]

3. 列出对象(按前缀)

// S3 系
const { Contents } = await client.send(
  new ListObjectsV2Command({ Bucket, Prefix: "demo/", MaxKeys: 20 }),
);
  • Prefix 用于过滤"目录";MaxKeys / limit 控制数量
  • 结果可能分页:S3 的 NextContinuationToken、OSS 的 nextMarker
  • 列表返回对象的 key 与大小(size),可用来实现"目录浏览"

4. 上传(Put)

// S3 系
await client.send(new PutObjectCommand({
  Bucket, Key, Body: Buffer.from(content), ContentType: "text/plain",
}));
  • 同 key 重复上传 = 覆盖(S3 系无版本控制时);Supabase 需 upsert: true 才能覆盖,否则报 Duplicate
  • 建议显式设置 Content-Type,否则对象可能被存为默认类型

5. 下载(Get)

// S3 系
const { Body } = await client.send(new GetObjectCommand({ Bucket, Key }));
const text = await Body.transformToString();
  • 小对象可直接读入内存;大对象应流式读取

6. 签名 URL(限时下载 / 客户端直传)

  • 下载 URL(GET):getSignedUrl(client, new GetObjectCommand({...}), { expiresIn: 60 })
  • 上传 URL(PUT):同上但用 PutObjectCommand
  • 用途:前端直传 / 直下,数据不经过后端;原理与坑见 签名 URL(Signed URL)

两种数据流对比:后端代理(SDK 直传)与客户端直传(签名 URL)

flowchart LR
  subgraph M1["方式一:后端代理(SDK 直传)"]
    direction TB
    A1["后端服务(持凭证)"] -->|"SDK:PutObject / GetObject"| S1["对象存储"]
  end
  subgraph M2["方式二:客户端直传(签名 URL)"]
    direction TB
    B2["后端(持凭证)"] -.->|"签发签名 URL"| A2["前端(无凭证)"]
    A2 -->|"PUT / GET 签名 URL"| S2["对象存储"]
  end

7. 删除

await client.send(new DeleteObjectCommand({ Bucket, Key }));  // 删对象
await client.send(new DeleteBucketCommand({ Bucket }));       // 删桶(须为空)
  • 删除是幂等的:删不存在的 key 也成功(S3 系;OSS 也返回 204)
  • 非空桶删除失败:先删对象再删桶;清理逻辑应 best-effort(失败报告但不致命)
  • 清理原则:只删自己创建的资源 —— 预先存在的 bucket 只写自己的前缀, 运行结束后不删除该桶

通用机制与约定

  • 前缀即目录:用带 / 结尾的前缀组织对象(如 demo/r2-client/);列对象 + 前缀过滤 = 目录浏览;"删除目录" = 删该前缀下所有对象
  • 默认私有:bucket 默认不可匿名访问;公开访问要么设公开桶,要么用签名 URL
  • 凭证不进代码:凭据放环境变量(.env),git-ignore 掉 .env*,只提交 .env.example 模板
  • 浏览器直传需要 CORS:浏览器端直传 / 直读签名 URL 时,还要给 bucket 配置 CORS(允许的来源、方法、响应头)—— 这是独立于签名本身的配置
  • SDK 公共配置项(S3 系常见):
  • endpoint:可覆盖为本地测试服务(如 MinIO)
  • forcePathStyle:本地 MinIO 需要(localhost 无虚拟主机 DNS),真实云服务不需要
  • region:R2 忽略(用 auto)、MinIO 要 us-east-1
  • checksum 开关:新版 SDK 默认给 PutObject 加 CRC32 头,设 requestChecksumCalculation: "WHEN_REQUIRED" 可保持上传纯净、兼容所有 S3 兼容端点

参考

Backlinks (5)
flowchart LR
  n0["AI"]
  n1["Database"]
  n2["Dev Tools"]
  n3["Emoji"]
  n4["Frontend"]
  n5["Game Dev"]
  n6["Collection"]
  n7["Languages"]
  n8["Maps"]
  n9["Media"]
  n10["Monitor"]
  n11["Plans"]
  n12["Knowledge"]
  n13["Cloud"]
  n14["对象存储基本用法(Bucket / Object / 常用操作)"]
  n15["对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"]
  n16["Object Storage"]
  n17["挂载 Bucket 为本地文件系统(FUSE Mount)"]
  n18["对象存储签名 URL(Signed URL)原理与实战"]
  n19["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
  n20["Infrastructure"]
  n21["Prototypes"]
  n22["Research"]
  n23["Better Auth 源码阅读指南"]
  n24["DuckDB 环境与基本使用"]
  n25["DuckDB 实战研究"]
  n26["DuckDB 模拟数据"]
  n27["PostgreSQL 数据用 DuckDB 加速查询"]
  n28["Hollow Knight 英语主题"]
  n29["英语学习 Dashboard"]
  n30["English Scraps Archive"]
  n31["English Scraps 使用指南"]
  n32["Jellyfin 源码阅读指南"]
  n33["Lux 资料整理"]
  n34["Nest Commander 学习资料"]
  n35["NestJS 源码阅读指南"]
  n36["Protomaps 自建底图研究"]
  n37["自制 PMTiles 地图(最简单例子)"]
  n38["MapLibre 集成 Protomaps"]
  n39["PMTiles 格式与工具链"]
  n40["上海地区底图项目"]
  n41["Redash 源码阅读指南"]
  n42["Rust 学习计划"]
  n43["shadcn/ui 源码阅读指南"]
  n44["TRIP 项目核心原理与代码阅读指南"]
  n1 --> n25
  n6 --> n0
  n6 --> n1
  n6 --> n2
  n6 --> n3
  n6 --> n4
  n6 --> n5
  n6 --> n7
  n6 --> n8
  n6 --> n9
  n6 --> n10
  n6 --> n11
  n6 --> n22
  n12 --> n20
  n13 --> n16
  n14 --> n15
  n14 --> n18
  n14 --> n19
  n14 --> n21
  n15 --> n14
  n15 --> n17
  n15 --> n18
  n15 --> n19
  n16 --> n14
  n16 --> n15
  n16 --> n17
  n16 --> n18
  n16 --> n19
  n17 --> n14
  n17 --> n18
  n17 --> n19
  n18 --> n21
  n19 --> n14
  n19 --> n15
  n19 --> n18
  n19 --> n21
  n20 --> n13
  n21 --> n14
  n21 --> n17
  n21 --> n18
  n21 --> n19
  n21 --> n36
  n22 --> n23
  n22 --> n25
  n22 --> n29
  n22 --> n32
  n22 --> n33
  n22 --> n34
  n22 --> n35
  n22 --> n36
  n22 --> n41
  n22 --> n42
  n22 --> n43
  n22 --> n44
  n24 --> n26
  n25 --> n1
  n25 --> n22
  n25 --> n24
  n25 --> n26
  n25 --> n27
  n26 --> n24
  n26 --> n27
  n27 --> n25
  n27 --> n26
  n28 --> n31
  n29 --> n28
  n29 --> n30
  n29 --> n31
  n31 --> n29
  n31 --> n30
  n36 --> n8
  n36 --> n37
  n36 --> n38
  n36 --> n39
  n36 --> n40
  n37 --> n39
  n38 --> n21
  n38 --> n40
  n39 --> n37
  n39 --> n40
  n40 --> n38
  n40 --> n39
  click n0 "../../../../../collection/ai/" "AI"
  click n1 "../../../../../collection/database/" "Database"
  click n2 "../../../../../collection/dev-tools/" "Dev Tools"
  click n3 "../../../../../collection/emoji/" "Emoji"
  click n4 "../../../../../collection/frontend/" "Frontend"
  click n5 "../../../../../collection/game-dev/" "Game Dev"
  click n6 "../../../../../collection/" "Collection"
  click n7 "../../../../../collection/languages/" "Languages"
  click n8 "../../../../../collection/maps/" "Maps"
  click n9 "../../../../../collection/media/" "Media"
  click n10 "../../../../../collection/monitor/" "Monitor"
  click n11 "../../../../../collection/scraps/plans/" "Plans"
  click n12 "../../../../" "Knowledge"
  click n13 "../../" "Cloud"
  click n14 "./" "对象存储基本用法(Bucket / Object / 常用操作)"
  click n15 "../data-migration/" "对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"
  click n16 "../" "Object Storage"
  click n17 "../mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
  click n18 "../signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
  click n19 "../vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
  click n20 "../../../" "Infrastructure"
  click n21 "../../../../../prototypes/" "Prototypes"
  click n22 "../../../../../research/" "Research"
  click n23 "../../../../../research/topics/better-auth/" "Better Auth 源码阅读指南"
  click n24 "../../../../../research/topics/duckdb/basic-usage/" "DuckDB 环境与基本使用"
  click n25 "../../../../../research/topics/duckdb/" "DuckDB 实战研究"
  click n26 "../../../../../research/topics/duckdb/mock-data/" "DuckDB 模拟数据"
  click n27 "../../../../../research/topics/duckdb/postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
  click n28 "../../../../../research/topics/english/hollow-knight/" "Hollow Knight 英语主题"
  click n29 "../../../../../research/topics/english/" "英语学习 Dashboard"
  click n30 "../../../../../research/topics/english/scraps/archive/" "English Scraps Archive"
  click n31 "../../../../../research/topics/english/scraps/" "English Scraps 使用指南"
  click n32 "../../../../../research/topics/jellyfin/" "Jellyfin 源码阅读指南"
  click n33 "../../../../../research/topics/lux/" "Lux 资料整理"
  click n34 "../../../../../research/topics/nest-commander/" "Nest Commander 学习资料"
  click n35 "../../../../../research/topics/nestjs/" "NestJS 源码阅读指南"
  click n36 "../../../../../research/topics/protomaps/" "Protomaps 自建底图研究"
  click n37 "../../../../../research/topics/protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
  click n38 "../../../../../research/topics/protomaps/maplibre/" "MapLibre 集成 Protomaps"
  click n39 "../../../../../research/topics/protomaps/pmtiles/" "PMTiles 格式与工具链"
  click n40 "../../../../../research/topics/protomaps/shanghai-map/" "上海地区底图项目"
  click n41 "../../../../../research/topics/redash/" "Redash 源码阅读指南"
  click n42 "../../../../../research/topics/rust/" "Rust 学习计划"
  click n43 "../../../../../research/topics/shadcn-ui/" "shadcn/ui 源码阅读指南"
  click n44 "../../../../../research/topics/trip/" "TRIP 项目核心原理与代码阅读指南"
Links (4)