橙宝书
端到端项目

项目九 短链接系统

用 Worker 加 KV 实现带鉴权创建、302 跳转、过期时间与点击统计的最小短链接服务。

编辑与核验:橙宝书编辑团队 ·

PROJECT 09新手约 50 分钟产出:可创建、可跳转、可统计的短链接服务

完成标准

管理员能用 Bearer Token 创建短链,支持自定义 slug 或随机 6 位 slug 和可选过期时间;访客访问短链收到 302 跳转;点击数可通过统计接口读取;非法 URL、重复 slug 与未授权请求都得到稳定错误码。

前置条件与运行随书示例

需要 Node.js 22、pnpm 和已安装的仓库依赖。示例位于 examples/url-shortener,只使用本地 KV 模拟,不触碰任何远程状态;先复制本地 Secret,再启动 Worker:

cp examples/url-shortener/.dev.vars.example examples/url-shortener/.dev.vars
pnpm wrangler dev --config examples/url-shortener/wrangler.jsonc

另一个终端运行 node --test examples/url-shortener/test/index.test.mjs,应看到 8 个测试通过。随后按示例 README 的 curl 创建短链,响应状态应为 201curl -i 访问 http://127.0.0.1:8787/<slug> 应得到 302Location 指向目标 URL。绑定与 Secret 的背景知识见 Bindings 与 Secrets

示例认证边界

ADMIN_KEY 只用于演示服务端到服务端的鉴权。公开短链服务必须换成更长、定期轮换的密钥,并只放在服务端 Secret 里,绝不能进入浏览器代码或日志。

技术方案与数据模型

一个 Worker 加一个 KV namespace(绑定名 LINKS)就够了。KV 的 key 是 slug,value 是一段 JSON:{url, clicks, createdAt, expiresAt}。过期时间直接用 KV 原生的 expirationTtl,到期键自动清除,不需要定时任务。API 面只有三个:

接口鉴权行为
POST /api/shortenBearer ADMIN_KEY创建短链,自定义 slug 或随机 6 位,可带 ttlSeconds
GET /<slug>302 跳转到目标 URL 并累加点击数
GET /api/stats/<slug>Bearer ADMIN_KEY返回目标 URL、点击数、创建与过期时间

slug 只允许 ^[a-zA-Z0-9_-]{1,64}$,目标 URL 只接受 http:https: 协议,拒绝 javascript: 等可被跳转利用的 scheme。

实现核心接口

创建路径先鉴权,再依次校验 URL、TTL 和 slug,重复 slug 返回稳定的 409 slug_taken;随机 slug 用 crypto.getRandomValues 生成,撞名时重试:

const SLUG_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;

function parseTargetUrl(value) {
  if (typeof value !== 'string') return null;
  try {
    const url = new URL(value);
    return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : null;
  } catch {
    return null;
  }
}

function randomSlug(length = 6) {
  const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  const bytes = crypto.getRandomValues(new Uint8Array(length));
  return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join('');
}

跳转路径读出 JSON、clicks 加一后写回,再返回 302。写回时按 expiresAt 重算剩余 expirationTtl,避免一次点击把过期时间抹掉。注意 KV 是最终一致的:这个读改写计数在并发点击下会互相覆盖,读取也可能因边缘缓存滞后,所以点击数是近似值,只能用于趋势观察,不能用于计费。跳转响应带 cache-control: no-store,让每次跳转都经过 Worker 计数;缓存策略的完整讨论见 缓存规则第一策略

KV 还是 D1

短链是典型的读多写少场景,两种存储都可行,取舍在一致性和查询能力:

维度KVD1
读取延迟全球边缘缓存,极低单区域主库,远离区域需回源
一致性最终一致,写后全球生效最长约 60 秒强一致
点击计数读改写,并发下是近似值UPDATE ... SET clicks = clicks + 1 原子精确
过期清理原生 expirationTtl需定时任务或查询时过滤
报表查询只能按 key 读SQL 任意聚合

只要跳转快、结构简单、计数允许近似,选 KV;需要精确计数、复杂报表或关系数据,选 D1。更系统的选型方法见 存储选型

防滥用与安全边界

公开的创建接口必然被滥用:给人可用的创建入口加 Turnstile 人机校验,再用 WAF Rate Limiting 按 IP 限速,二者与本文的 ADMIN_KEY 服务端守卫是互补关系,不是替代关系。具体配置见 Turnstile 与 WAF。另外把 slug 命名空间留给系统路由(/api/ 前缀优先匹配),避免用户注册出劫持 API 路径的 slug。

成熟项目参考

短链是 Cloudflare 生态里被反复实现过的场景,动手前值得先读这些项目的源码:

  • miantiao-me/Sink(★7000+):Nuxt 3 全栈短链,KV 存链接、Analytics Engine 存访问统计,支持自定义 slug、UTM 参数、过期时间、访问密码和二维码,AGPL-3.0 许可;项目从 ccbikai/Sink 迁移而来。
  • xyTom/Url-Shorten-Worker(★1700+):单文件 Worker 加 KV,代码极小,适合完整读一遍源码;2025 年加入了验证码防滥用。
  • x-dr/short(★400+):Pages Functions 加 D1 的中文项目,正好可以和本文的 KV 方案做对照。

验证与排障

现象先检查恢复动作
401 unauthorized.dev.varsAuthorization: Bearer 头是否一致重新复制本地模板,不把值提交到仓库
400 invalid_url目标是否以 http://https:// 开头修正请求体,不支持其他协议
409 slug_takenslug 是否已存在换 slug 或先删除旧键
跳转正常但点击数不变KV 最终一致滞后或并发覆盖等待最长约 60 秒再查;精确计数迁移到 D1
过期键仍短暂可跳边缘缓存中的旧值属预期行为,统计时以过期时间过滤

远程边界与回滚

创建远程 KV namespace、写入生产 Secret、部署 Worker 都会修改 Cloudflare 状态,本教程不自动执行。代码回滚不会删除已经写入 KV 的链接数据,也不会恢复已删除的 namespace:回滚前先导出或记录关键 slug 映射,回滚后验证核心短链仍能跳转;只有在确认没有生产流量依赖后,才考虑删除或重建 namespace。

下一步:为跳转与静态资源建立缓存规则第一策略

官方来源

这篇内容帮你完成目标了吗?

内测反馈只在当前浏览器生成,不会自动上传。

本页目录