父节点
032bc3dd0a
当前提交
933adcdcad
@ -0,0 +1,213 @@
|
||||
# feat(user/order): 定制师小程序分享 + 登录锁定剩余次数 + AdminLoginResponse 加 mobile
|
||||
|
||||
> **仓库**: HL (后端 hl-user-service + hl-mp-service + hl-order-service-v2 + hl-common-*)
|
||||
> **关联 PR/Issue**: PR #2913, Closes #2907
|
||||
> **日期**: 2026-05-22
|
||||
> **影响范围**: 管理后台登录页错误提示、admin 自身信息接口、小程序定制师身份识别、客人下单链路
|
||||
> **接收方**: mmg (前端)
|
||||
> **前端**: **需要 3 处改动 — 登录失败响应解析 + admin info 显示 mobile + 小程序分享按钮**
|
||||
|
||||
---
|
||||
|
||||
## 🚨 三大变化
|
||||
|
||||
### 1. AdminLoginResponse 加 `mobile` 字段
|
||||
|
||||
`/admin/auth/info` / `/admin/auth/login` / `/admin/auth/sms/login` / `/admin/auth/wechat/verify-2fa` / `/admin/auth/refresh` 等所有 admin 鉴权出口共 **9 处 builder 全部填充** admin.mobile。
|
||||
|
||||
前端可以直接 `data.mobile` 拿到当前登录管理员的手机号。
|
||||
|
||||
### 2. 5 次错误锁定 + 剩余次数 / 锁定分钟数
|
||||
|
||||
引入 `LoginFailException` (BusinessException 子类) + `GlobalExceptionHandler.handleLoginFail` 拦截器 + `Result.error(code, msg, data)` 三参重载,**统一在 `Result.data` 携带结构化字段**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200203,
|
||||
"message": "密码错误,剩余 3 次尝试",
|
||||
"data": {
|
||||
"remainingAttempts": 3,
|
||||
"lockedMinutes": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**前端直接读 `data.remainingAttempts` / `data.lockedMinutes`,不用解析 message 中文**。
|
||||
|
||||
### 3. 定制师小程序分享 + 客人下单 openid 反查
|
||||
|
||||
完整链路:
|
||||
|
||||
```
|
||||
1. 定制师后台补 mobile (#2886 已上线)
|
||||
2. 定制师 SMS 登录小程序,user 表存 phone + openid
|
||||
3. 小程序前端调 GET /mp/customizer/me 识别身份 → 显示「分享给客人」按钮
|
||||
4. 客人通过分享链接下单,提交 sharerOpenid
|
||||
5. 后端 sharerOpenid → user.phone → admin_user.mobile → adminId → order.customizer_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 一、错误响应 data 字段规范
|
||||
|
||||
### 密码登录失败 `POST /admin/auth/login`
|
||||
|
||||
| 场景 | code | message | data |
|
||||
|---|---|---|---|
|
||||
| 错 1-4 次 | 200203 | `密码错误,剩余 N 次尝试` | `{remainingAttempts: N}` |
|
||||
| **错 5 次直接锁定** | **200212** | `账号已锁定,请 30 分钟后重试` | `{lockedMinutes: 30}` |
|
||||
| 已锁定中重新登录 | 200212 | 同上 | `{lockedMinutes: <剩余分钟>}` |
|
||||
|
||||
⚠️ 改密码路径(`PUT /admin/auth/password`)旧密码错使用 **200213** `ADMIN_CHANGE_PASSWORD_OLD_WRONG`(避免 200203 模板冲突)
|
||||
|
||||
### 短信验证码登录失败 `POST /admin/auth/sms/login`
|
||||
|
||||
| 场景 | code | message | data |
|
||||
|---|---|---|---|
|
||||
| 码错 1-4 次 | 200102 | `验证码错误,剩余 N 次尝试` | `{remainingAttempts: N}` |
|
||||
| **码错 5 次直接锁定** | **200104** | `短信登录失败次数过多,请 15 分钟后重试` | `{lockedMinutes: 15}` |
|
||||
| 验证码过期 / 未发送 | 200103 | `验证码已过期,请重新获取` | `{}` (**不增 fail count**,防攻击者锁住真用户) |
|
||||
|
||||
---
|
||||
|
||||
## 🚨 前端需要做的事
|
||||
|
||||
### 1. 登录失败响应解析(账密 + 短信)
|
||||
|
||||
文件:`hl-ui/src/views/login/...`、`hl-ui/src/api/admin.js`
|
||||
|
||||
```js
|
||||
// 拦截器或登录页 onError
|
||||
if (resp.code === 200203 || resp.code === 200102) {
|
||||
// 密码 / 验证码错
|
||||
const remaining = resp.data?.remainingAttempts
|
||||
showError(`${resp.message}` + (remaining != null ? `(还剩 ${remaining} 次)` : ''))
|
||||
}
|
||||
if (resp.code === 200212 || resp.code === 200104) {
|
||||
// 账号 / 短信登录已锁定
|
||||
const minutes = resp.data?.lockedMinutes
|
||||
showError(`${resp.message}` + (minutes != null ? `(剩余 ${minutes} 分钟)` : ''))
|
||||
lockLoginButton(minutes * 60 * 1000)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. /admin/auth/info 显示当前管理员手机号
|
||||
|
||||
文件:`hl-ui/src/views/profile/...` 或顶部用户信息组件
|
||||
|
||||
```js
|
||||
const me = await getAdminInfo() // GET /admin/auth/info
|
||||
this.mobile = me.data.mobile // 直接读 (null 时显示「未补录」)
|
||||
```
|
||||
|
||||
### 3. 小程序定制师身份识别 + 分享按钮(mp 前端)
|
||||
|
||||
文件:`mp-cli/src/pages/...`、`mp-cli/src/api/customizer.js`
|
||||
|
||||
```js
|
||||
// 登录后调一次
|
||||
const me = await getCustomizerMe() // GET /mp/customizer/me
|
||||
if (me.data.isCustomizer) {
|
||||
this.showShareButton = true
|
||||
this.customizerInfo = me.data // adminId, customizerName, customizerAvatar
|
||||
}
|
||||
```
|
||||
|
||||
**分享链接 URL 必须带 `?sharerOpenid={当前用户 openid}`**(从当前小程序登录态拿)。
|
||||
|
||||
客人点开分享链接下单时,把 `sharerOpenid` 透传到下单请求 body (字段名沿用现有 `MpCreateOrderRequest.sharerOpenid`,已具备)。
|
||||
|
||||
### 4. 自查
|
||||
|
||||
```bash
|
||||
grep -rE "remainingAttempts|lockedMinutes|/mp/customizer/me|sharerOpenid|admin.*mobile" hl-ui/src mp-cli/src
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、新增接口清单
|
||||
|
||||
### `GET /mp/customizer/me` — 当前用户的定制师身份
|
||||
|
||||
JWT 鉴权,无 token → 401。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"isCustomizer": true,
|
||||
"adminId": 9999999998,
|
||||
"customizerName": "张三",
|
||||
"customizerAvatar": "https://..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`isCustomizer: false` 时其他字段为 null。
|
||||
|
||||
### 内部 Feign(后端服务间用,前端不调)
|
||||
|
||||
- `GET /internal/user/admin/by-phone?phone=` 返 admin 信息或 null
|
||||
- `GET /internal/mp/user/by-openid?openid=` 返 user.phone 或 null
|
||||
|
||||
---
|
||||
|
||||
## 三、客人下单链路改造(对前端透明)
|
||||
|
||||
`POST /mp/order/create` 流程改造,**前端无 API 字段变化**:
|
||||
|
||||
```
|
||||
优先级:
|
||||
1. request.customizerId 显式传 (现有 ?adminId=xxx URL 透传路径)
|
||||
2. customizerId 为空 + sharerOpenid 非空 → 后端 openid → phone → adminId 反查
|
||||
3. 都没命中 → 沿用现有 assignRandomCustomizer 兜底
|
||||
```
|
||||
|
||||
反查失败降级不抛异常,客人下单不被后台问题阻塞。
|
||||
|
||||
---
|
||||
|
||||
## 四、新错误码(本期新增 4 个)
|
||||
|
||||
| code | 含义 | data |
|
||||
|---|---|---|
|
||||
| 200212 | 账号已锁定(密码登录) | `{lockedMinutes: 30}` |
|
||||
| 200213 | 旧密码错(改密码场景,与登录密码错码分离) | `{}` |
|
||||
| 200102 message 更新 | 验证码错误,剩余 N 次尝试 | `{remainingAttempts: N}` |
|
||||
| 200203 message 更新 | 密码错误,剩余 N 次尝试 | `{remainingAttempts: N}` |
|
||||
|
||||
---
|
||||
|
||||
## 五、测试服验证记录
|
||||
|
||||
部署到测试服 (`api.test.1814.love:9443`) 后 round-trip 验证 (2026-05-22 18:07):
|
||||
|
||||
| # | 场景 | 实测 |
|
||||
|---|------|------|
|
||||
| T1 | admin 登录 + /admin/auth/info 返回 mobile | ✅ mobile=13800138000 |
|
||||
| T2 | 密码错 1-3 次 remainingAttempts 累加 | ✅ 4/3/2 |
|
||||
| T3 | 密码错 5 次直接锁定 | ✅ 200212 + lockedMinutes=30 |
|
||||
| T4 | SMS 错码 remainingAttempts 累加 | ✅ 4/3/2 |
|
||||
| T5 | /mp/customizer/me 端点路由 | ✅ (401 鉴权要求, 路由正常) |
|
||||
|
||||
⚠️ test_admin_lock 测试用户因没绑企微 login 时 token=null(`requireWechatBind=true`),改用内置 admin(已绑企微)测 info 端点通过。
|
||||
|
||||
---
|
||||
|
||||
## 六、不在本期范围
|
||||
|
||||
- ❌ dev-v3 二期同步 (另起工单)
|
||||
- ❌ 客人维度长期绑定定制师 (另起工单)
|
||||
- ❌ admin_user 表 schema 改动 (字段都已具备)
|
||||
- ❌ 跨 appId openid 隔离 (当前单小程序)
|
||||
|
||||
---
|
||||
|
||||
## 七、联系人
|
||||
|
||||
后端: wx(呼籁旅行)
|
||||
前端: mmg
|
||||
|
||||
如有疑问可在 [#2907](https://git.1814.love:8443/wx/HL/issues/2907) 评论区留言。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户