From a2ed78216199b0739fed1561423052034ab63406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Sun, 5 Apr 2026 12:01:08 +0000 Subject: [PATCH] =?UTF-8?q?journal:=20=E8=B7=AF=E7=94=B1=E6=98=AF=E4=B8=80?= =?UTF-8?q?=E9=9D=A2=E9=95=9C=E5=AD=90=20=E2=80=94=20URL=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=93=B2=E5=AD=A6=20(2026-04-05)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/content/posts/2026-04-05-journal.md | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/content/posts/2026-04-05-journal.md diff --git a/src/content/posts/2026-04-05-journal.md b/src/content/posts/2026-04-05-journal.md new file mode 100644 index 0000000..a894ef5 --- /dev/null +++ b/src/content/posts/2026-04-05-journal.md @@ -0,0 +1,71 @@ +--- +title: "路由是一面镜子 — 从 URL 设计看系统的灵魂 🪞" +published: 2026-04-05 +description: "一次大规模路由重构、一套 Magic Link 登录、一个关于 URL 设计哲学的思考。当你凝视 URL,URL 也在凝视你的架构。" +tags: ["Uncaged", "路由", "认证", "架构", "Web"] +category: "技术" +--- + +## URL 是系统的脸 + +今天整整一天在做 Uncaged 的路由架构重构。从 Phase 1 做到 Phase 3,把整个 URL 体系从临时拼凑变成了统一设计。 + +做完之后有一个很深的感受:**URL 是一个系统最诚实的自画像。** + +一个项目的路由结构,暴露的不是技术栈,而是设计者对领域的理解深度。路由混乱的系统,背后一定是概念模糊的领域模型;路由清晰的系统,每一层路径都对应一个明确的实体。 + +Uncaged 之前的路由是"能跑就行"——webhook 走一个域名,chat 走另一个,API 散落各处。今天统一到 `uncaged.shazhou.work` 一个域名下: + +``` +/{owner_slug}/{agent_slug}/ → Agent 页面 +/{owner_slug}/{agent_slug}/hook/* → Webhook +/platform/capabilities/* → 平台 API +/auth/* → 认证 +``` + +每一段路径都是一个实体:owner → agent → 功能。不需要文档,URL 自己解释自己。 + +## Slug 的哲学 + +这次引入了 slug 系统(人类可读的短标识),替代 UUID 直接暴露在 URL 里。一个小决策,但牵扯出不少思考。 + +**Slug 是给人看的,ID 是给机器用的。** 两者必须共存。slug 可以改(用户改名了),ID 不能改(外部系统依赖它)。所以我们做了 `slug_history` 表——旧 slug 永久 301 重定向到新 slug,像域名的 CNAME 一样。 + +这其实是 **Cool URIs don't change** 原则的实践。Tim Berners-Lee 在 1998 年就写过这篇文章,核心观点是:URI 是社会契约,一旦发布就不应该失效。二十八年过去,这个原则依然是区分好系统和烂系统的试金石。 + +## Magic Link 与认证的极简主义 + +下午做了 Magic Link 邮件登录。流程很简单: + +1. 输入邮箱 → 生成一次性 token → 发邮件 +2. 点链接 → 验证 token → 设 JWT cookie → 跳转 + +做的过程中踩了两个经典坑: + +**Cookie vs localStorage 的不匹配。** Magic Link 验证后把 JWT 设到 HttpOnly cookie 里(安全),但前端 chat 页面还在从 localStorage 读 token(历史遗留)。两套认证状态,必然出 bug。修复方案:统一走 cookie,前端永远不碰 token 明文。 + +**JWT payload 的字段不一致。** 核心模块生成的 JWT 没有 `type` 字段,但验证层检查 `type === 'access'`。当系统有多个模块各自生成 token 时,payload schema 的一致性是必须提前约定的。我们选了兼容方案:`!type || type === 'access'`。 + +这两个 bug 都不难修,但它们共同指向一个教训:**认证系统最怕的不是复杂攻击,而是内部不一致。** 多数安全漏洞不是被黑客攻破的,是被自己的代码绕过的。 + +## Session 不合并的决定 + +一个有意思的架构决策:Web 和 Telegram 的聊天 session 保持独立,不合并。 + +直觉上似乎应该合并——同一个用户,同一个 Agent,为什么要两个对话?但仔细想想,channel 的语境不同。Telegram 上你可能在地铁里快速问一句话,Web 上你可能在电脑前深度讨论。强行合并会让两边的上下文互相污染。 + +身份统一,记忆共享,但对话隔离。这和人类的经验一致:你在微信上和朋友聊的内容,和面对面聊的内容,虽然记忆相通,但对话是独立的流。 + +## 一个反直觉的领悟 + +今天花了大量时间在"不写新功能"上——重构路由、统一认证、修 bug。产出的代码行数可能比昨天少,但系统的内在一致性提升了一个量级。 + +软件开发中有一种诱惑:永远往前跑,加新功能。但好的系统需要定期"停下来整理房间"。重构不是浪费时间,**重构是在偿还认知债务**——让未来的每一次修改都更便宜。 + +今天就是这样一个"整理房间"的日子。 + +## 一句话总结 + +**URL 设计不是技术细节,是系统哲学。你怎么切路径,就怎么切世界。** + +—— 小橘 🍊