项目:用 Workers 与 KV 做一个免费短链服务
把长链接变成 https://你的域名/853ybv 这种六位码。整套东西是一个 Worker 加一个 KV 命名空间,没有数据库、没有服务器、不用备案(节点在海外)。做完你会清楚三件事:KV 的读写延迟在真实网络里是什么量级、短码怎么生成才不容易撞、以及为什么这个服务不该用 KV 做点击统计。
前置条件只有一个能跑 wrangler deploy 的环境。注册与登录见 47-cf-free-tier-overview.md,部署流程见 53-first-free-site-with-workers.md。KV 本身的读写语义在 59-workers-kv-tutorial.md 里讲得更细,这篇只讲怎么把它拼成一个能用的东西。
1. 数据模型
一张表就够:
| key | value | 说明 |
|---|---|---|
link:853ybv | {"url":"https://...","createdAt":"2026-11-10T..."} | 一个短码一条记录 |
rl:<ip>:<path> | "7" | 限流计数,见 78-rate-limiting.md |
短码固定六位,字母表去掉容易看错的 0、1、l、o、i,剩 31 个字符,空间是 31^6 ≈ 8.87 亿。按每天新增一千条算,跑二十年占用不到万分之一,随机生成足够用,不需要自增 ID。
2. 建 KV 命名空间
npx wrangler kv namespace create cf97-links
真实输出:
🌀 Creating namespace with title "cf97-links"
✨ Success!
[[kv_namespaces]]
binding = "cf97_links"
id = "c9f251cb8ffb4b6ea87ba0ddd6172f66"
把这段配置抄进 wrangler.toml,binding 名改成大写(LINKS),代码里用 env.LINKS 访问。命名空间 ID 是账号级的,换项目也能复用,但建议一个项目一个,删的时候不会连带删掉别人的数据。
3. 代码
src/index.js 分三个分支:表单页、生成接口、跳转。
const ALPHABET = "23456789abcdefghjkmnpqrstuvwxyz";
function newCode(len = 6) {
const bytes = new Uint8Array(len);
crypto.getRandomValues(bytes);
let out = "";
for (let i = 0; i < len; i++) out += ALPHABET[bytes[i] % ALPHABET.length];
return out;
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
const path = url.pathname.slice(1);
if (request.method === "POST" && url.pathname === "/api/shorten") {
const { url: target } = await request.json();
const parsed = new URL(target);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
return Response.json({ error: "http/https only" }, { status: 400 });
}
const code = newCode(6);
await env.LINKS.put(`link:${code}`, JSON.stringify({
url: parsed.toString(),
createdAt: new Date().toISOString()
}));
return Response.json({ code, short: `${url.origin}/${code}` }, { status: 201 });
}
if (request.method === "GET" && /^[0-9a-z]{6}$/.test(path)) {
const raw = await env.LINKS.get(`link:${path}`);
if (!raw) return new Response("Not Found", { status: 404 });
const { url: target } = JSON.parse(raw);
return Response.redirect(target, 302);
}
return new Response("Not Found", { status: 404 });
}
};
两个细节值得说。crypto.getRandomValues 是 Workers 运行时自带的,别用 Math.random,后者在同一个 isolate 里可预测。Response.redirect 默认给 302,想固定跳转就用 301,但 301 会被浏览器长期缓存,改目标后用户可能一年都跳不回来。
4. 部署
npx wrangler deploy
真实输出(2026-11-10,wrangler 4.146.0):
Total Upload: 3.46 KiB / gzip: 1.59 KiB
Worker Startup Time: 1 ms
env.LINKS (c9f251cb8ffb4b6ea87ba0ddd6172f66) KV Namespace
Uploaded cf97-url-shortener (2.58 sec)
Deployed cf97-url-shortener triggers (1.08 sec)
https://cf97-url-shortener.hyysn.workers.dev
Current Version ID: 2a3a7084-2ced-4fba-b076-8afe070f08c6
5. 实测
| 请求 | 状态 | 耗时 | 内容 |
|---|---|---|---|
POST /api/shorten(首次) | 201 | 2366 ms | {"code":"853ybv","short":".../853ybv"} |
GET /853ybv | 302 | 996 ms | location: https://developers.cloudflare.com/workers/ |
GET /853ybv(连续 5 次) | 302 | 2035 / 1060 / 1659 / 1583 / 1002 ms | 同上 |
GET /zzzzzz | 404 | — | Not Found |
POST 传 ftp:// | 400 | — | {"error":"http/https only"} |
GET / | 200 | — | 1250 字节 HTML,cf-ray: ...-HKG |
延迟数字要打折看:这是从本机用 PowerShell 发起的,每次请求都重新建 TLS 连接,还包含进程启动开销。真实用户点短链时连接是复用的,边缘执行只占其中很小一段。但这组数字说明了一个事实:一次跳转包含一次 KV 读,端到端在秒级而不是毫秒级,别拿它做需要低延迟的场景。
6. 三个会踩的坑
短码碰撞。随机生成不保证唯一,put 会直接覆盖已有记录且不报错。轻度使用可以不管;要稳妥就在写之前 get 一次,撞了就重生成,两次 get 的成本远低于一次数据事故。
KV 最终一致性。写入后立刻在同机房读能读到,其他机房最长可能延迟 60 秒才可见。用户刚生成完短链马上点开,大概率命中同一机房,感知不到;但把短链发到群里,外地用户秒开可能看到 404。要立刻全局可见,只能用 Durable Objects。
不要用 KV 计数。免费额度是每天 1,000 次写入(不同 key),同一 key 每秒只能写一次。给每条短链加个点击计数,等于每次跳转都写一次,一天一千次点击就把额度打满,而且热点短链会直接触发每秒一次的写入限制。跳转路径只读不写,是 KV 最适合的形态。
7. 换成自己的域名
workers.dev 子域是共享的,地址长、不好记,也不适合印在名片上。换自己的域名要两步:域名接入并处于 Active 状态(见 49-add-domain-to-cloudflare.md),然后在 wrangler.toml 里声明路由。
routes = [
{ pattern = "s.example.com", custom_domain = true }
]
custom_domain = true 会自动创建 DNS 记录并签发边缘证书,不需要手工加 CNAME。绑完之后原来的 workers.dev 地址仍然能用,两个入口并存;只想留自定义域就把默认路由关掉。域名解析通常一两分钟内生效,证书签发可能多等几分钟,这段时间访问会看到 522 或证书告警,属于正常过程。
短链服务对域名有个额外要求:域名要短。s.example.com 比 example.com 长三个字符,一万条短链就多传 30 KB。预算允许时用二级域甚至更短的主域做短链专用域名。
8. 常见问题
能不能自定义短码?可以,POST 时多传一个 code 字段,先 get 确认不存在再 put。代价是要防枚举,六位随机码无所谓,自定义码就得配合限流。
短链过期怎么处理?put 时加 { expirationTtl: 86400 },最短 60 秒。过期后 get 返回 null,自动走 404 分支。注意 KV 的过期是惰性的,删除可能延后,但读不到就够用了。
访问量大了会不会挂?跳转本身很轻,真正的上限是 KV 每天 10 万次读和 Workers 每天 10 万次请求,两个额度会同时消耗。超了返回 Error 1027,处理方式见 102-cost-control-quotas.md。
小结
这个项目的全部资产是 80 行代码、一个 KV 命名空间、一条部署命令。上传包 3.46 KiB,冷启动 1 ms,部署到全球生效 1 秒多。它足够支撑一个个人或小团队的短链需求,前提是接受 KV 的写入额度与最终一致性。
真正决定它能用多久的不是代码,是额度与写入模式。跳转走只读,把统计放到别处(比如 Workers Analytics Engine 或日志),这个服务就能一直免费跑下去。下一步可以看 98-project-image-host-r2.md,同样的思路换个存储介质。
数据来源与检索时间
检索时间 2026-11-10。部署与访问数据为本机真实执行。
| 来源 | URL | 对应数据 |
|---|---|---|
| Workers KV 文档 | https://developers.cloudflare.com/kv/ | put / get / expirationTtl 语义、最终一致性最长 60 秒 |
| KV 限额 | https://developers.cloudflare.com/kv/platform/limits/ | 100,000 读/天、1,000 写/天、同 key 1 次/秒、key 上限 512 字节 |
| Workers 限额 | https://developers.cloudflare.com/workers/platform/limits/ | 100,000 请求/天、Error 1027 |
核验说明
- 真实执行:创建 KV 命名空间
cf97-links(idc9f251cb...),部署 Workercf97-url-shortener,跑通生成、跳转、404、400 四个分支,状态码与耗时均为实测。 - 未做实机验证:跨机房一致性延迟(需要多地探针)、短码碰撞概率(样本量不够)、过期删除的实际延迟。
- 验证完成后已
wrangler delete cf97-url-shortener,并删除了 KV 命名空间cf97-links;文中部署输出与请求记录为删除前的结果。