SFT/docs/superpowers/specs/2026-07-28-personal-open-mo...

137 lines
9.2 KiB
Markdown

# 个人开户移动端H5 发送短信验证码接口变更 设计文档
## 背景
`docs/superpowers/specs/2026-07-27-personal-open-mobile-h5-design.md` 已实现 `fryl_h5` 独立工程的个人开户移动端H5确认页,其中"发送短信验证码"环节前端假设新增了一个专属接口 `POST /api/wallet-management/personal-opening/mobile/send-verify-code`(请求体仅 `{applicationNo}`),并登记在《缺失的后端接口.md》Part 12 第94条,标注为"前端单方假设,待后端确认"。
后端现已提供该场景的正式接口实现(`PersonalOpeningMobileController.sendVerifyCodeGeneral`),接口地址与请求参数均与此前假设不同:
```java
@RequestMapping(value = {"/api/wallet-management/personal-opening/mobile/send-verify-code/general"}, method = {RequestMethod.POST})
public void sendVerifyCodeGeneral(@Valid @RequestBody @NotNull SendVerifyCodeGeneralParamVo param) {
personalOpeningService.sendVerifyCodeGeneral(
param.getChannelNo(), param.getTradeNo(), param.getTradeType(),
param.getMobile(), param.getCardNo(), param.getCardName(), param.getIdNo());
}
```
`SendVerifyCodeGeneralParamVo` 字段:`cardName`(银行卡户名)、`cardNo`(银行卡号)、`channelNo`(渠道编号)、`idNo`(证件号码)、`mobile`(手机号,明文)、`tradeNo`(交易码,按业务场景区分,如个人开户为222101、企业开户为222103)、`tradeType`(短信类型,固定221506)。
本次设计即按该新接口重新对接"发送验证码"环节,同时收敛此前"前端不持有明文手机号"的假设设计,改为前端持有明文手机号、自行计算掩码展示。
## 范围
- 仅改动 `fryl_h5` 工程中"发送验证码"这一个环节的接口调用与相关字段传递/展示。
- 不改动 `mobile/submit`(验证码校验+提交开户)接口调用方式,该接口请求体 `{applicationNo, verifyCode}` 保持不变。
- 不涉及企业开户、贷款申请确认扫码等其它移动端确认场景(即使新接口是"通用"接口,本次也只在个人开户H5这一处对接)。
- 不改动管理后台侧任何代码。
## 接口契约变更
### 废弃
```
POST /api/wallet-management/personal-opening/mobile/send-verify-code
Body: { applicationNo }
```
### 新接口
```
POST /api/wallet-management/personal-opening/mobile/send-verify-code/general
Body: {
channelNo, // 渠道编号
tradeNo, // 交易码,个人开户场景固定 "222101"
tradeType, // 短信类型,固定 "221506"
mobile, // 手机号,明文
cardNo, // 银行卡号
cardName, // 银行卡户名
idNo // 证件号码
}
Response: 标准 { code, message, data: null }
```
### 各字段取值方案
| 字段 | 取值方式 | 说明 |
| --- | --- | --- |
| `tradeType` | 前端硬编码常量 `"221506"` | 与业务场景无关的固定值,不依赖页面数据 |
| `tradeNo` | 前端硬编码常量 `"222101"` | 个人开户场景专属编码;仅在 `fryl_h5` 内部写死,不做成可配置项(YAGNI,当前只有一个场景) |
| `mobile` | `mobile/draft-detail` 响应新增返回的明文手机号 | 见下方"配套契约调整" |
| `channelNo` | `mobile/draft-detail` 响应新增返回 | 申请单创建时已确定渠道,与发送该 header/参数的通用规则("页面无渠道上下文则不传")不冲突——这里是有上下文(来自申请单),只是该上下文并非来自页面交互而是来自后端数据 |
| `cardNo` | 复用 `mobile/draft-detail` 已假设的 `bankCardNumber` 字段 | 该字段此前已存在于契约假设中,只是没有透传到发码环节 |
| `cardName` | `mobile/draft-detail` 响应新增返回的独立字段 `bankCardName` | 不复用 `customerName`(即便个人账户场景数值通常相同,仍作为独立字段返回,避免语义耦合) |
| `idNo` | 复用真实 swagger 已有字段 `certificateNumber` | 无需新增假设 |
### 配套契约调整:`mobile/draft-detail` 响应字段
- **新增** `mobile`(明文手机号)、`channelNo`(渠道编号)、`bankCardName`(银行卡户名)。
- **移除** 此前假设的 `mobilePhoneMask`(掩码手机号)字段假设——不再要求后端做掩码计算,前端改为拿到明文 `mobile` 后自行计算掩码展示。
## 掩码展示方案(设计原则调整)
此前设计原则是"前端不持有明文手机号,页面全程只展示后端算好的掩码值"。本次后端新接口明确要求前端持有并回传明文手机号,该原则不再适用,调整为:
> 前端可以持有明文手机号(仅用于调用发码接口的请求参数),但**页面展示时必须做掩码处理**,不得在 UI 上直接渲染明文手机号。
新增 `fryl_h5/src/utils/maskPhone.js`:
```js
export function maskMobilePhone(phone) {
if (!phone || phone.length < 8) return phone || ''
return `${phone.slice(0, 3)}****${phone.slice(-4)}`
}
```
算法与后端 `PersonalOpeningMobileController.maskMobilePhone` 参考实现一致(保留前3位+后4位,中间固定4个 `*`),供步骤1"信息核对"页和步骤3"手机验证"页展示时统一调用。
## 数据流
```
PersonalOpenMobile.vue (onMounted)
→ fetchDraftDetailApi(applicationNo)
→ draftDetail = { ..., mobile, channelNo, bankCardName, bankCardNumber, certificateNumber, ... }
InfoConfirmStep.vue:
手机号展示 = maskMobilePhone(draftDetail.mobile)
PersonalOpenMobile.vue → <SmsVerifyStep> 传入:
mobile / channel-no / card-no(=bankCardNumber) / card-name(=bankCardName) / id-no(=certificateNumber)
SmsVerifyStep.vue:
手机号展示 = maskMobilePhone(props.mobile)
点击"获取验证码" → sendVerifyCodeApi({ channelNo, mobile, cardNo, cardName, idNo })
(tradeNo/tradeType 由 api 函数内部硬编码补充)
点击"确认提交" → submitApplicationApi(applicationNo, verifyCode) // 不变
```
## 错误处理
与现有全局规则一致,不新增特殊分支:
- 响应 `code !== 200`:由 `fryl_h5/src/api/request.js` 的响应拦截器统一弹窗提示 `message`,发码按钮不进入倒计时态(`sending` 置回 `false`),允许用户重试。
- 网络异常:同现有拦截器统一提示,不新增处理逻辑。
- 新接口请求体中的必填字段若因 `draftDetail` 缺失导致为空(如后端暂未及时补充 `mobile`/`channelNo`/`bankCardName`),前端不做额外的前置校验拦截——按现有项目规范,字段缺失由后端接口返回的错误信息驱动提示,前端不臆造校验规则。
## 需要改动的文件清单
1. **`fryl_h5/src/api/personalOpen.js`** —— `sendVerifyCodeApi` 改签名为 `({ channelNo, mobile, cardNo, cardName, idNo })`,请求地址改为 `/api/wallet-management/personal-opening/mobile/send-verify-code/general`,内部补充固定 `tradeNo: '222101'`、`tradeType: '221506'`。
2. **`fryl_h5/src/utils/maskPhone.js`**(新建)—— `maskMobilePhone` 掩码工具。
3. **`fryl_h5/src/views/steps/InfoConfirmStep.vue`** —— 手机号展示改为 `maskMobilePhone(detail?.mobile)`
4. **`fryl_h5/src/views/PersonalOpenMobile.vue`** —— 给 `<SmsVerifyStep>` 补充传入 `mobile`/`channel-no`/`card-no`/`card-name`/`id-no`,不再传 `mobile-phone-mask`
5. **`fryl_h5/src/views/steps/SmsVerifyStep.vue`** —— props 增加 `mobile`/`channelNo`/`cardNo`/`cardName`/`idNo`(移除 `mobilePhoneMask`),页面内用 `maskMobilePhone(props.mobile)` 展示;`handleSendCode` 改为调用新签名的 `sendVerifyCodeApi`
6. **`mock-server/routes/personalOpenMobile.js`** ——
- `draft-detail` mock 响应:新增 `mobile`(取 `application.mobilePhone` 明文)、`channelNo`(取 `application.channelNo`)、`bankCardName`(mock 取 `application.customerName`);移除 `mobilePhoneMask` 字段及对应的 mock 内 `maskPhone` 辅助函数(掩码计算职责已移交前端)。
- 发码路由地址改为 `/wallet-management/personal-opening/mobile/send-verify-code/general`,不再依赖 `applicationNo` 定位申请单(新接口本身是与申请单解耦的通用接口),改为直接以请求体 `mobile` 作为 `genSmsCode`/`verifySmsCode` 的 key(`businessType` 沿用 `WALLET_PERSONAL_OPEN_MOBILE`)。
- `submit` 路由校验验证码时,`verifySmsCode` 的 key 同步从 `applicationNo` 改为 `application.mobilePhone`,与发码环节的 key 保持一致。
7. **《缺失的后端接口.md》Part 12** ——
- 第93条补充 `mobile`(明文)、`channelNo`、`bankCardName` 三个新假设字段,删除 `mobilePhoneMask` 假设描述。
- 第94条更新为:后端已提供正式接口 `POST .../mobile/send-verify-code/general`,记录实际请求体字段及 `tradeNo`/`tradeType` 固定值来源,不再标注为"前端假设,待确认"。
8. **`docs/superpowers/specs/2026-07-27-personal-open-mobile-h5-design.md`** —— "接口契约"章节第2点同步更新为新接口地址/参数,"步骤1"字段表的"手机号"来源由 `mobilePhoneMask` 改为 `mobile`(前端掩码展示)。
## 范围边界(本次不做)
- 不新增"银行卡户名"在步骤1"信息核对"页的展示行(超出本次"验证码接口订正"范围,如后续产品要求展示可另行处理)。
- 不改动 `mobile/submit` 接口的请求参数结构。
- 不处理企业开户、贷款确认等其它场景是否也要切换到 `send-verify-code/general` 通用接口(不在本次任务范围内,超出范围不擅自扩展)。
- 不新增前端对 `mobile`/`channelNo`/`bankCardName` 缺失情况的额外前置校验或兜底逻辑。