项目九 短链接系统
用 Worker 加 KV 实现带鉴权创建、302 跳转、过期时间与点击统计的最小短链接服务。
编辑与核验:橙宝书编辑团队 ·
完成标准
管理员能用 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 创建短链,响应状态应为 201;curl -i 访问 http://127.0.0.1:8787/<slug> 应得到 302 且 Location 指向目标 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/shorten | Bearer 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
短链是典型的读多写少场景,两种存储都可行,取舍在一致性和查询能力:
| 维度 | KV | D1 |
|---|---|---|
| 读取延迟 | 全球边缘缓存,极低 | 单区域主库,远离区域需回源 |
| 一致性 | 最终一致,写后全球生效最长约 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.vars 与 Authorization: Bearer 头是否一致 | 重新复制本地模板,不把值提交到仓库 |
400 invalid_url | 目标是否以 http:// 或 https:// 开头 | 修正请求体,不支持其他协议 |
409 slug_taken | slug 是否已存在 | 换 slug 或先删除旧键 |
| 跳转正常但点击数不变 | KV 最终一致滞后或并发覆盖 | 等待最长约 60 秒再查;精确计数迁移到 D1 |
| 过期键仍短暂可跳 | 边缘缓存中的旧值 | 属预期行为,统计时以过期时间过滤 |
远程边界与回滚
创建远程 KV namespace、写入生产 Secret、部署 Worker 都会修改 Cloudflare 状态,本教程不自动执行。代码回滚不会删除已经写入 KV 的链接数据,也不会恢复已删除的 namespace:回滚前先导出或记录关键 slug 映射,回滚后验证核心短链仍能跳转;只有在确认没有生产流量依赖后,才考虑删除或重建 namespace。
下一步:为跳转与静态资源建立缓存规则第一策略。
官方来源
这篇内容帮你完成目标了吗?
内测反馈只在当前浏览器生成,不会自动上传。