OSS 对象存储:是什么、为什么、怎么用
引言
在现代 Web 应用中,用户头像、商品图片、视频文件、导出报表……这些非结构化数据如果直接放到服务器磁盘上,既占用宝贵的计算资源,又难以横向扩展。对象存储(Object Storage Service,OSS) 正是为解决这一问题而生的云存储方案。
本文以阿里云 OSS 为主线(AWS S3 API 设计高度相似,思路完全通用),系统讲解 OSS 的核心概念、前后端集成方式、权限控制和最佳实践。
核心概念
什么是对象存储
对象存储是一种以「对象(Object)」为基本单元的存储架构。每个对象由三部分组成:
| 部分 | 说明 |
|---|---|
| Data(数据) | 文件的实际二进制内容 |
| Key(键) | 对象的唯一标识符,类似文件路径,如 avatars/user-123.jpg |
| Metadata(元数据) | 描述对象的信息,如 Content-Type、自定义标签、上传时间 |
与传统文件系统(有目录树)不同,对象存储是扁平的键值空间——「目录」只是 Key 的前缀,并不真实存在。这使得它可以无限水平扩展。
核心术语
- Bucket(存储桶):对象的容器,类似一个独立的命名空间。一个账号可以创建多个 Bucket,不同业务用不同 Bucket 隔离。
- Object(对象):存储的基本单元,即一个文件 + 其元数据。
- Endpoint(访问域名):访问 OSS 的域名,分内网(ECS 内部访问免流量费)和外网两种。
- AccessKey:用于 API 鉴权的密钥对(AccessKeyId + AccessKeySecret),相当于账号密码,绝对不能泄露到前端代码或 Git 仓库。
- STS(Security Token Service):临时安全令牌服务,用于给前端发放有时效的上传凭证,是前端直传的核心机制。
- 预签名 URL(Pre-signed URL):携带签名信息的临时访问链接,可在不暴露 AccessKey 的前提下授权用户下载私有文件。
对象存储 vs 文件系统 vs 数据库 BLOB
| 维度 | 对象存储 | 文件系统(NAS) | 数据库 BLOB |
|---|---|---|---|
| 扩展性 | 无限水平扩展 | 有限,需挂载 | 受数据库性能限制 |
| 成本 | 极低(按量付费) | 中等 | 高 |
| 访问方式 | HTTP API / SDK | POSIX(文件路径) | SQL |
| CDN 集成 | 原生支持 | 复杂 | 不适合 |
| 适合场景 | 媒体、文档、备份 | 共享代码、配置 | 极小文件 |
实际应用
场景一:后端服务器上传(Node.js)
最简单的模式:前端将文件传给后端,后端转存到 OSS。适合对文件有处理需求的场景(压缩、格式转换、病毒扫描)。
npm install ali-oss
// server/oss.js
const OSS = require('ali-oss')
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: process.env.OSS_ACCESS_KEY_ID, // 从环境变量读取,绝不硬编码
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
bucket: 'my-app-bucket',
})
// 上传 Buffer 或本地文件路径
async function uploadFile(key, filePath) {
const result = await client.put(key, filePath)
console.log('上传成功:', result.url)
return result.url
}
// 上传流(适合管道处理大文件)
async function uploadStream(key, readableStream) {
const result = await client.putStream(key, readableStream)
return result.url
}
// 生成私有文件的预签名下载链接(1 小时有效)
function getSignedUrl(key) {
return client.signatureUrl(key, { expires: 3600 })
}
在 Express 中接收并转存:
const multer = require('multer')
const upload = multer({ storage: multer.memoryStorage() })
app.post('/api/upload', upload.single('file'), async (req, res) => {
const { originalname, buffer, mimetype } = req.file
const key = `uploads/${Date.now()}-${originalname}`
const result = await client.put(key, buffer, {
headers: { 'Content-Type': mimetype },
})
res.json({ url: result.url, key })
})
场景二:前端直传(推荐方案)
让文件绕过服务器直接上传到 OSS,减少服务器带宽压力。关键:由服务端签发 STS 临时凭证,而非暴露 AccessKey。
流程:
- 前端请求后端获取 STS 临时凭证
- 后端调用 STS 服务,生成带有限权限的临时 Token(有效期 15 分钟~1 小时)
- 前端用临时凭证直接上传到 OSS
// server/sts.js - 签发临时凭证
const STS = require('@alicloud/sts-sdk') // 或使用 ali-oss 内置的 STS
app.get('/api/oss-token', async (req, res) => {
const sts = new STS({
accessKeyId: process.env.OSS_ACCESS_KEY_ID,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
})
// 限制临时凭证只能上传到指定目录
const policy = {
Version: '1',
Statement: [{
Effect: 'Allow',
Action: ['oss:PutObject'],
Resource: [`acs:oss:*:*:my-app-bucket/user-uploads/${req.user.id}/*`],
}],
}
const result = await sts.assumeRole(
'acs:ram::xxxxx:role/oss-upload-role',
JSON.stringify(policy),
3600, // 有效期 1 小时
'session-name'
)
res.json({
accessKeyId: result.Credentials.AccessKeyId,
accessKeySecret: result.Credentials.AccessKeySecret,
stsToken: result.Credentials.SecurityToken,
expiration: result.Credentials.Expiration,
})
})
// client/upload.js - 前端直传
import OSS from 'ali-oss'
async function uploadToOSS(file) {
// 1. 从后端获取临时凭证
const { accessKeyId, accessKeySecret, stsToken } = await fetch('/api/oss-token').then(r => r.json())
// 2. 创建客户端(使用临时凭证)
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId,
accessKeySecret,
stsToken,
bucket: 'my-app-bucket',
})
// 3. 直传文件
const key = `user-uploads/${userId}/${Date.now()}-${file.name}`
const result = await client.put(key, file)
return result.url
}
场景三:服务端签名 + 表单直传(PostObject)
适合 H5 / 小程序场景,前端完全不引入 OSS SDK,改用原生 FormData:
// server - 生成 PostObject 签名
const crypto = require('crypto')
app.get('/api/oss-policy', (req, res) => {
const expiration = new Date(Date.now() + 60 * 60 * 1000).toISOString() // 1小时后过期
const dir = `user-uploads/${req.user.id}/`
const policy = Buffer.from(JSON.stringify({
expiration,
conditions: [
['content-length-range', 0, 10 * 1024 * 1024], // 最大 10MB
['starts-with', '$key', dir], // 只能上传到指定目录
],
})).toString('base64')
const signature = crypto
.createHmac('sha1', process.env.OSS_ACCESS_KEY_SECRET)
.update(policy)
.digest('base64')
res.json({
host: 'https://my-app-bucket.oss-cn-hangzhou.aliyuncs.com',
OSSAccessKeyId: process.env.OSS_ACCESS_KEY_ID,
policy,
signature,
dir,
})
})
// client - 表单直传(无需 SDK)
async function uploadWithPolicy(file) {
const { host, OSSAccessKeyId, policy, signature, dir } = await fetch('/api/oss-policy').then(r => r.json())
const key = `${dir}${Date.now()}-${file.name}`
const formData = new FormData()
formData.append('key', key)
formData.append('OSSAccessKeyId', OSSAccessKeyId)
formData.append('policy', policy)
formData.append('Signature', signature)
formData.append('file', file) // file 必须是最后一个字段!
await fetch(host, { method: 'POST', body: formData })
return `${host}/${key}`
}
常见误区
1. 把 AccessKey 写进前端代码
// ❌ 极度危险!AccessKey 会暴露在 JS Bundle 中
const client = new OSS({
accessKeyId: 'LTAI5xxxxx',
accessKeySecret: 'xxxxxx', // 硬编码,任何人都能从网络请求中看到
})
正确做法:使用 STS 临时凭证,或 PostObject 签名,AccessKey 永远只留在服务端。
2. Bucket 公开读 + 存储敏感文件
公开读的 Bucket 意味着任何知道 URL 的人都能访问文件。合同、身份证、医疗记录等敏感数据必须使用私有 Bucket + 预签名 URL。
3. 忽略 CORS 配置
前端直传时必须在 OSS 控制台配置 CORS 规则,允许来自你域名的跨域请求,否则浏览器会报 CORS 错误。
4. 大文件不用分片上传
超过 100MB 的文件应使用分片上传(Multipart Upload),避免网络中断导致整个上传失败:
// ali-oss 内置分片上传,自动处理断点续传
const result = await client.multipartUpload(key, file, {
progress: (percent) => console.log(`上传进度: ${(percent * 100).toFixed(1)}%`),
partSize: 5 * 1024 * 1024, // 每片 5MB(最小值)
})
企业实践与业界方案
阿里云 OSS 在电商场景的典型架构
业界常见做法:
用户端
│
├─ 获取 STS 凭证 ──► 应用服务器(生成临时凭证)
│
├─ 直传文件 ──────► OSS Bucket(私有读)
│ │
└─ 访问资源 ──────► CDN(绑定自定义域名)──► 回源 OSS
CDN 缓存命中率高,OSS 流量费用大幅降低,同时用户访问延迟也更低。图片还可以通过 OSS 图片处理(Image Processing)URL 参数实现实时裁剪、缩放、格式转换(如 WebP):
https://example.com/product/img.jpg?x-oss-process=image/resize,w_800/format,webp
AWS S3 + CloudFront 方案
AWS 的对应方案是 S3(存储)+ CloudFront(CDN)。S3 的 API 设计是业界事实标准,阿里云 OSS、MinIO、Cloudflare R2 等都兼容 S3 协议,迁移成本极低。
私有化部署:MinIO
业界常见做法:不方便使用公有云时(金融、政务行业),用 MinIO 在私有机房搭建兼容 S3 协议的对象存储服务,代码几乎不需要修改即可切换。
关联知识
前置知识:
- HTTP 协议基础(PUT / POST 请求、Content-Type、CORS)
- HMAC-SHA1 签名原理(理解预签名 URL 的安全机制)
- IAM / RAM 权限控制模型(理解 STS 临时凭证)
延伸知识:
- OSS 生命周期规则(自动归档/删除过期文件,降低存储成本)
- OSS 事件通知(文件上传后触发 Serverless 函数进行图片处理)
- CDN 缓存策略与 Cache-Control Header 的配合
- AWS S3 Transfer Acceleration(加速全球上传)
- Cloudflare R2(零出口流量费,S3 兼容)
参考文档:
实践建议
-
本地用 MinIO 替代云 OSS 做开发调试,避免产生费用,且与生产环境 API 完全一致:
docker run -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=minioadmin \ minio/minio server /data --console-address ":9001"访问
http://localhost:9001可进入 MinIO 控制台创建 Bucket。 -
用环境变量管理密钥,配合
.env文件(加入.gitignore),永远不硬编码:# .env OSS_ACCESS_KEY_ID=LTAI5xxxxx OSS_ACCESS_KEY_SECRET=xxxxx OSS_BUCKET=my-app-bucket OSS_REGION=oss-cn-hangzhou -
推荐练习:实现一个头像上传功能,包含:服务端签发 STS 凭证 → 前端直传 → CDN URL 回显,完整走通整个链路。
总结
OSS(对象存储)是现代应用存储非结构化文件的标准方案,核心是以 HTTP API 访问「Bucket + Object」的键值空间;安全核心是永远不把 AccessKey 暴露给客户端,而是通过 STS 临时凭证或 PostObject 签名实现前端安全直传;工程核心是将 OSS 与 CDN 结合,用图片处理、生命周期规则降低成本、提升性能。
