跳到主要内容

文件存储与 OSS

问题

服务端如何处理文件上传和存储?S3/OSS 的使用方式和最佳实践是什么?

答案

文件存储方案

方案适用场景优缺点
本地磁盘开发环境简单,不适合生产
云对象存储生产环境可靠、CDN 加速、按量计费
数据库 BLOB小文件简单,不推荐大文件

云存储服务对比

服务提供商特点
S3AWS行业标准,生态最完善
OSS阿里云国内首选
COS腾讯云国内次选
R2Cloudflare无出站流量费用
Supabase StorageSupabase开源,与数据库集成

上传方式

1. 服务端中转上传

server-upload.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({ region: 'us-east-1' });

app.post('/api/upload', upload.single('file'), async (req, res) => {
const file = req.file!;
const key = `uploads/${Date.now()}-${file.originalname}`;

await s3.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: key,
Body: file.buffer,
ContentType: file.mimetype,
}));

res.json({ url: `https://cdn.example.com/${key}` });
});

2. 签名直传(推荐)

presigned-upload.ts
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

// 后端:生成预签名 URL
app.post('/api/upload/presign', async (req, res) => {
const { filename, contentType } = req.body;
const key = `uploads/${Date.now()}-${filename}`;

const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket: 'my-bucket',
Key: key,
ContentType: contentType,
}), { expiresIn: 300 }); // 5 分钟有效

res.json({ uploadUrl: url, fileUrl: `https://cdn.example.com/${key}` });
});

// 前端:直接上传到 OSS
const { uploadUrl, fileUrl } = await api.getPresignedUrl(file.name, file.type);
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
签名直传优势
  1. 减轻服务器压力:文件不经过服务器,直接传到 OSS
  2. 上传更快:少了一次中转
  3. 节省带宽:服务器不消耗带宽

常见面试问题

Q1: 服务端中转上传和签名直传怎么选?

答案

  • 签名直传(推荐):大文件、高频上传、带宽敏感场景
  • 服务端中转:需要对文件做预处理(压缩、水印)、安全要求高

Q2: 如何限制上传文件的大小和类型?

答案

三层校验:

  1. 前端input accept + File API 检查(易绕过,仅做体验优化)
  2. 服务端:Content-Type + 文件头(magic number)检测
  3. OSS 策略:Bucket Policy 限制

Q3: 文件 URL 安全怎么处理?

答案

  • 公开文件:CDN 直接访问
  • 私有文件:预签名 URL(带过期时间)
  • 敏感文件:Referer 限制 + 签名 + 短期有效

Q4: 如何实现图片上传后自动处理?

答案

  1. 上传到 OSS 后触发函数计算(如 Lambda)自动处理
  2. 使用 OSS 的图片处理参数:?x-oss-process=image/resize,w_200
  3. CDN 图片处理服务(如 Cloudflare Images)

相关链接