返回博客列表
云存储CDN架构设计

对象存储直传与 CDN 混合架构实战

浏览器直传对象存储、相对路径存储策略与 CDN 图片处理的完整方案

作者: ekent·发布于 2026年1月24日

文件上传是 Web 应用的基础功能,但很多团队的实现方式是:前端上传到后端服务器 → 后端再转存到对象存储。这种方案简单直接,但文件流量全部经过后端,带宽成本高、服务器压力大。我们在实际项目中采用了浏览器直传 + CDN 的混合架构,大幅降低了服务端负担。

传统方案的问题

用户浏览器 → 后端服务器 → 对象存储(OSS/COS)

一个 10MB 的图片要经过后端中转,意味着:

  • 后端需要处理 10MB 的请求体,占用内存和带宽
  • 上传速度受限于「用户→后端」和「后端→OSS」两段中较慢的那段
  • 并发上传时后端可能成为瓶颈

浏览器直传方案

核心思路是让浏览器直接上传到对象存储,后端只负责签发临时凭证:

用户浏览器 → 对象存储(直传)
     ↑
后端签发 STS 临时凭证

后端:签发临时凭证

以腾讯云 COS 为例,后端通过 STS(Security Token Service)生成有限制的临时密钥:

// API: GET /api/upload/credentials
export async function GET() {
  const credentials = await getStsCredential({
    secretId: process.env.COS_SECRET_ID,
    secretKey: process.env.COS_SECRET_KEY,
    durationSeconds: 1800, // 30 分钟有效
    policy: {
      statement: [{
        effect: 'allow',
        action: ['cos:PutObject', 'cos:PostObject'],
        resource: [`qcs::cos:${region}:uid/${appId}:${bucket}/uploads/*`],
      }],
    },
  });

  return Response.json(credentials);
}

关键安全设计:

  • 临时凭证有效期只有 30 分钟
  • 权限限定为 PutObjectPostObject,不能删除或列举
  • 资源路径限定在 /uploads/* 目录下,不能写到其他位置

前端:直传上传

前端拿到临时凭证后,直接上传到 COS:

async function uploadFile(file: File) {
  // 1. 获取临时凭证
  const creds = await fetch('/api/upload/credentials').then(r => r.json());

  // 2. 生成存储路径(按日期分目录)
  const date = new Date().toISOString().slice(0, 10);
  const key = `uploads/${date}/${generateId()}_${file.name}`;

  // 3. 直传到 COS
  await cos.putObject({
    Bucket: bucket,
    Region: region,
    Key: key,
    Body: file,
    Headers: {
      'x-cos-security-token': creds.sessionToken,
    },
  });

  // 4. 返回相对路径(不带域名)
  return key;
}

数据库存相对路径,展示时拼 CDN 域名

这是一个很容易被忽视但极其重要的设计:数据库中只存储相对路径,不存完整 URL

✅ 数据库存储: uploads/2026-01-24/abc123_photo.jpg
❌ 数据库存储: https://cdn.example.com/uploads/2026-01-24/abc123_photo.jpg

为什么?因为 CDN 域名可能会变。切换云服务商、更换 CDN 加速域名、甚至同一个存储桶绑定多个域名(国内/海外分开加速),如果数据库存了完整 URL,迁移时要批量更新所有记录。

展示时再拼接:

function getImageUrl(relativePath: string): string {
  const cdnBase = process.env.CDN_BASE_URL; // https://cdn.example.com
  return `${cdnBase}/${relativePath}`;
}

CDN 图片处理

主流云服务商的 CDN 都支持 URL 参数实时处理图片,不需要提前生成缩略图:

function getThumbnail(path: string, width: number): string {
  const base = getImageUrl(path);
  // 腾讯云万象:缩放到指定宽度,自动 WebP
  return `${base}?imageMogr2/thumbnail/${width}x/format/webp`;
}

// 阿里云 OSS 风格:
// ${base}?x-oss-process=image/resize,w_${width}/format,webp

列表页用小图(200px 宽),详情页用中图(800px),点击放大用原图。同一张图片根据场景返回不同尺寸,带宽节省非常明显。

富文本中的 URL 处理

如果项目有富文本编辑器(如文章编辑),上传的图片会以完整 URL 嵌入 HTML 中。保存时需要将完整 URL 转回相对路径,读取时再转回完整 URL:

// 保存前:完整 URL → 相对路径
function normalizeContent(html: string): string {
  return html.replace(
    new RegExp(`${CDN_BASE_URL}/`, 'g'),
    ''
  );
}

// 读取后:相对路径 → 完整 URL
function renderContent(html: string): string {
  return html.replace(
    /(src=["'])(uploads\/)/g,
    `$1${CDN_BASE_URL}/$2`
  );
}

总结

浏览器直传 + CDN 的架构核心是三个分离:上传流量不经后端、存储路径不含域名、图片处理交给 CDN。这套方案在我们的项目中将文件上传相关的服务器带宽降低了 90% 以上,同时图片加载速度因为 CDN 缓存和就近访问提升了 3-5 倍。对于任何有文件上传需求的 Web 应用,都值得采用这种架构。