From 5b2f2c109dc8b49554be4d48db748c2752a9a863 Mon Sep 17 00:00:00 2001 From: lovife <474239041@qq.com> Date: Tue, 29 Sep 2026 21:37:54 +0800 Subject: [PATCH] =?UTF-8?q?docs(handoff):=20=E8=A1=A5=E4=B8=8A=20=C2=A7?= =?UTF-8?q?=E4=B8=80=C2=B7=E4=BA=8C=E5=8D=81=E4=BA=94=EF=BC=88=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E5=9F=9F=E5=90=8D=EF=BC=89=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E4=B8=8A=E4=B8=80=E7=89=88=E5=B9=82=E7=AD=89=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E8=A2=AB=E6=AD=A3=E6=96=87=E5=BC=95=E7=94=A8=E8=AF=AF=E5=88=A4?= =?UTF-8?q?=EF=BC=8C=E6=95=B4=E8=8A=82=E6=B2=A1=E5=86=99=E8=BF=9B=E5=8E=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版脚本用 "§一·二十五" in text 当"已存在就跳过"的判断,而 §一·二十四 的结尾 正好有「见下一节(§一·二十五)」这句话 → 误判为已存在 → 整节没落进文档。 教训:幂等判断要认**标题**(## §一·二十五),不能认"整篇里有没有这个词"; 并且写完要**自查一次**标题能否找到(这次加了)。 另:本次提交还顺带记录了一条同类教训 —— 在 PowerShell 里用 git commit -m "带中文引号的消息" 会被 shell 拆成多个 pathspec(这次就炸了)。本项目一直用消息文件,就是为此。 --- docs/交接摘要.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/交接摘要.md b/docs/交接摘要.md index c14d153..ab6568b 100644 --- a/docs/交接摘要.md +++ b/docs/交接摘要.md @@ -1999,3 +1999,64 @@ input.dispatchEvent(new Event('change', { bubbles: true })) > 但客户端提了一个**新的架构要求**(租户按域名前缀区分,如 `t1.xxx.com` / `t2.xxx.com`), > 它会同时影响 PC 登录、H5 微信授权回调、小程序域名白名单与二维码 scene —— > 见下一节(§一·二十五),那里记录了分析与待拍板的点。 + + +--- + +## §一·二十五 租户域名:用子域前缀判断"这是哪个单位的站点" + +> 客户原话:**不同的租户采用不同的地址前缀来获取租户ID,如 t1.xxx.com 和 t2.xxx.com** +> 落地提交:`d05cec0`(细节与完整论证在那条提交信息里,这里只留要点与待办) + +### 它解决什么 + +在此之前"这次是哪个单位"**都要人填或扫**:PC 登录手填租户码(跨单位重名时报 `40012` +「请填写租户码」,而用户根本不知道该填什么)、H5 与小程序靠扫码 scene。 +而域名本身就是"我是谁"最自然的答案 —— 在 `t1.xxx.com` 上打开页面时,不该再问。 + +前缀用 **`t{tenantId}`**,与扫码 scene 的 `t{tid}` 是**同一套约定**(scene 早就是 `t1-k17`)。 + +### ★ 唯一必须守住的规则:Host 定位,token 授权 + +``` +未登录 → Host 决定"去哪个单位里找这个人" (定位) +已登录 → 一律以 token 里的 tenantId 为准 (授权) + 并且:Host 与 token 的租户**不一致就拒绝**(403) +``` + +不校验后者,在 `t1.xxx.com` 上拿 t2 的 token 就能读 t2 的数据 —— +而**界面一切正常**(前端本来就会带 token),是个安静、极难被发现的越权。 + +### 两条来自微信的硬约束(决定架构,不是可选) + +1. **公众号网页授权的「授权回调页面域名」只能配一个** → 每租户一个子域时, + 回调**统一落在主域名**,再按 scene 里的 `t{tid}` 302 回该单位站点 + (复用既有的 state 走一圈 + token 放 fragment 的机制)。 +2. **小程序的 request 合法域名是固定白名单**,配不了 `*.xxx.com` → + **小程序端用不了域名**,继续靠 scene 里的 `t{tid}`。这点客户需要知道。 + +### 另一个已处理的坑 + +开发期 `base` 是 `localhost`,真按它回跳会把手机上的学生从穿透域名扔到 +`t1.localhost`(打不开,且是浏览器级失败,看不出是我们跳错了)→ +H5 回跳里加了本机地址判断,本机 base 一律回退到 `h5-base-url`。 + +### 语义:`null` 不是错误 + +解析不出来 = "这是统一入口/单域名部署",调用方回退老路径。 +所以**单域名部署仍然完全合法**(`enabled: false`)。 + +### E2E:`scripts/e2e-m1-tenant-domain.py` → 16/16 + +t1 域名免租户码登录 / 与手填租户码矛盾时明确报错 / **跨租户 token → 403** / +四种边界回退(非 t 前缀、租户不存在、主域名、无关域名)。 +用 `http.client` 伪造 `Host`(urllib 会按 URL 自己算 Host,覆盖不掉)。 +全量 **676 条断言、0 失败**。 + +### ⏳ 需拍板 / 待办 + +1. **正式域名(base)** + 泛域名解析 + **泛域名证书 `*.xxx.com`**(备案一个主域名即覆盖子域)。 +2. **要不要保留"手填租户码"**:我保留了(统一入口、老客户端、运维排查都需要)。 +3. 小程序端确认继续靠 scene(上面第 2 条硬约束)。 +4. **补文档**:这条新需求目前只落在代码与本节,应补进 `数据信息填报系统PRD.md` + 与 `03-接口设计.md`(登录段的"租户从哪来"要多一种来源)。