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