120 lines
5.2 KiB
Markdown
120 lines
5.2 KiB
Markdown
# 业务 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`。
|
||
|
||
原因:不同环境的人脸数据与时间戳不同,不清理会导致重启后增量同步对不上。
|
||
|
||
另:ArcSoft 激活参数(`appId` / `sdkKey` / `activeKey`)无需单独处理——`DeviceInitActivity`
|
||
每次启动都会调用 `getDeviceConfig()` 从新环境重新获取并覆盖,切环境后自动适配。
|
||
|
||
**决策 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 次 → 弹窗 → 切「本地」→ 重启 → 抓包/日志确认请求打到新地址;
|
||
再切回「测试」验证可恢复。
|
||
|