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

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),前端不做额外的前置校验拦截——按现有项目规范,字段缺失由后端接口返回的错误信息驱动提示,前端不臆造校验规则。

需要改动的文件清单

  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(明文)、channelNobankCardName 三个新假设字段,删除 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 缺失情况的额外前置校验或兜底逻辑。