Ver Fonte

docs: add mobile-h5 password login API integration design

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu há 2 meses atrás
pai
commit
5e0a1ce0a1

+ 162 - 0
docs/superpowers/specs/2026-08-10-mobile-h5-password-login-api-design.md

@@ -0,0 +1,162 @@
+# mobile-h5 账号密码登录对接真实 API
+
+> 日期:2026-08-10  
+> 适用范围:`mobile-h5` 员工登录页 → 复用 backend Web 登录接口  
+> 关联:`docs/superpowers/specs/2026-08-10-miniprogram-login-ui-design.md`  
+> 后端权威:`backend/app/api/public.py`(`/web/auth/login`、`/web/auth/login-rsa-public-key`)
+
+---
+
+## 1. 目标与范围
+
+### 1.1 目标
+
+1. 将登录页「账号 + 密码」从 `auth-mock` 改为调用真实后端登录接口。
+2. 传输方式与 `web-admin` 一致:RSA(PKCS#1 v1.5 / JSEncrypt)加密 `{ username, password, ts }`。
+3. 登录成功后持久化会话,进入工作台首页;未登录仍停留登录页。
+
+### 1.2 已确认决策
+
+| 项 | 约定 |
+|----|------|
+| 接口 | 复用 `GET /api/web/auth/login-rsa-public-key`、`POST /api/web/auth/login` |
+| 加密 | RSA 密文体 `{ encryptedLogin }`,与 web-admin 一致 |
+| 配置 | `mobile-h5/utils/config.js` 中可改 `API_BASE_URL`(默认 `http://127.0.0.1:8000/api`) |
+| 微信按钮 | 本期置灰不可点;不调微信、不写假登录态 |
+| 会话刷新 | 本期存 `refreshToken`,不做自动 refresh / 401 拦截 |
+| 后端改动 | **无**(不新增接口、不改登录逻辑) |
+
+### 1.3 非目标(本期不做)
+
+- 微信 `code` 换票、openid 绑定、手机号授权
+- Token 自动刷新、全局 401 重登
+- 忘记密码 / 注册 / 记住密码
+- 对称 API 加解密层(登录路径已在 `API_ENCRYPT_EXCLUDE_PATHS`)
+
+---
+
+## 2. 架构
+
+```
+┌─────────────────────┐     GET  /web/auth/login-rsa-public-key
+│  pages/login        │ ──► POST /web/auth/login { encryptedLogin }
+│  index.vue          │
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────┐
+│  utils/auth.js      │  passwordLogin / clearSession / isLoggedIn
+│  utils/login-rsa.js │  拉公钥 + JSEncrypt 加密
+│  utils/request.js   │  uni.request 封装(baseURL、解析 code/message/data)
+│  utils/config.js    │  API_BASE_URL
+└─────────┬───────────┘
+          │
+          ▼
+┌─────────────────────┐
+│  backend            │  校验 sys_user → JWT + userInfo
+│  /api/web/auth/*    │
+└─────────────────────┘
+```
+
+账号含义:`username` = `sys_user.user_no`(与 web-admin 相同)。
+
+---
+
+## 3. 数据流
+
+### 3.1 成功路径
+
+1. 前端校验非空(沿用 `validateCredentials`)。
+2. 协议门禁:未勾选则弹 `ConfirmDialog`;取消则中止。
+3. `GET {API_BASE_URL}/web/auth/login-rsa-public-key` → `publicKeyPem`。
+4. 用 JSEncrypt 加密 `JSON.stringify({ username, password, ts: Date.now() })` → `encryptedLogin`。
+5. `POST {API_BASE_URL}/web/auth/login`,body:`{ encryptedLogin }`。
+6. 响应约定:`{ code: 200, message, data: { token, refreshToken, expiresIn, userInfo } }`。
+7. 本地存储(`uni.setStorageSync`):
+   - `zkxw_mobile_token` ← `data.token`
+   - `zkxw_mobile_refresh_token` ← `data.refreshToken`
+   - `zkxw_mobile_user` ← `JSON.stringify(data.userInfo)`
+8. Toast「登录成功」→ `uni.reLaunch` 到 `/pages/index/index`。
+
+### 3.2 失败路径
+
+| 场景 | 表现 |
+|------|------|
+| 空账号/密码 | 字段级错误(现有逻辑) |
+| 网络失败 / 超时 | Toast「网络异常,请稍后重试」 |
+| RSA 加密失败 | Toast「登录加密失败,请重试」 |
+| `code !== 200`(含 401/403 业务码) | Toast 展示 `message`(如「用户名或密码错误」「账号已被停用…」) |
+| 登录中再次点击 | 忽略(`loading` 门禁) |
+
+### 3.3 已登录进入登录页
+
+`onShow`:若本地存在非空 `zkxw_mobile_token`,直接 `reLaunch` 首页(与现逻辑一致;本期不校验 token 是否仍有效)。
+
+---
+
+## 4. 模块与文件
+
+| 文件 | 职责 |
+|------|------|
+| `mobile-h5/utils/config.js` | 导出 `API_BASE_URL`(默认本地后端) |
+| `mobile-h5/utils/request.js` | 薄封装:`get`/`post`;拼接 baseURL;解析统一响应;失败抛带 `message` 的 Error |
+| `mobile-h5/utils/login-rsa.js` | 对齐 web-admin `buildWebEncryptedLoginBody`(依赖 `jsencrypt`) |
+| `mobile-h5/utils/auth.js` | 由 `auth-mock.js` 演进:保留 session 读写;`passwordLogin` 调真实 API;删除或停用 `mockWechatLogin` |
+| `mobile-h5/pages/login/index.vue` | 调 `passwordLogin`;微信按钮加 `is-disabled` / 不绑定点击 |
+| `mobile-h5/styles/components/login.scss` | `.login-wechat.is-disabled`:降低透明度、`pointer-events: none` |
+| `mobile-h5/package.json` | 增加依赖 `jsencrypt`(与 web-admin 同族) |
+
+**不修改:** `backend/**`、微信相关 API、协议页内容。
+
+### 4.1 依赖说明
+
+- 在 `mobile-h5` 安装 `jsencrypt`;小程序构建需能解析该包(HBuilderX / npm 构建按项目既有方式)。
+- 若发行构建对 `jsencrypt` 不友好,允许改为 vendor 拷贝同算法实现,行为须与 web-admin 一致(密文可被现有后端解密)。
+
+### 4.2 请求约定
+
+- URL:`API_BASE_URL` + 路径(路径以 `/web/auth/...` 开头,**不要**再重复 `/api`)。
+- Header:登录两接口无需 `Authorization`。
+- Content-Type:`application/json`。
+- 超时:建议 15s。
+- 真机调试:开发者工具 / 小程序后台需配置合法 request 域名,或开发期关闭域名校验;`API_BASE_URL` 改为可达地址。
+
+---
+
+## 5. UI:微信按钮置灰
+
+- 模板:`class="login-wechat is-disabled"`,**不**绑定 `@click`(或点击直接 return)。
+- 样式:`opacity: 0.45`(或等价视觉);`pointer-events: none`。
+- 文案仍为「微信授权登录」(表明能力预留,非「即将开通」文案)。
+
+协议勾选逻辑对微信按钮本期无意义(不可点);账号密码路径协议门禁保持不变。
+
+---
+
+## 6. 会话与后续业务请求
+
+- 本期仅登录页写会话;首页等业务仍可继续用 mock 数据。
+- `getToken()` / `isLoggedIn()` / `getUser()` / `clearSession()` 保留,供后续页面与登出使用。
+- `clearSession` 须同时清除 token、refreshToken、user。
+- 后续业务 `request` 带 `Authorization: Bearer {token}` 时再扩展,不在本期强制。
+
+---
+
+## 7. 验收标准
+
+1. 使用后台已有启用账号(有角色)可登录成功并进入首页;本地可看到真实 JWT(非 `mock_` 前缀)。
+2. 错误密码 / 停用 / 无角色时,Toast 为后端返回文案。
+3. 微信按钮视觉置灰且无法触发登录。
+4. 修改 `API_BASE_URL` 后请求打到对应主机(可用抓包或后端日志确认)。
+5. 后端代码无变更即可完成联调。
+
+---
+
+## 8. 风险与备注
+
+| 风险 | 缓解 |
+|------|------|
+| 模拟器 `127.0.0.1` 真机不可达 | 文档说明改 `API_BASE_URL` 为局域网 IP 或测试环境 |
+| 时钟偏差导致 RSA `ts` 校验失败 | 与后端 `LOGIN_ENCRYPT_TS_SKEW_SEC` 一致;提示用户校对设备时间 |
+| 旧 mock token 残留 | 实现时可用新 key 或登录成功覆盖;建议清除含 `mock_` 前缀的旧会话(可选,启动时一次) |
+| 微信登录后续 | 另开设计:openid 绑定或手机号匹配;本期按钮仅占位 |