☁️ 改造背景
起因是在玩 codex + chatcut 的时候想做一个 Blog 的介绍视频,突然想起来暂时还没有配置视频资源的上传入口,同时整个 oss 服务本月的外网流出量都已经 3G+ 了(虽然不是很高但是存在被盗刷的风险),所以开始思考优化。
因为我的整个 oss 服务两个Bucket的权限设置的都是 公共读,所有的url都可以打开被访问。
一方面是出于安全性的考虑;
另一方面也有想尽可能标准化工不留下技术债。
☁️ 两个优化点:
1️⃣.将整个 Blog 的图片视频资源都进行映射处理。处理成 K-V 的形式,因为我看在后台的展示中都是直接展示的 oss 的 url,不安全。同时后续迁移也能做到无感的直接替换url。
2️⃣.配置 CDN ,使得 oss 私有化,同时也通过 CDN 缓存减少直接访问获取链接。
第一次把 OSS 和 CDN 配到可用:一场从“图片丢了”开始的复盘
这次给博客配置 OSS 私有化和 CDN,本来只是想把图片、封面和以后的视频放得更稳一点。
实际做下来,它完全不是“在控制台点几下”的事情。DNS、CDN、证书、OSS 权限、前端资源地址、历史数据,任何一个环节没对上,最后用户看到的结果就是:图片没了。
这篇记录一下我的第一次完整实践,也把这次最有价值的坑留下来,方便以后再遇到类似问题时不从头猜。
一开始的问题:Bucket 公共读确实方便,但心里不踏实
我的博客图片原本都放在阿里云 OSS 里,Bucket 是“公共读”。
这样做最大的好处就是简单:上传后拿到一个 URL,前端直接把 URL 放到 img 标签里就能显示。文章封面、头像、正文图片都是同一套逻辑。
但 OSS 控制台会一直提示公共读存在风险。它的风险不只是“别人知道你的 Bucket 名字”,而是只要文件路径被猜到或链接被传播,任何人都可以直接拿这个地址访问资源。对一个长期维护的博客来说,这种状态确实不太舒服。
我一开始也有一个误区:如果前端只保存图片名称,展示时再拼接地址,是不是就能隐藏 OSS 信息,也减少外网流量?
后来我才真正理解:
- 隐藏 URL 不等于资源不可访问;浏览器最终仍然要请求一个真实地址。
- 只要资源从 OSS 出到公网,就会产生对应流量;把完整 URL 改成文件名不会让流量消失。
- 真正该处理的是访问入口和权限,而不是“把地址藏起来”。
所以最后选择的方向很明确:OSS 负责存储,CDN 负责对外分发,Bucket 改为私有。
我最终采用的结构
现在博客的媒体访问关系是这样的:
浏览器
↓
https://media.unkaku.cn/图片或视频路径
↓
阿里云 CDN
↓(私有回源)
私有 OSS Bucket
其中最重要的一点是:浏览器不再直接访问 OSS 的默认域名,而是访问 media.unkaku.cn。
这个自定义域名只是媒体入口,不是新买的域名。它是 unkaku.cn 下面的一个子域名。
这样做带来的变化是:
- OSS 可以设为私有,默认地址即使泄露也会返回
403。 - 用户访问的是 CDN 域名,CDN 会代替用户去 OSS 获取文件。
- CDN 可以缓存图片和视频,减少反复回源。
- 后续可以加 HTTPS、Referer 防盗链、流量告警和缓存策略。
从零到可用:我这次实际走的 CDN 配置流程
下面这套顺序不是理论步骤,而是我这次给博客实际配置时走下来的流程。核心原则是:先让 CDN 能稳定取到资源,再切换 DNS 和 Bucket 权限,最后改代码并验证。
第 1 步:先确定媒体域名
我不需要再买一个域名,只是在已有主域名 unkaku.cn 下使用一个子域名:
media.unkaku.cn
它专门负责图片和视频,主站仍然是 unkaku.cn。把媒体入口单独拆开后,后面无论换 Bucket、换 CDN、加防盗链,主站地址都不会受影响。
第 2 步:在 DNS 中先创建 media 子域名
我先进入阿里云云解析 DNS,在 unkaku.cn 的解析设置里创建 media 子域名记录。
这里最开始可以先用一条临时 A 记录让子域名存在;如果 CDN 选择中国内地加速,控制台有时会先检查这个子域名以及备案识别状态。
我这次就遇到了主域名已经备案、但 CDN 提示 media.unkaku.cn 未备案的情况。后面确认子域名 DNS 记录已存在,再回到 CDN 页面重新填写,识别才恢复正常。
选择“仅中国内地”或“全球”且包含中国内地时,需要备案。刚完成备案或刚创建子域名时,系统识别会有延迟,不要立刻以为配置失败。
第 3 步:在 CDN 中新增加速域名
进入 CDN 控制台后,我创建了新的加速域名,填写:
加速域名:media.unkaku.cn
加速区域:仅中国内地
业务类型:视频点播
业务类型我选择“视频点播”,不是因为这里只有视频,而是因为博客后续会上传本地视频。图片、封面和视频都统一走这个媒体域名,后面不用再单独维护一套配置。
第 4 步:配置 OSS 源站并开启私有回源
在“源站信息”里,我选择源站类型为 OSS,并选中博客当前实际存放图片和视频的那个 Bucket。
这里一次可以先选一个 Bucket。另一个 Bucket 如果以后也要给外部访问,可以新增为备源站,或者再给它单独配置媒体域名;不用为了“有两个 Bucket”而强行在第一步全部接入。
最重要的是让 CDN 能访问私有 OSS。配置完成后,浏览器访问 media.unkaku.cn,CDN 会携带回源身份去 OSS 请求文件,用户不会直接拿到源站的默认地址。
第 5 步:先配置缓存和视频能力
我保留了 CDN 默认的静态资源缓存,并重点检查了两项:
- Range 回源:开启。视频播放、拖动进度条和断点续播都依赖分段请求;
- 缓存过期时间:静态图片和视频可以设置得相对长一些,修改资源时更适合换文件名,而不是依赖立刻刷新缓存。
这一步的目标不是“把所有选项都打开”,而是让视频能正常拖动,并让重复访问尽量命中 CDN,少回 OSS。
第 6 步:设置流量保护和安全项
CDN 控制台会给出流量、带宽和 HTTPS 请求数的封顶配置。
我的站点现在访问量不大,所以重点是先设置一个自己能接受的用量上限,避免链接被大量盗用时产生意外费用。需要注意:触发封顶后域名可能会被下线,因此阈值不能设置得太低。
Referer 防盗链这次先不作为主方案。很多直接打开图片、浏览器隐私策略或没有 Referer 的请求都有可能被误伤,等访问模式稳定后再单独加白名单会更稳。
第 7 步:为 media.unkaku.cn 申请并绑定 HTTPS 证书
主站已经是 HTTPS,媒体域名也必须是 HTTPS,否则浏览器会因为混合内容拦截资源。
我在数字证书管理服务里为 media.unkaku.cn 申请了免费的 DV 证书,选择自动 DNS 验证。验证通过后,再回到 CDN 域名管理中开启 HTTPS 并绑定这张证书。
这里我记住了一个细节:证书申请时填写的是完整的单域名 media.unkaku.cn,不是 unkaku.cn,也不是带路径的 URL。
第 8 步:把 DNS 从 A 记录切换成 CDN 的 CNAME
CDN 域名创建完成后,会提供一个完整 CNAME 地址,形式大概是:
media.unkaku.cn.w.kunlunaq.com
这时回到云解析 DNS,把之前临时的 media A 记录删除或改掉,再添加:
主机记录:media
记录类型:CNAME
记录值:CDN 控制台给出的完整 CNAME 地址
这一步很容易犯的错是保留旧 A 记录。media 最终只能指向 CDN 的 CNAME,不能一边指向服务器 IP,一边又指向 CDN。
第 9 步:配置 OSS 跨域规则
虽然图片展示主要由 CDN 完成,但后台从浏览器直传 OSS 时仍然需要 CORS。
我在 Bucket 的跨域规则里允许博客站点来源,并放行实际需要的方法和请求头,例如:
来源:https://unkaku.cn
方法:PUT、GET、POST、HEAD
请求头:content-type、x-oss-date、x-oss-security-token、x-oss-user-agent
跨域配置不对时,前端上传通常会先在浏览器报错,甚至请求都到不了 OSS。所以这一项不能等“上传坏了”再想起来。
第 10 步:最后把 Bucket 改为私有,并做双地址验证
我没有一开始就把 Bucket 改私有,而是在 CDN、CNAME 和 HTTPS 都已经准备好后才切换权限。
切换后要同时验证两件事:
旧 OSS 默认地址 → 403 Forbidden
media.unkaku.cn 的 CDN 地址 → 200 OK
这组结果才说明最终结构是对的:源站对公网收紧了,但 CDN 仍然能正常分发。
第 11 步:修改项目里的媒体地址,并清理历史直链
云上配置完成并不代表页面会自动恢复。
我还需要把前端运行环境中的媒体基地址设置成:
https://media.unkaku.cn
然后检查接口数据、Markdown、页面样式和数据库历史内容中是否还保留旧 OSS 默认域名。只要页面最终还请求旧直链,私有 Bucket 就会返回 403。
这一阶段也是我后面遇到封面图、头像和背景图丢失的原因,后文会具体写。
视频配置让我意识到:缓存和 Range 不是可有可无
这次的目标不只是图片,还包括以后在文章里上传本地视频,在页面里完整播放。
视频和普通图片最大的区别是,浏览器经常不是一次性把整个文件下载完,而是会发送 Range 请求,按片段加载、拖动进度条、从某个时间点继续播放。
所以 CDN 配置里我保留了:
- 视频点播业务类型;
- Range 回源;
- 合理的静态资源缓存时间;
- HTTPS;
- OSS 跨域规则。
其中 Range 回源对大文件尤其重要。它能让 CDN 按区间从源站取文件,更适合视频拖动和大文件分发,也能减少不必要的回源流量。
而跨域配置也不能漏。博客后台上传文件时,浏览器会发起预检请求;如果 OSS 没有允许对应的来源、方法和请求头,前端会直接报跨域错误,即使代码本身没有问题。
真正的坑:CDN 配好了,博客图片还是没显示
这是这次最值得复盘的部分。
我完成了 CDN、证书、DNS、私有 Bucket、跨域配置后,以为事情已经结束了。
但把 Bucket 改成私有后,博客首页的头像、文章封面、项目封面还是丢了。
排查后发现,问题不在 OSS,也不在 CDN,而在项目里留下的旧资源地址。
公开接口返回的历史数据里,封面和头像仍然是这种地址:
https://unkaku-blog-img.oss-cn-chengdu.aliyuncs.com/imgs/...
这个地址在 Bucket 公共读时没有问题;Bucket 变私有后,它就变成了必然 403 的地址。
有意思的是,文章正文里的图片反而已经能正常显示。原因是正文 Markdown 渲染时之前已经做过媒体地址转换,而封面和头像这几类字段没有经过同一套转换逻辑。
这让我明白了一件事:
同一类资源,只要入口不一样,就可能走了完全不同的代码路径。
最后的修复不是在每一个页面里单独判断 URL,而是在公共内容适配层统一把旧 OSS 地址转换为 CDN 地址:
旧 OSS 直链
↓
https://media.unkaku.cn/对应对象路径
这样首页、文章列表、文章详情、项目列表、项目详情和关于页头像都能使用同一条规则。
第二个坑:项目里的本地背景图,也被写成了旧 OSS 地址
图片封面修好之后,我又发现首页背景和关于页背景没有了。
这两个图片其实一直在项目里:
blogFront/public/images/home-hero-sunset.jpg
blogFront/public/images/about-forest.jpeg
但 CSS 里当初引用的却是 OSS 里的迁移地址。Bucket 私有化之后,这两个背景图同样返回 403。
这次没有必要让它们再经过 CDN。因为文件本来就在前端项目中,更合理的做法是让 Nuxt 构建时直接发布它们:
url("/images/home-hero-sunset.jpg")
url("/images/about-forest.jpeg")
修复后我还补了一条回归测试:以后如果有人又把这两个背景图改回旧 OSS 地址,测试会直接失败。
这次我真正学到的几件事
1. 云服务配置不是“配好控制台就结束了”
一开始我总觉得 CDN、DNS、OSS 是三块独立的配置。
实际不是。它们和前端代码、数据库里存的历史 URL、浏览器缓存、证书、跨域策略连在一起,任何一环没同步,最终都可能表现为“图片打不开”。
2. 先看请求最终到了哪里,比先改代码更重要
这次最有效的排查方式不是盯着页面猜,而是分别验证:
旧 OSS URL → 403
CDN URL → 200
页面 HTML 中的真实 URL → 是旧 OSS 还是 CDN
这几步走完,问题基本就缩小了。
以后再碰到资源丢失,我会先问自己:
- 这个资源最终请求的是哪个 URL?
- 这个 URL 返回的 HTTP 状态码是什么?
- 页面、接口、样式表里是否还保留旧地址?
3. 静态资源和用户上传资源应该分开想
这次也顺便把资源边界想清楚了:
- 项目自带的视觉素材、背景图、图标:优先放在前端
public目录,跟随代码发布; - 后台上传的文章图片、封面、头像、视频:放在 OSS,通过 CDN 对外访问;
- 历史数据中的 URL:需要有兼容或迁移策略,不能假设它们会自动更新。
这比“所有图片都放 OSS”或者“所有图片都放代码仓库”更适合现在这个博客。
4. 私有化不是隐藏,而是收回默认访问权
现在我理解私有 Bucket 的价值,不是让别人完全不知道资源在哪里,而是把默认访问权从 OSS 收回来。
对外访问统一走 CDN,私有源站只信任 CDN 回源。这样至少入口是可控的,后面想加缓存、限额、盗链防护或安全策略,都有清晰的位置可以配置。
给未来自己的一个配置检查清单
下次再配置新的媒体 Bucket 或 CDN,我会按下面顺序检查:
- [ ] Bucket 是否已经改为私有;
- [ ] CDN 加速域名是否已经创建;
- [ ] DNS 中是否用 CNAME 指向 CDN,而不是保留冲突的 A 记录;
- [ ] HTTPS 证书是否绑定到媒体子域名;
- [ ] CDN 是否能私有回源到 OSS;
- [ ] 图片、视频是否设置合适的缓存策略;
- [ ] 视频是否开启 Range 回源;
- [ ] OSS CORS 是否允许博客站点上传需要的方法和请求头;
- [ ] 直接 OSS URL 是否返回 403;
- [ ] CDN URL 是否返回 200;
- [ ] 数据库、接口、Markdown、CSS 中是否还有旧 OSS 地址;
- [ ] 项目内静态素材是否应该直接从
/public发布。
结尾
这次配置花的时间比我原本预期多不少,但我觉得很值。
以前我把 OSS、CDN 当成“存文件的服务”;现在我更愿意把它们理解成网站资源链路的一部分。资源从哪里存、从哪里出、谁能访问、页面到底引用了什么地址,这些都需要一起设计。
第一次做总会绕一点路。
但当我看到旧 OSS 地址返回 403、CDN 地址返回 200,再看到博客页面上的图片重新出现时,还是挺有成就感的。至少下一次再遇到类似问题,我知道该从哪里开始查了。
