From 41cfec05835b9c5e36e2860eccdecd468e5e02e6 Mon Sep 17 00:00:00 2001 From: mazengfei <331023091@qq.com> Date: Tue, 1 Sep 2026 17:15:04 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E4=B8=9A=E5=8A=A1=20?= =?UTF-8?q?BaseUrl=20=E5=88=87=E6=8D=A2=E8=AE=BE=E8=AE=A1=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../specs/2026-09-01-baseurl-switch-design.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-01-baseurl-switch-design.md diff --git a/docs/superpowers/specs/2026-09-01-baseurl-switch-design.md b/docs/superpowers/specs/2026-09-01-baseurl-switch-design.md new file mode 100644 index 0000000..ab6ab80 --- /dev/null +++ b/docs/superpowers/specs/2026-09-01-baseurl-switch-design.md @@ -0,0 +1,116 @@ +# 业务 BaseUrl 切换 — 设计文档 + +- 日期:2026-09-01 +- 状态:已确认 +- 关联模块:网络层 / 设备初始化 / 全局配置 + +## 1. 背景与目标 + +SmartTakePlate(发盘机)当前业务服务地址由 `GlobalData.appBaseUrl` 决定,但该字段: +- 默认值为空串,全局无任何活跃代码赋值(仅剩注释)。 +- 已具备一套"半成品"基础设施(`SpTool.getBaseUrl/setBaseUrl`、`GlobalKey.KEY_BASE_URL`、 + `LOCAL/TEST/PROD_BASE_URL` 常量),但从未接线。 + +目标:让现场运维人员能在**设备端**切换业务环境(本地 / 测试 / 生产),切换结果持久化, +重启后生效。 + +## 2. 需求 + +- **入口**:设备端隐藏入口,置于**登录前的初始化界面**(`DeviceInitActivity`)。 + 原因:若入口放在业务主界面,一旦配置的服务地址访问不到,业务界面因依赖网络请求成功才能 + 进入,将无法切回正确地址,形成死锁。 +- **生效方式**:持久化到 SharedPreferences,重启 app 后生效(最简单可靠)。 +- **选项来源**:仅预设环境列表(本地 / 测试 / 生产),不支持手输。 + +## 3. 现状分析 + +| 已存在 | 状态 | +|---|---| +| `GlobalData.appBaseUrl`(`var String = ""`) | 默认空串,无活跃赋值 | +| `LOCAL_BASE_URL` / `TEST_BASE_URL` / `PROD_BASE_URL` | 已定义,不带尾斜杠 | +| `GlobalKey.KEY_BASE_URL = "baseUrlKey"` | 已定义 | +| `SpTool.getBaseUrl() / setBaseUrl()` | 已实现,无调用方 | +| `ApiClient.retrofit`(`by lazy`,`baseUrl(GlobalData.appBaseUrl)`) | 用空串构建,切环境不重建 | +| `ApiService` / `ApiServiceV2` 全部方法 | 用 `@Url url = "${GlobalData.appBaseUrl}/..."` 默认参数,调用时求值 | + +关键结论: +1. 所有请求地址由 `GlobalData.appBaseUrl` 在**每次调用时**经默认参数拼接决定,故切环境 + 只需改这一个变量(配合重启重建 `Retrofit`)。 +2. `GlobalData.appBaseUrl=""` 会让 `Retrofit.Builder().baseUrl("")` 抛异常 —— 现有隐患。 + +## 4. 设计 + +### 4.1 组件划分与改动清单 + +| 文件 | 改动 | +|---|---| +| `GlobalData.kt` | 补充环境列表模型(名称 + URL),复用已有 `LOCAL/TEST/PROD_BASE_URL` | +| `MyApp.kt` | `initGlobalData()` 接线:读 `SpTool.getBaseUrl()`,兜底 `TEST_BASE_URL` | +| `DeviceInitActivity.kt` | 加隐藏连点手势 + 弹环境选择弹窗 | +| `ApiClient.kt` | 修复 Retrofit baseUrl 校验(补尾斜杠 / 占位),消除空串崩溃 | +| 新增 `EnvironmentSelectDialog`(或内联 Dialog) | 环境单选弹窗,高亮当前项 | +| `BaseActivity.kt`(复用) | 复用已有 `clearAllFace()` 清人脸库 | + +### 4.2 关键设计决策 + +**决策 A — URL 规范化(消除现有隐患)** + +- 约定 `GlobalData.appBaseUrl` **不带尾斜杠**,供 `@Url` 拼接得到形如 + `https://dev.yixiong-tech.com:8081/terminal/...` 的完整地址。 +- `Retrofit` 的 baseUrl 单独规范化:空则占位 `http://localhost/`,非空且无尾斜杠则补 `/`。 + 因所有请求走 `@Url` 全路径,Retrofit baseUrl 不参与实际拼接,只需合法即可。 + +**决策 B — 切换时清理环境残留** + +选中环境后执行: +1. `SpTool.setBaseUrl(url)` 持久化; +2. 清空本地人脸库(`FaceDatabase` / `clearAllFace()`); +3. 重置人脸时间戳 `lastFaceTimestamp = 0`; +4. 重置 `firstGetFace = true`。 + +原因:不同环境的人脸数据与时间戳不同,不清理会导致重启后增量同步对不上。 + +**决策 C — 隐藏手势** + +在 `DeviceInitActivity` 连续点击屏幕(如 5 次)触发,不新增可见 UI 元素。该界面登录前 +可达、不依赖网络,满足"配错地址也能切回"的要求。 + +## 5. 数据流 + +```mermaid +sequenceDiagram + participant User as 运维人员 + participant App as MyApp/DeviceInit + participant Sp as SpTool(SharedPrefs) + participant Net as Retrofit/网络 + + App->>Sp: 启动时读 KEY_BASE_URL + alt 有值 + Sp-->>App: 返回已存 URL + else 无值(首次) + Sp-->>App: 空 → 用 TEST_BASE_URL + end + App->>App: GlobalData.appBaseUrl = 结果 + + User->>App: 初始化界面连点5次 + App->>App: 弹环境选择Dialog(高亮当前) + User->>App: 选择「生产」并确认 + App->>Sp: setBaseUrl(PROD) + App->>App: 清人脸库 + 重置时间戳 + firstGetFace=true + App->>User: Toast「重启后生效」 + User->>App: 重启 app + App->>Net: appBaseUrl 已指向生产 → 全量拉新环境人脸 +``` + +## 6. 错误处理 + +- **baseUrl 空 / 非法**:兜底 `TEST_BASE_URL`,并保证 Retrofit baseUrl 合法(占位/补斜杠)。 +- **清人脸库失败**:非致命,仅记日志,不阻塞切换(重启后首次拉取仍会兜底全量)。 +- **写 SharedPreferences 失败**:几乎不抛;Toast 提示用户重试。 + +## 7. 验证方式 + +- 构建:`./gradlew assembleDebug` 通过。 +- 手动:初始化界面连点 5 次 → 弹窗 → 切「本地」→ 重启 → 抓包/日志确认请求打到新地址; + 再切回「测试」验证可恢复。 +