From e6cd1049d49aa44745ef4cdb719d01d46a615c68 Mon Sep 17 00:00:00 2001 From: lovife <474239041@qq.com> Date: Tue, 29 Sep 2026 21:42:00 +0800 Subject: [PATCH] =?UTF-8?q?chore(tenant):=20=E7=A7=9F=E6=88=B7=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E6=94=B9=E4=B8=BA=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=AD?= =?UTF-8?q?=EF=BC=88=E5=AE=A2=E6=88=B7=E5=86=B3=E5=AE=9A=E5=85=88=E4=B8=8D?= =?UTF-8?q?=E5=81=9A=EF=BC=89=E2=80=94=E2=80=94=20=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E4=BF=9D=E7=95=99=EF=BC=8C=E5=85=B3=E5=BE=97=E5=B9=B2=E5=87=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 客户:先不做多租户了。 处理:fill.tenant-domain.enabled 默认 true → **false**,代码全部保留。 理由: · 这个能力是隔离的:关掉后 TenantDomainResolver 一律返回 null, 登录走老路径、AuthInterceptor 那段校验根本不触发 —— 不影响任何既有行为 · 它已经验过(§一·二十五 16/16),删掉等于扔掉一份验过、将来要用的东西 · 要真用上它,前提是有正式域名 + 泛域名解析 + 泛域名证书;这些没定之前 开它只会让人以为"域名能区分单位了",实际并不能。等定了改一个配置 + 重启即可。 「关得干净」是本轮的验收点:E2E 改成**两种模式都能跑**,按当前模式选断言。 现在跑的是关闭态这一节(6 条): ✓ t1.localhost + 不填租户码 → 40012(域名不参与判断) ✓ 同一域名 + 填租户码 → 照样能登;主域名 + 租户码 → 照样能登 ✓ 主域名 + 不填租户码 → 仍是 40012(跨单位重名的原行为) ✓ 关掉之后不引入任何新校验:带 t1 的 token 访问 t14 域名照常通过 脚本自己探测模式(免租户码登录成功=开、40012=关),所以开着时跑它会跑那 16 条, 不会因为模式不对而假绿。 将来开启:填 base → 配泛域名解析+证书 → XT_TENANT_DOMAIN_ENABLED=true → 重启 → 跑 E2E。 两条硬约束不变:微信授权回调域名只能配一个(回调统一落主域名再按 scene 跳回)、 小程序域名固定白名单(小程序端永远靠 scene)。 随之暂缓:§一·二十五 的三条待拍板(正式域名 / 手填租户码 / 小程序确认)现在都不用追了; PRD 与 03-接口设计的补文档也等真要做时再写(现在写进去只会多一条"设计了但没启用")。 全量 666 条断言、0 失败(租户域名 16 → 6,因为跑的是关闭态那节)。 --- .../src/main/resources/application-dev.yml | 15 +++-- docs/交接摘要.md | 63 ++++++++++++++++++ scripts/e2e-m1-tenant-domain.py | 66 ++++++++++++++++++- 3 files changed, 138 insertions(+), 6 deletions(-) diff --git a/backend/xt-server/src/main/resources/application-dev.yml b/backend/xt-server/src/main/resources/application-dev.yml index 4489077..0fa2606 100644 --- a/backend/xt-server/src/main/resources/application-dev.yml +++ b/backend/xt-server/src/main/resources/application-dev.yml @@ -138,14 +138,20 @@ fill: # 代码里还会再判一次 profile 不含 prod(双保险)。 stub-enabled: ${WX_H5_STUB:true} - # ── 租户域名(用子域前缀区分租户)────────────────────────────────────── + # ── 租户域名(用子域前缀区分租户)—— 客户 2026-09-29 决定**先不做**,默认关闭 ── # # 形态:t{tenantId}.{base},如 t1.example.com / t2.example.com。 # 前缀用的是 **tenantId**,与扫码 scene 的 t{tid} 是同一套约定(scene 早就是 t1-k17)。 # + # ⚠️ **默认关闭**:单域名部署是合法形态,而且这个能力要真正用上, + # 前提是**有正式域名 + 泛域名解析 + 泛域名证书**(`*.example.com`)—— + # 这些没定之前开它只会让人以为"域名能区分单位了",实际并不能。 + # 客户说先不做,所以这里是 false;**能力本身保留且已验过** + # (e2e-m1-tenant-domain.py 16/16,见 §一·二十五), + # 将来只需:填 base、配泛域名证书、把这里改成 true、重启。 + # # 打开之后: - # · PC 登录在 t1.xxx.com 上**不用再填租户码** —— 域名已经回答了"你是哪个单位的", - # 那句「该账号存在于多个单位,请填写租户码」在这条路径下不会再出现 + # · PC 登录在 t1.xxx.com 上**不用再填租户码** —— 域名已经回答了"你是哪个单位的" # · H5 微信授权回调仍落在主域名(微信的"授权回调页面域名"只能配一个), # 回调再按 scene 里的 t{tid} 跳回该单位的站点 # @@ -154,9 +160,8 @@ fill: # 就能读 t2 的数据,而界面看起来一切正常。 # ⚠️ 小程序端**用不了它**:微信的 request 合法域名是固定白名单,配不了 *.xxx.com, # 所以小程序继续靠 scene 里的 t{tid}。 - # ⚠️ 生产要配**泛域名解析 + 泛域名证书**(*.example.com)。 tenant-domain: - enabled: ${XT_TENANT_DOMAIN_ENABLED:true} + enabled: ${XT_TENANT_DOMAIN_ENABLED:false} base: ${XT_TENANT_DOMAIN_BASE:localhost} scheme: ${XT_TENANT_DOMAIN_SCHEME:http} diff --git a/docs/交接摘要.md b/docs/交接摘要.md index ab6568b..97835fa 100644 --- a/docs/交接摘要.md +++ b/docs/交接摘要.md @@ -2060,3 +2060,66 @@ t1 域名免租户码登录 / 与手填租户码矛盾时明确报错 / **跨租 3. 小程序端确认继续靠 scene(上面第 2 条硬约束)。 4. **补文档**:这条新需求目前只落在代码与本节,应补进 `数据信息填报系统PRD.md` 与 `03-接口设计.md`(登录段的"租户从哪来"要多一种来源)。 + + +--- + +## §一·二十六 租户域名:客户决定**先不做** —— 能力保留、默认关闭 + +> 客户原话:**先不做多租户了。** + +### 怎么处理的:关掉,但**不删** + +`fill.tenant-domain.enabled` 默认值从 `true` 改成 **`false`**(`application-dev.yml`)。 +**代码全部保留**,理由: + +- 这个能力是**隔离的**:关掉之后 `TenantDomainResolver` 一律返回 `null`, + 登录走老路径、`AuthInterceptor` 里那段校验根本不会触发 —— + **不影响任何既有行为**(下面有断言专守这一点)。 +- 它已经**验过**(§一·二十五 的 16/16),删掉等于把一份验过的、将来要用的东西扔掉。 +- 要真正用上它,前提是**有正式域名 + 泛域名解析 + 泛域名证书** —— + 这些没定之前开它只会让人以为"域名能区分单位了",实际并不能。 + 所以"先不做"是对的;等域名定了,**改一个配置 + 重启**就能用。 + +### 「关得干净」是这一轮的验收点 + +关一个功能键,最怕的是**关不干净**(悄悄留下一点新行为)。 +所以 `e2e-m1-tenant-domain.py` 改成**两种模式都能跑**,并按当前模式选断言: + +``` +[关闭态] 域名不再提供租户身份,老路径完全不变 ← 当前跑的就是这一节 + ✓ t1.localhost + 不填租户码 → 40012(域名不参与判断) + ✓ 同一域名 + 填租户码 → 照样能登 + ✓ 主域名 + 租户码 → 照样能登 + ✓ 主域名 + 不填租户码 → 仍是 40012(跨单位重名的原行为) + ✓ 关掉之后**不引入任何新校验**:带 t1 的 token 访问 t14 域名照常通过 + (这是关闭前的既有行为;开启态下会被 403 拒掉,见 run_on) +``` + +脚本自己探测模式(用 `t1.localhost` 免租户码试登录:成功=开、`40012`=关), +所以**开着的时候跑它就会跑那 16 条**,不会因为模式不对而假绿。 + +**全量 666 条断言、0 失败。** + +### 将来要开,需要四步 + +1. 定下正式主域名,填 `XT_TENANT_DOMAIN_BASE`(如 `example.com`) +2. 配**泛域名解析**与**泛域名证书** `*.example.com`(备案一个主域名即覆盖子域) +3. 把 `XT_TENANT_DOMAIN_ENABLED` 设为 `true` +4. 重启后跑 `python scripts/e2e-m1-tenant-domain.py`(它会自动切到开启态的 16 条断言) + +⚠️ 别忘了两条硬约束(详见 §一·二十五):**微信授权回调域名只能配一个** +(所以回调统一落主域名、再按 scene 跳回)、**小程序域名是固定白名单** +(所以小程序端永远靠 scene,不受这个能力影响)。 + +### 随之暂缓的待办 + +§一·二十五 里那三条待拍板(正式域名 / 要不要保留手填租户码 / 小程序确认), +**现在都不用追了** —— 它们只在开启这个能力时才有意义。 +`PRD` 与 `03-接口设计` 的补文档同理:**等真要做时再补**, +现在写进去反而会让文档里多一条"设计了但没启用"的东西。 + +### 下一步 + +回到 §一·二十二 的顺序:**第 4 步 = 审核闭环(后端)** —— +初审 / 终审 / 撤回 / 批量通过(只带无问题项)/ 驳回(逐条填原因)。 diff --git a/scripts/e2e-m1-tenant-domain.py b/scripts/e2e-m1-tenant-domain.py index 1ba9e29..0af6b21 100644 --- a/scripts/e2e-m1-tenant-domain.py +++ b/scripts/e2e-m1-tenant-domain.py @@ -94,7 +94,7 @@ def login(host: str, tenant_code: str | None = None, account: str = "admin", return call("POST", "/api/v1/auth/login", host, body=body) -def main() -> int: +def run_on() -> int: ap = argparse.ArgumentParser(description="租户域名 E2E") ap.add_argument("--password", default="Admin@123") args = ap.parse_args() @@ -174,5 +174,69 @@ def main() -> int: return 1 if FAILED else 0 +def detect_mode() -> str: + """ + 探测后端有没有启用租户域名:用 t1.localhost 且不填租户码试一次登录。 + + 为什么要探测而不是写死:客户 2026-09-29 决定"先不做",默认已关; + 但这个能力保留着(填个域名就能开)。脚本两种模式都要能跑, + 而且**两种模式的断言是不同的** —— 关闭态要验的是"行为与从前完全一致"。 + """ + r = login("t1.localhost", None) + if r.get("code") == 0: + return "ON" + if r.get("code") == 40012: + return "OFF" + raise SystemExit(f" ✗ 探测失败(既不是 0 也不是 40012):{r}") + + +def run_off() -> int: + """ + 关闭态(当前默认):验"关掉之后,行为与从前**完全一致**"。 + + 这一节看着简单,但它是"先不做"这件事的验收 —— + 关一个功能键,最怕的是关不干净(悄悄留下一点新行为)。 + """ + print("=" * 76) + print("租户域名 E2E —— 当前是**关闭态**(客户先不做,能力保留)") + print("=" * 76) + + print("\n[关闭态] 域名不再提供租户身份,老路径完全不变") + a = login("t1.localhost", None) + check("t1.localhost + 不填租户码 → 40012(域名不参与判断)", + a.get("code") == 40012, f"{a}") + b = login("t1.localhost", "demo") + check("同一域名 + 填租户码 → 照样能登(老路径不受影响)", b.get("code") == 0, f"{b}") + c = login("localhost", "demo") + check("主域名 + 租户码 → 照样能登", c.get("code") == 0, f"{c}") + d = login("localhost", None) + check("主域名 + 不填租户码 → 仍是 40012(跨单位重名的原行为)", + d.get("code") == 40012, f"{d}") + + token = (b.get("data") or {}).get("token") + e = call("GET", "/api/v1/users?pageNum=1&pageSize=1", "t14.localhost", token) + check("关掉之后**不引入任何新校验**:带着 t1 的 token 访问 t14 域名照常通过", + e.get("code") == 0, + f"{e}(这是关闭前的既有行为;开启态下会被 403 拒掉,见 run_on)") + + ok("要验开启态:以 XT_TENANT_DOMAIN_ENABLED=true 重启后端,再跑本脚本") + + print() + print("=" * 76) + print(f"通过 {len(PASSED)} 失败 {len(FAILED)} 共 {len(PASSED) + len(FAILED)}") + if FAILED: + for n, det in FAILED: + print(f" · {n}\n {det}") + print("=" * 76) + return 1 if FAILED else 0 + + +def main() -> int: + mode = detect_mode() + if mode == "ON": + return run_on() + return run_off() + + if __name__ == "__main__": raise SystemExit(main())