项目概述
本项目实现 slug 与重定向服务:为内容分配稳定唯一的 slug,并把旧 slug 重定向到新 slug,返回正确状态码且不产生链与环。slug 是内容的公开身份,重命名不应打断任何指向旧名的链接。
架构与关键决策
标题经规范化生成 slug 并与保留词表比对,再以唯一约束存储,使两个请求不能占用同一 slug,冲突时取确定性后缀。重命名不更新旧 slug,而是写入一条旧到新的重定向行,使历史身份始终可解析。解析沿重定向行前进直到抵达规范 slug,同时计数跳数并在超过小上限时拒绝,使环路明确失败而非挂起。映射存库并导出快照到边缘,使解析既快又省。
实施与测试
两个相同标题断言得到不同 slug;重命名断言旧 slug 以 301 重定向;人为构造环断言在跳数上限处停止;保留词断言被拒;规范化与冲突后缀断言确定性。
限制与后续
- slug 为单段字符串,不建模层级路径。
- 无链的自动合并,多次重命名会留下链。
- 边缘快照最终一致,重命名短暂只在库侧可解析。
代码示例
pnpm init
pnpm add pg express
pnpm add -D typescript tsx vitest
slug-service/
├── src/slugify.ts
├── src/assign.ts
├── src/rename.ts
├── src/resolve.ts
├── tests/slug.test.ts
└── package.json
const slug = raw.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
while (await exists(candidate)) candidate = base + '-' + (++n);
if (RESERVED.has(slug)) throw new Error('reserved slug');
await db.updateRedirectsTo(oldSlug, newSlug);
for (let i = 0; i < 5; i++) { target = await nextRedirect(target); if (!target) return target; } return null;
pnpm vitest run