对象存储基本用法(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 派生(如 R2https://<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-s3的ListBuckets / HeadBucket / CreateBucket / ListObjectsV2 / PutObject / GetObject / DeleteObject / DeleteBucket命令;OSS 用ali-oss的listBuckets / list / put / get / delete;Supabase 用@supabase/supabase-js的storage.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
- 该操作需要"列出所有桶"的权限 —— 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 兼容端点
参考
- 相关文档:供应商对比(Vendors)、 签名 URL(Signed URL)、 数据迁移(Migration)
- 可运行示例:Prototypes 列表: ali-oss-client · r2-client · supabase-storage-client (各 README 含环境配置与 curl 验证步骤)
Backlinks (5)
- Object Storage
- 对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)
- 对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)
- 挂载 Bucket 为本地文件系统(FUSE Mount)
- Prototypes
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 项目核心原理与代码阅读指南"