缤纷云S4对象存储接入实践

2026年8月8日
滨纷云

缤纷云 S4 对象存储接入:从本地磁盘到云端的迁移实践

这篇文章记录了我把个人作品集网站的图片存储,从服务器本地磁盘迁移到缤纷云 S4 对象存储的全过程,包括架构设计、CDN 加速和高级防盗链配置。

为什么要把图片搬到云端?

迁移前,所有图片都存在服务器本地磁盘(public/uploads/):

  • 服务器磁盘有限,照片、壁纸越存越多
  • 图片直接由 Next.js 服务读取,占用服务器带宽
  • 没有 CDN 加速,全国访客加载速度差异大
  • 生产构建后还要处理 .next 与 public 的同步问题

缤纷云 S4 兼容 S3 协议,自带 CDN 加速和高级防盗链,正好解决上面所有问题。

整体架构

[浏览器] ──POST /api/upload──→ [Next 服务端] ──生成预签名 Put URL──→ [缤纷云 S4]
     │                                  │
     │                                  └──返回 presigned URL + accessPath ─→ [浏览器直传 S4]
     │
     │  GET /api/s4/xxx.jpg
     │                                  [Next 服务端]
     │       ←──302 → location: https://cdn.domain/uploads/xxx.jpg?_ts=...&_btf_tk=...
     │
     [浏览器] ──直连缤纷云 CDN────→ 高速加载

核心设计思想:上传用预签名直传,读取用服务端签名跳转。

上传:预签名 URL 直传

服务端不直接接收文件,而是生成一个预签名 PutObject URL 发给浏览器,浏览器直接 PUT 到缤纷云。这样 AK/SK 永远不会暴露在前端。

// src/lib/s4.ts
export async function generatePresignedUploadUrl(key: string, expiresIn = 3600) {
  const command = new PutObjectCommand({ Bucket: setting.bucket, Key: key });
  return getSignedUrl(getS3Client(setting), command, { expiresIn });
}

读取:CDN 高级防盗链签名

浏览器请求 /api/s4/xxx.jpg,服务端生成一个短时签名 URL,302 跳转到 CDN 加载。

export function generateSignedCdnUrl(path: string, ttl?: number) {
  const deadline = Math.floor(Date.now() / 1000) + (ttl ?? 3600);
  const rawString = `${authKey}${path}${deadline}`;
  const md5Result = crypto.createHash('md5').update(rawString).digest('hex');
  return `${cdnDomain}${path}?_btf_tk=${md5Result}&_ts=${deadline}`;
}

安全设置效果

迁移完成后,在缤纷云控制台做了一系列安全加固:

1. CDN 高级防盗链(URL 鉴权)

  • 请求必须带合法签名(_btf_tk + _ts)
  • 无签名 → 403
  • 签名过期 → 403
  • 伪造签名 → 403

效果:即使有人拿到 CDN 直链,过一会儿就失效,无法长期盗用。

2. Referer 白名单

只允许本站域名引用图片:

*.luckymeet.vip
localhost
  • 本站页面加载 → ✅ 放行
  • 外部网站盗链 <img src="CDN URL"> → ❌ 拒绝

这个能挡住 99% 的随意盗链。注意:Referer 可以伪造,防君子不防小人,但普通用户浏览器不会伪造。

3. User-Agent 黑名单

封禁常见的爬虫和脚本 UA:

curl*
python*
Go-http-client*
wget*
java*
axios*
node-fetch*
okhttp*
scrapy*

4. 空 Referer / 空 User-Agent 不允许访问

正常浏览器请求一定有 Referer 和 UA,空的都是脚本或异常请求,直接拒绝。

几个容易混淆的概念

/api/s4/ 和桶里的 uploads/ 是两回事

  • s4 是 API 路由名,不是桶里的文件夹
  • 桶里的 key 由 generateUniqueKey() 决定,当前是 uploads/xxx.jpg
  • API 路由把 s4/xxx.jpg 映射到桶里的 uploads/xxx.jpg

别人访问我的 localhost 无效

别人写 <img src="http://localhost:3000/api/s4/xxx.jpg">,访问的是他电脑上的 localhost,不是我的服务器,图片自然显示不出来。

CDN 的图片本就不需要 CORS

只要用 <img> 标签加载图片,就不需要配置 CORS。只有 JS 用 fetch 读取图片做 Canvas 处理时才需要。

总结

需求 方案
AK/SK 不暴露 预签名直传,AK/SK 只在服务端
图片加速 CDN 高速加载
防盗链 CDN 高级鉴权 + Referer 白名单 + UA 黑名单
配置热更新 管理后台修改 S4 设置,无需重启