跨端: 出厂默认地址改成 example.com(原来是开发者自己的生产域名)
用户 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 里已有的地址**保持不动** —— 那是用户自己配的服务器,不该被清。)
This commit is contained in:
@ -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 直连不通时用)。
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user