腾讯云 COS + EdgeOne 静态资源分发实践

自建社区类项目积累了一批用户上传的静态资源(图片、皮肤文件、安装包),这套”COS 存储 + EdgeOne 分发”的组合用了很久,把架构决策和踩坑记录下来。

核心架构:上传下载分域名

COS 上传与 EdgeOne CDN 下载链路:cos.scwk.net 负责写入,cdn.scwk.net 负责边缘缓存读取,含缓存命中与未命中两条路径

1
2
客户端上传 ──→ cos.example.com(COS 默认源站域名)──→ 存储桶
客户端下载 ──→ cdn.example.com(EdgeOne CDN)──→ 回源 COS ──→ 用户

上传和下载走不同域名,这不是闲得无聊,是三个现实原因:

  1. 流量计费分离:CDN 的流量是单独计费的,上传如果挤占 CDN 回源通道,既慢又可能产生不必要的费用;
  2. 上传接口可以单独做鉴权:上传域名只开放 PUT/POST 和必要的预检,不挂公开缓存策略;
  3. 下载域名可以挂满缓存和防盗链:公开读、长缓存、Referer 白名单,两者互不干扰。

存储桶基础配置

创建 bucket 后第一件事不是传文件,是把这几个开关设好:

  • 权限:公有读私有写。下载域名要能匿名读,写入只允许带签名的请求或服务端中转;
  • 版本控制:开。用户上传的文件被覆盖时能找回,成本极低;
  • 生命周期:90 天前的低访问量文件转低频存储,配合版本数限制(保留最近 3 个版本),防止版本堆积把存储费用拉爆。

文件类型校验:扩展名会骗人

做皮肤文件上传时踩过的坑:文件后缀是 .scskin,但内部格式可能是 PNG,也可能是 WebP。如果只按扩展名放行,用户把任意文件改名成 .scskin 就能上传。

正确的校验姿势是读文件头(magic number):

1
2
3
4
5
6
7
8
9
10
import struct

def sniff_image_format(header: bytes) -> str | None:
if header.startswith(b'\x89PNG\r\n\x1a\n'):
return 'png'
if header[:4] == b'RIFF' and header[8:12] == b'WEBP':
return 'webp'
if header[:3] == b'\xff\xd8\xff':
return 'jpeg'
return None

策略:

  • 白名单制:只放行 image/png 和 image/webp 两种,JPEG 一律拒绝(皮肤贴图用有损格式会出现色带,社区明确不欢迎);
  • 服务端嗅探,不信客户端:Content-Type 是客户端自带的,想造假成本为零;
  • 大小上限:图片类 5MB 封顶,超限直接拒,别等上传完再删。

EdgeOne CDN 配置要点

接入 EdgeOne 后,下载域名 cdn.example.com 上配这几项:

配置值理由
缓存规则图片/静态文件 30 天内容不变则长缓存,文件名带 hash 时更省心
回源 HOSTcos.example.com指回 COS 源站
Referer 防盗链仅允许自己的站点域名防别的站贴图蹭流量
压缩Brotli + Gzip文本类资源再省一截

防盗链的代价:空 Referer 要放行(用户地址栏直开、App 内请求都可能不带 Referer),只拦”Referer 存在且不在白名单”的请求,否则容易误伤。

免费套餐的成本控制

EdgeOne 免费额度对小项目完全够用,但有两个隐性消耗点要注意:

  1. 回源流量:CDN 没命中时回源拿的流量另算,缓存规则配得合理就是省钱;
  2. 刷新/预热调用次数:文件更新后主动刷新 URL,免费额度内省着用,文件名带内容 hash 的话根本不需要主动刷新。

签名 URL:给私有资源开临时门

有些资源不能公开读(比如用户私有的皮肤),用预签名 URL:

1
2
3
4
5
6
7
8
9
import hashlib, hmac, time, base64
from urllib.parse import quote

def sign_url(key: str, secret: str, expire_seconds: int = 300) -> str:
expire = int(time.time()) + expire_seconds
sign_str = f"/{key}?expires={expire}"
digest = hmac.new(secret.encode(), sign_str.encode(), hashlib.sha1).digest()
token = base64.urlsafe_b64encode(digest).decode().rstrip('=')
return f"https://cdn.example.com/{key}?expires={expire}&token={token}"

过期时间给 5 分钟足够客户端拉起文件,又不会让链接满世界飞。CDN 层配”忽略指定参数缓存”或开启签名校验,按安全级别二选一。

小结

这套组合的关键决策就三个:上传下载分域名、文件类型服务端嗅探、长缓存 + 防盗链。免费套餐下把回源流量管住,成本可以压到接近零,性能却比传统”服务器直出文件”高一个量级。