9.2 KiB
个人开户移动端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),接口地址与请求参数均与此前假设不同:
@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:
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),前端不做额外的前置校验拦截——按现有项目规范,字段缺失由后端接口返回的错误信息驱动提示,前端不臆造校验规则。
需要改动的文件清单
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'。fryl_h5/src/utils/maskPhone.js(新建)——maskMobilePhone掩码工具。fryl_h5/src/views/steps/InfoConfirmStep.vue—— 手机号展示改为maskMobilePhone(detail?.mobile)。fryl_h5/src/views/PersonalOpenMobile.vue—— 给<SmsVerifyStep>补充传入mobile/channel-no/card-no/card-name/id-no,不再传mobile-phone-mask。fryl_h5/src/views/steps/SmsVerifyStep.vue—— props 增加mobile/channelNo/cardNo/cardName/idNo(移除mobilePhoneMask),页面内用maskMobilePhone(props.mobile)展示;handleSendCode改为调用新签名的sendVerifyCodeApi。mock-server/routes/personalOpenMobile.js——draft-detailmock 响应:新增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 保持一致。
- 《缺失的后端接口.md》Part 12 ——
- 第93条补充
mobile(明文)、channelNo、bankCardName三个新假设字段,删除mobilePhoneMask假设描述。 - 第94条更新为:后端已提供正式接口
POST .../mobile/send-verify-code/general,记录实际请求体字段及tradeNo/tradeType固定值来源,不再标注为"前端假设,待确认"。
- 第93条补充
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缺失情况的额外前置校验或兜底逻辑。