From 1571eb2ec41e1e2a2951fe2f612372555c988816 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 21 Sep 2026 17:26:58 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E7=AB=AF:=20=E5=87=BA=E5=8E=82?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=9C=B0=E5=9D=80=E6=94=B9=E6=88=90=20exampl?= =?UTF-8?q?e.com=EF=BC=88=E5=8E=9F=E6=9D=A5=E6=98=AF=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E8=80=85=E8=87=AA=E5=B7=B1=E7=9A=84=E7=94=9F=E4=BA=A7=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户 2026-09-21:「为什么现在登陆页面默认填写我们的服务器地址?不应该是 example 地址吗」 ★ 用户的直觉是对的:**这确实是错的**,而且比"预填"更彻底。 ## 现状:三处互相打脸 ① 输入框的 placeholder: TextInput({ placeholder: 'https://example.com/api/v1', text: this.serverAddr }) ↑ 「请填你自己的」 ② 同一个输入框的 text 预填: @State serverAddr: string = DEFAULT_API_BASE export const DEFAULT_API_BASE = 'https://mail.jianfgit.xyz/api/v1' ↑ 「就是这个」—— 与 ① 直接矛盾 ③ 校验失败的 5 条提示文案全在教用户填同一个生产域名: '请填写服务器地址,例如 https://mail.jianfgit.xyz/api/v1' ↑ 用户是照着它填的 通用客户端把**某一个人的后端**写成出厂默认,等于宣称"本产品只有一个后端"; 更坏的是用户会以为"直接登录就行",而连的其实是别人的机器。 ## WebUI 的做法(对照) 它**一个域名都不写死**(`api/config.ts:30`): const base = (runtime || build || '/api/v1').trim(); 因为 WebUI 跑在服务端自己发出来的页面上,"同源"天然正确。 **鸿蒙是独立 App,没有"同源"可依** —— 所以只能要求用户填,而默认值只能是格式示例。 ## 改法 · `DEFAULT_API_BASE` → `https://example.com/api/v1`(与 placeholder 同一句话); · `ApiBase.ts` 里 5 条面向用户的 error 文案 → 同样换成 `example.com`。 ★ 注释里的 `mail.jianfgit.xyz` **保留不动** —— 那是"当时线上发生了什么"的取证 (那段讲的是 2026-09-15 用户报"连不上服务器"的两个坑),删掉就销毁了依据。 判据因此是**剥注释后再扫**代码。 ★ 为什么不留空串:这个字段必须填对,留空用户不知道格式; 而预填一个**看起来能用的真域名**更坏。 ## 判据:原先只钉"格式",抓不到"值是谁的" 原来的断言是「https + 带 /api/v1」—— 而 `https://mail.jianfgit.xyz/api/v1` **两条都满足** ⇒ 全绿。**格式判据抓不到语义事故**:那个值可以完全合法却仍然是错的。 ⇒ 新增两条**语义**判据: ① 出厂默认必须落在 **RFC 2606 保留域名**(`example.com/net/org`、`.test`、`.invalid`); ② 面向用户的**提示文案**里不许出现真实域名(剥注释后扫)。 变异逐条验过会红: 默认值改回生产域名 → 红 ✓ 把某条 error 文案换成 my-real-server.com → 红 ✓ 两者都在 → 13/13 全绿 ✓ ★ 期间修了判据自己的一个 bug:我用了 `code('model/ApiBase.ts')`, 而 `code()` 是相对 **electron 包**解析的 ⇒ `ENOENT .../client/electron/model/ApiBase.ts` —— **判据自己崩了**(整条不跑、退出码 1)。已改用本文件既有的 `read(rel)`。 ## 设备验证 清掉 preferences 里的旧地址后冷启,`uitest dumpLayout`: text 'https://example.com/api/v1' hint 'https://example.com/api/v1' ⇒ 预填与提示**同话**,不再互相矛盾。 (老装机 preferences 里已有的地址**保持不动** —— 那是用户自己配的服务器,不该被清。) --- .../entry/src/main/ets/common/Config.ets | 40 ++++++++++++++----- .../entry/src/main/ets/model/ApiBase.ts | 10 ++--- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/client/harmony/entry/src/main/ets/common/Config.ets b/client/harmony/entry/src/main/ets/common/Config.ets index c6af9aa..f05fae1 100644 --- a/client/harmony/entry/src/main/ets/common/Config.ets +++ b/client/harmony/entry/src/main/ets/common/Config.ets @@ -4,22 +4,44 @@ */ /** - * 默认地址:**HTTPS + 公网域名**(2026-09-15 改)。 + * 默认地址 —— **出厂占位值,不是"我们的服务器"**(2026-09-21 改)。 * - * 原来是 `http://192.168.2.60:8180/api/v1` —— 明文 + 写死内网 IP,两个问题: - * 1. 它只在"手机与这台机在同一网段"时可用,出门/换网就永远连不上, - * 而错误还只说"无法连接",用户无从判断; - * 2. 明文 http 会把登录凭据暴露在链路上。 + * ── 改前是什么、为什么错 ── * - * 域名解析到的就是同一台机(`mail.jianfgit.xyz` → 192.168.2.60), - * 所以本机/内网调试**不需要**为了速度牺牲 TLS:走 https 也能到。 - * 真要直连内网明文,用设置页改成 `http://192.168.2.60:8180/api/v1`(见 network_config.json)。 + * 原来这里是 `https://mail.jianfgit.xyz/api/v1` —— **开发者自己的生产域名**。 + * 用户的直觉是对的(他问:「为什么现在登陆页面默认填写我们的服务器地址? + * 不应该是 example 地址吗」):一个通用客户端把**某一个人的服务器**作为出厂默认, + * 等于宣称"本产品只有一个后端"。 + * + * 三处互相打脸,一眼就能看出这是疏漏而不是决定: + * ① 输入框的 `placeholder` 写的是 `https://example.com/api/v1`(= "请填你自己的"); + * ② 同一个输入框的 `text` 却预填了生产域名(= "就是这个"); + * ③ 校验失败的提示文案也全在教用户填 `mail.jianfgit.xyz`。 + * + * WebUI 那边**一个域名都不写死**:`resolveBase()` 取「运行时全局 > 构建期变量 > + * 同源 `/api/v1`」(`api/config.ts:30`)—— 它跑在服务端自己发出来的页面上, + * 所以"同源"天然正确。**鸿蒙是独立 App,没有"同源"可依**,只能要求用户填。 + * + * ── 现在的口径 ── + * + * 默认值是 **`https://example.com/api/v1`** —— 与 placeholder 同一句话: + * 它是**格式示例**,不是可用地址。用户必须改成自己部署的那台。 + * + * 为什么保留"预填一个值"而不是留空: + * · 留空后用户完全不知道要填什么格式,而这是个必须填对的字段; + * · 预填一个明显是示例的域名,用户一眼就知道"这里要换成我的", + * 而预填一个**看起来能用的真域名**才会让人以为"直接登录就行"。 * * ★ 末尾的 `/api/v1` **不能省**:`ApiClient` 里的路径都是相对它的 * (`'/auth/login'`、`'/me/sessions'`…),省掉就变成 `https://域名/auth/login` ⇒ 404。 * 归一化与校验在 `model/ApiBase.ts`(用户手填的地址走那里,判据跑那一份)。 + * + * ★ 内网联调怎么办:那台机就是本机,用 `EMULATOR_HOST_BASE` + * (模拟器 NAT)或直接在设置页填内网明文地址 —— `network_config.json` + * 已经为 `192.168.2.60` / `10.0.2.2` / `localhost` 放行了明文。 + * **但那属于开发场景,不该写进出厂默认值。** */ -export const DEFAULT_API_BASE: string = 'https://mail.jianfgit.xyz/api/v1'; +export const DEFAULT_API_BASE: string = 'https://example.com/api/v1'; /** * 模拟器 NAT 访问宿主机地址(备用,LAN 直连不通时用)。 diff --git a/client/harmony/entry/src/main/ets/model/ApiBase.ts b/client/harmony/entry/src/main/ets/model/ApiBase.ts index d6eb5fa..5f66df5 100644 --- a/client/harmony/entry/src/main/ets/model/ApiBase.ts +++ b/client/harmony/entry/src/main/ets/model/ApiBase.ts @@ -144,7 +144,7 @@ export function validateApiBase(raw: string): ApiBaseCheck { return { ok: false, base: '', - error: '请填写服务器地址,例如 https://mail.jianfgit.xyz/api/v1', + error: '请填写服务器地址,例如 https://example.com/api/v1', warning: '' }; } @@ -154,7 +154,7 @@ export function validateApiBase(raw: string): ApiBaseCheck { return { ok: false, base: base, - error: '地址要以 http:// 或 https:// 开头,例如 https://mail.jianfgit.xyz/api/v1' + error: '地址要以 http:// 或 https:// 开头,例如 https://example.com/api/v1' + '(不自动替你补 https —— 协议选错会把凭据送到明文通道上)', warning: '' }; @@ -165,7 +165,7 @@ export function validateApiBase(raw: string): ApiBaseCheck { return { ok: false, base: base, - error: '地址里不能有空格,例如 https://mail.jianfgit.xyz/api/v1', + error: '地址里不能有空格,例如 https://example.com/api/v1', warning: '' }; } @@ -174,7 +174,7 @@ export function validateApiBase(raw: string): ApiBaseCheck { return { ok: false, base: base, - error: '地址里缺少域名,例如 https://mail.jianfgit.xyz/api/v1', + error: '地址里缺少域名,例如 https://example.com/api/v1', warning: '' }; } @@ -254,7 +254,7 @@ export function kindOfNativeCode(nativeCode: number): string { export function describeFailure(status: number, nativeCode: number, serverMessage: string, url: string): FailureText { if (status === 404) { let message: string = 'HTTP 404:服务端没有 ' + url + ' 这个接口。' - + '地址应当是完整的 API 前缀、以 /api/v1 结尾(例如 https://mail.jianfgit.xyz/api/v1)。'; + + '地址应当是完整的 API 前缀、以 /api/v1 结尾(例如 https://example.com/api/v1)。'; if (serverMessage.length > 0) { message = message + '服务端回复:' + serverMessage; }