火山引擎对象存储 TOS 入门到上手
TOS 是什么
TOS(Volcengine Object Storage) 是火山引擎提供的对象存储服务,对标 AWS S3、阿里云 OSS、腾讯云 COS。最大特点是完全兼容 S3 协议——意味着你能用任何 S3 SDK 直接接 TOS,迁移成本几乎为零。
一句话定位:存海量非结构化数据(图片、视频、日志、备份、模型权重),按量计费、几乎无限容量、不挂本地磁盘。
- 用户上传的图 / 视频 / 文件,需要一个能直接 URL 访问的地方
- 跨服务、跨地域共享同一份数据
- 想脱开 ECS 本地盘做备份 / 归档
三个核心概念
| 概念 | 含义 | 注意点 |
|---|---|---|
| Bucket | 命名空间,全局唯一 | 名字创建后不可改名;区域不可迁移 |
| Object | 桶里的一个文件 + 它的元数据 | 没有「文件夹」概念,a/b/c.jpg 的 a/b/ 只是 key 的一部分 |
| Key | 对象在桶里的路径 | 不超过 1KB,建议用 / 分层便于前缀查询 |
桶名 = 全局命名空间。如果你在 cn-beijing 建了 my-prod,那么这个桶名在所有区域都被占用。起名时请带环境前缀,如 my-prod、my-staging、my-archive-2026。
AccessKey 三件套
TOS 用 AccessKey ID + Secret AccessKey 鉴权。永远别把这对密钥硬编码进前端。
| 凭证类型 | 用途 | 能干啥 |
|---|---|---|
| 主账号 AK | 全部权限 | 只在服务端初始化时用一次,平时禁用 |
| 子账号 AK | 受限权限 | 给业务服务用,配 IAM 策略 |
| STS 临时凭证 | 短期、动态 | 前端 / 移动端发 Presigned URL 走这个 |
任何暴露在前端代码里的 AK 都是公开密钥。前端只能拿 STS 临时凭证(默认 1 小时过期),或者直接拿后端签好的 Presigned URL。
控制台三步上手
- 火山引擎控制台 → 对象存储 TOS → 创建桶
- 选区域(离用户最近的那个)
- 选存储类型:标准 / 低频 / 归档(归档取回慢、便宜)
- 权限:私有读写(默认)/ 公有读 / 公有读写(不要用公有读写)
- 访问控制 IAM → 用户 → 创建 AccessKey
- 只授予这一个桶的读写权限(最小权限原则)
- 立即下载 AK 凭据,关闭页面就再也看不到 SecretAccessKey 了
- 桶设置 → 跨域 CORS / 防盗链 / 生命周期规则
Node.js 上传(官方 SDK)
npm install @volcengine/sdk
# 或用 AWS SDK(协议兼容):
# npm install @aws-sdk/client-s3
最小可跑上传:
import { TosClient } from "@volcengine/sdk";
const client = new TosClient({
accessKeyId: process.env.TOS_AK!,
accessKeySecret: process.env.TOS_SK!,
region: "cn-beijing",
endpoint: "tos-cn-beijing.volces.com",
});
await client.putObject({
bucket: "my-prod",
key: `uploads/${Date.now()}-${file.name}`,
body: file.stream(),
contentType: file.type,
});
经典模式:前端永远不持 SecretAccessKey。用户上传文件时:
- 前端 → 后端:
POST /api/upload/sign,带上文件名 / 大小 / 类型 - 后端 → TOS:生成 Presigned PUT URL(含签名)
- 后端 → 前端:返回 URL
- 前端 → TOS:直接
fetch(url, { method: 'PUT', body: file }),流量绕开后端、不吃后端带宽
Presigned URL(最常用的对接姿势)
Presigned URL = 把一次操作(PUT / GET / DELETE)的鉴权签名直接打到 URL 里,持 URL 的人就有权限,过期即失效。
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
// 上传 URL(5 分钟有效)
const uploadUrl = await getSignedUrl(
client,
new PutObjectCommand({ Bucket: "my-prod", Key: key, ContentType: mime }),
{ expiresIn: 300 }
);
// 下载 URL(私有文件也能直接给浏览器用)
const downloadUrl = await getSignedUrl(
client,
new GetObjectCommand({ Bucket: "my-prod", Key: key }),
{ expiresIn: 3600 }
);
| 用途 | 过期时间建议 |
|---|---|
| 上传 | 5–15 分钟 |
| 下载 | 几分钟到 24 小时,看业务 |
| 分享 | 配合防盗链 referer,别让 URL 一传十十传百 |
大文件断点续传
超过 100MB 的文件用分段上传:
- 分片大小:5MB–5GB,推荐 5–20MB
- 片数限制:最多 10000 片,超过就要放大单片
- 断点重传:客户端记录已成功片的 partNumber + ETag,下次续传直接跳过成功的片
- 失败清理:服务端用 lifecycle 规则自动清理未完成的分段上传(见下)
生命周期规则(省钱 + 自洁)
桶级规则,按前缀匹配批量处理:
| 动作 | 触发条件 | 用途 |
|---|---|---|
| 转低频 | 创建 30 天后 | 访问频率下降,自动省钱 |
| 转归档 | 创建 90 天后 | 几乎不读,归档存储最便宜 |
| 删除 | 创建 365 天后 | 日志 / 临时文件自动清 |
| 清理未完成分段 | 14 天未完成 | 防上传中断留垃圾、吃存储费 |
{
"Rules": [
{
"ID": "log-auto-clean",
"Filter": { "Prefix": "logs/" },
"Status": "Enabled",
"Expiration": { "Days": 365 }
},
{
"ID": "kill-aborted-uploads",
"Filter": { "Prefix": "" },
"Status": "Enabled",
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 14 }
}
]
}
CORS(前端直传必开)
桶的「跨域设置」加:
AllowedOrigin: https://your-site.com
AllowedMethod: PUT, GET, HEAD, POST
AllowedHeader: *
ExposeHeader: ETag, Content-Length
如果同时打开 AllowedHeader: * 和允许自定义 header,浏览器会拒绝带凭证的请求。要么用具体 origin,要么用具体 header 白名单,不要双宽。
最小权限 IAM 策略
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"tos:GetObject",
"tos:PutObject",
"tos:DeleteObject",
"tos:InitiateMultipartUpload",
"tos:ListMultipartUploadParts",
"tos:UploadPart",
"tos:CompleteMultipartUpload",
"tos:AbortMultipartUpload"
],
"Resource": ["trn:tos:::my-prod/*", "trn:tos:::my-prod"]
}
]
}
要点:
- 只授权业务实际用到的桶
- 只给操作对象的权限,不给
tos:ListAllMyBuckets - 如果只是「前端签名」,后端只需要
tos:PutObject之类;ListBuckets / DeleteBucket 这种管理类 API 不下发
防盗链
私有桶 + Presigned URL 已经能挡 99% 的盗链。公有读桶才需要 referer:
Referer 白名单: https://your-site.com
空 Referer 行为: 拒绝
Referer 是「客户自觉」的,curl / 爬虫都能伪造。防盗链靠短过期 + 签名而不是靠 Referer。
计量计费口径
| 项 | 怎么算 |
|---|---|
| 存储 | 按 GB × 天 × 存储类型单价 |
| 请求 | PUT / GET / LIST 各有单价(万次为单位) |
| 流量 | 公网下行按 GB;内网下行免费,CDN 回源另算 |
| 数据取回 | 低频 / 归档取回要额外付费 |
省钱三招:
- 同地域用内网:服务在
cn-beijing就建cn-beijing的桶,公网流量 = 钱 - 挂 CDN:TOS 直接做源站,CDN 发静态内容,回源流量便宜一个数量级
- 归档化冷数据:超过 90 天不读就丢归档
常见坑
速查表
| 场景 | 用什么 |
|---|---|
| 服务端上传 | PutObject |
| 浏览器直传 | Presigned PUT URL(5–15 分钟过期) |
| 私密文件下载 | Presigned GET URL |
| 大文件(>100MB) | 分段上传 + 断点续传 |
| 前端不能用 AK | STS 临时凭证 / Presigned URL |
| 清理垃圾 | lifecycle AbortIncompleteMultipartUpload |
| 省钱 | 冷数据转低频 / 归档 |
| 跨服务共享 | 同区域 + 内网 Endpoint |
| 静态站加速 | TOS 做源站 + CDN |
TOS 用起来像 S3,费用像水电,密钥一旦泄露像裸奔——前三件事做好了,后面就是白嫖。