跳到正文
Virok
已成形

只存 object key,不存 URL

2 分钟

AI 摘要

内容管理系统中存 URL 而非 object key,会在更换存储商或 CDN 时引发连锁重写问题。核心方案是将 URL 生成与内容存储解耦:内容只存供应商无关的 object key,渲染时动态组装 URL,换基础设施只需改提供层一行代码。

规则一句话:内容里存 object key,URL 在渲染时生成

错误:https://pub-xxxx.r2.dev/blog/2026/09/architecture.png
正确:blog/2026/09/architecture.png

为什么

URL 里含三样会变的东西:域名、路径前缀、访问凭据形式。

  • 换存储商 → 域名变
  • 换 CDN → 域名变
  • 从公开桶改成签名访问 → 整条 URL 的结构都变

而 object key 是你自己的命名空间,跟供应商无关。它只在你重新组织内容结构时才需要变——那是你主动做的决定,不是被动挨的刀。

破规则的方式

这条规则简单到看起来不需要说,但它有几种很常见的破法:

1. 上传工具自动填了完整 URL。

后台上传完,工具很贴心地把可访问的完整链接显示在界面上,你复制粘贴进文章——破了。

2. 图床。

用第三方图床,拿到的是图床的短链。这时候你不但丢了 key,连对象都不在你手上。

3. Markdown 编辑器拖图片进去。

拖进去的瞬间,编辑器把图片上传到某个地方然后插入完整 URL。

共同点是:都是“顺手”的操作。所以我不用规则约束自己,用结构约束——上传入口只有一个,它只返回 key,压根不给你 URL。

代码上怎么保证

export interface StorageProvider {
  upload(key: string, body: ArrayBuffer, contentType: string): Promise<void>;
  delete(key: string): Promise<void>;
  /** key → 可访问 URL,这是唯一生成 URL 的地方 */
  getUrl(key: string): string;
}

业务层拿到的永远是 key。要显示时调 getUrl(key)

这样换存储商的时候,改动范围是:一个新的 Provider 实现 + 工厂函数里的一行

const storage = createStorageProvider(
  import.meta.env.STORAGE_PROVIDER ?? "local",
  { /* 凭据 */ }
);

例外

有一种情况可以直接存 URL:纯外链。比如引用别人网站的图片、嵌入外部视频。这些对象本来就不在你手上,存 key 没有意义——但也要意识到,它们可能哪天就 404 了。

对我自己的站点,做法是:外链图片在写文章时下载一份存进自己的存储,然后存 key。多花三十秒,换十年后链接还活着。

这是一条笔记,不是成稿文章。它可能不完整、可能有错,也可能过段时间就被我推翻。 想看成体系的文章,去 Blog