用户端小程序 实名认证 开发设计文档

  • 模块:ruoyi-user(会员业务)+ ruoyi-gateway(路由/鉴权头注入)+ hugray-admin(后台主体认证审核)
  • 日期:2026-10-13
  • 需求来源:"点击实名认证跳转到实名认证页面,只做基础的身份证格式校验和姓名必填校验不做三方真实验证,提交后的数据需要到管理后台的主体认证审核列表"
  • 关联前端:hugray-user-miniapp(pages/profile/edit.vue 入口、新增 pages/profile/certification.vue)
  • 关联后台:hugray-admin/src/views/member/certification/index.vue(主体认证审核列表,菜单 2202)
  • 关联文档:doc/design/用户端_我的资料_头像上传_20261013.md(同类 C 端写接口范式)、doc/design/用户端_我的资料_20261013.html(本页 UI 参考)
  • 数据权威源:192.168.110.111:3306/hugray-account(实际表结构优先于本文档)

一、功能概述

「我的资料」页(pages/profile/edit.vue)「实名认证」行当前点击仅弹占位 toast,本次改为**跳转到实名认证页**:

  1. 新增 C 端实名认证页 pages/profile/certification.vue(自定义导航栏 + 姓名 / 身份证号两个输入 + 底部「提交」按钮,见原型图);
  2. 页面**只做基础校验**:姓名必填、身份证号为 18 位标准格式;**不接入任何三方实名核验**;
  3. 提交后落 account_certification_apply(subject_type='1' 个人、audit_status='0' 待审核),并联动 account_user.auth_status='1'(审核中),使其出现在后台**主体认证审核列表**;
  4. 「我的资料」页实名认证徽标改为按后端下发的 authStatus 显示真实状态(未认证 / 认证中 / 已认证 / 认证失败),形成闭环。

后台「审」链路(列表 / 详情 / 通过 / 驳回)**已存在且无需改动**:AccountCertificationApplyController(/account/user/certification/**,权限 member:certification:*)、AccountCertificationApplyServiceImpl(审核 + auth_status 联动 + 通过后落 account_certification)。本次只补 C 端"提交"这一缺口。


二、现状与关键约束

2.1 缺口:只有"审"没有"增"

AccountCertificationApplyController 类注释明确:*"本模块只有"审"没有"增删":申请由 C 端会员提交(不在本仓库范围)"*。C 端提交接口与页面均需**新建**。

2.2 数据模型已就绪(无 DDL 变更)

表 用途 关键列
account_certification_apply 每次提交的申请(驳回重提多一条),**本次落这张表** id(自增主键兼申请号)、user_id、subject_type、real_name、id_card、audit_status、del_flag、create_by、create_time
account_certification 当前生效认证资料,审核**通过**时由服务层落一份 审核通过后写入,本次不直接写
account_user.auth_status 会员整体认证状态 0未认证 1审核中 2已认证 3认证失败

状态联动口径(见 DDL 注释):**提交申请 → auth_status='1';通过 → '2';驳回 → '3'**。通过/驳回联动已由既有审核服务实现,本次只补"提交 → '1'"。

2.3 主键策略

account_certification_apply.id 为 bigint AUTO_INCREMENT(既有表,非雪花),故 insert 走 useGeneratedKeys="true" keyProperty="id",与既有 insertCertification 一致。

2.4 认证状态取值必须收口

状态/类型取值语义已在多处存在,本次沿用既有字符串约定(与 AccountCertificationApplyServiceImpl 中的 AUDIT_PASS="1" 等常量同风格),在服务层抽为常量,不散落魔法值。

2.5 路径规划

  • 网关注入 user_id;SecurityUtils.getUserId() 取会员身份,**C 端接口不接受前端传 userId**(防水平越权)。
  • C 端个人中心控制器 AccountProfileController(@RequestMapping("/user"),外部 /miniapp/account/user/**)已承载 /info、/avatar,本次**在其上新增** POST /user/certification(外部 POST /miniapp/account/user/certification)。
  • 与后台 AccountCertificationApplyController(外部 /account/user/certification/**)**前缀不同域**(/miniapp/account vs /account),无冲突。

三、接口设计

统一响应体:C 端 R<T>(成功 code=200);后台 AjaxResult / TableDataInfo。

3.1 提交实名认证(本次新增)

  • 外部路径:POST /miniapp/account/user/certification(网关 StripPrefix=2 → 服务内 POST /user/certification)
  • 鉴权:需登录。路径不在网关白名单,未登录由 AuthFilter 直接返回 401,前端 request.js 自动跳登录页;**后端不做未登录分支**。
  • 入参:JSON,会员身份取自网关注入的 user_id 头,**不接受前端传 userId**
字段 类型 必填 校验 说明
realName String 是 @NotBlank + @Size(max=64) 姓名(对应 real_name varchar(64))
idCard String 是 @NotBlank + 18 位格式 @Pattern + @Size(max=32) 身份证号(对应 id_card varchar(32))
  • 出参:R<Void>(成功即 code=200)
  • 错误提示:姓名不能为空 / 姓名过长 / 身份证号不能为空 / 身份证号格式不正确 / 已有实名认证申请正在审核中,请勿重复提交

身份证号基础格式正则(18 位:地区码 6 位 + 出生日期 + 顺序码 + 校验位;**只校验结构与日期范围,不校验校验码、不核验真实性**):

^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$

3.2 查询会员信息(本次扩展)

  • 外部路径:GET /miniapp/account/user/info(既有)
  • 改动:出参 AccountUserInfoVo 新增 authStatus 字段(account_user.auth_status 原值:0/1/2/3),供「我的资料」页显示实名认证真实状态。手机号仍只下发脱敏值。

3.3 后台审核链路(既有,不改)

GET /account/user/certification/list(member:certification:list)、GET /account/user/certification/detail/{id}、PUT /account/user/certification/audit(member:certification:audit)。C 端提交的申请将直接出现在该列表(subject_type='1' 个人、audit_status='0' 待审核,列表按待审核在前排序)。


四、数据库设计

库:hugray-account。

无 DDL 变更:account_certification_apply 与 account_user.auth_status 均已存在,本次仅插入 / 更新。


五、流程设计

5.1 前端提交链路(pages/profile/certification.vue)

STEP 1: onShow 校验登录态(ensureLogin),未登录跳登录页
STEP 2: 用户填写姓名 / 身份证号
STEP 3: 点「提交」→ 本地校验:姓名非空(trim)且 ≤64;身份证号匹配 18 位正则
STEP 4: 校验不通过则 toast 提示并中止,不发请求
STEP 5: 调 submitCertification(realName, idCard) → POST /miniapp/account/user/certification
STEP 6: 成功后 toast「提交成功,等待审核」并 navigateBack 回我的资料页;失败提示后端错误信息

5.2 后端提交(AccountCertificationApplyServiceImpl.submitPersonalCertification)

STEP 1: 校验 userId 非空(未登录由网关拦截,此处兜底),为空抛业务异常
STEP 2: 防重复提交:该会员若存在 audit_status='0'(待审核)且未删除的申请,
        抛「已有实名认证申请正在审核中,请勿重复提交」
STEP 3: 组装申请快照:subject_type='1'(个人)、real_name、id_card、
        audit_status='0'、create_by=登录用户名
STEP 4: insertCertificationApply 落 account_certification_apply(自增回填 id)
STEP 5: 联动 account_user.auth_status='1'(审核中,调既有 updateUserAuthStatus 单列语句)
STEP 6: 以上 STEP 4/5 同事务(@Transactional),任一失败整体回滚

5.3 状态流转(端到端)

C 端提交 ──► account_certification_apply.audit_status='0'  +  account_user.auth_status='1'
                    │
后台「通过」──► audit_status='1' + auth_status='2' + 落 account_certification(既有逻辑)
后台「驳回」──► audit_status='2' + auth_status='3' + 记审核意见(既有逻辑)

六、枚举与状态

名称 取值 说明
subject_type 主体类型 1 个人 / 2 个体工商户 / 3 企业 本次固定提交 1(个人实名);企业认证入口本期仍为占位
audit_status 审核状态 0 待审核 / 1 已通过 / 2 已驳回 提交置 0,审核改 1/2
auth_status 会员认证状态 0 未认证 / 1 审核中 / 2 已认证 / 3 认证失败 提交置 1;通过置 2;驳回置 3

「我的资料」页徽标与 auth_status 映射:0→未认证、1→认证中、2→已认证、3→认证失败。


七、影响范围与文件清单

新增(ruoyi-user)

文件 说明
domain/dto/AccountCertificationApplyDto.java C 端实名认证入参(realName / idCard,含校验注解与字段注释)
src/test/.../AccountCertificationApplyServiceImplTest.java 提交服务单测(正常 / 待审核重复提交拒绝 / 状态联动)

修改(ruoyi-user)

文件 改动
controller/AccountProfileController.java 注入 IAccountCertificationApplyService;新增 POST /user/certification
service/IAccountCertificationApplyService.java 新增 submitPersonalCertification(Long userId, String realName, String idCard)
service/impl/AccountCertificationApplyServiceImpl.java 实现提交(防重校验 + 落申请 + 联动 auth_status='1',@Transactional)
mapper/AccountCertificationApplyMapper.java 新增 insertCertificationApply(AccountCertificationApply)、countPendingApplyByUserId(Long userId)
resources/mapper/user/AccountCertificationApplyMapper.xml 新增上述两条 SQL
domain/vo/AccountUserInfoVo.java 新增 authStatus 字段
service/impl/AccountUserServiceImpl.java getAccountUserInfo 填充 authStatus
src/test/.../AccountUserServiceImplTest.java 补充 authStatus 下发断言

新增(前端 hugray-user-miniapp)

文件 说明
pages/profile/certification.vue 实名认证页(自定义导航栏 + 姓名/身份证号输入 + 底部提交;沿用 edit.vue 的导航与按钮风格)

修改(前端 hugray-user-miniapp)

文件 改动
pages.json 注册 pages/profile/certification(navigationStyle: custom)
api/index.js 新增 submitCertification(realName, idCard)(→ /miniapp/account/user/certification)
pages/profile/edit.vue 「实名认证」行由 handlePlaceholder 改为 uni.navigateTo 跳认证页;realNameStatus 改为按 /info 返回的 authStatus 映射

网关无需改动:/miniapp/account/** 路由已存在。


八、边界与异常清单

# 场景 处理
1 未登录访问 网关 AuthFilter 返回 401,前端跳登录页;后端不做分支
2 user_id 头缺失/为空 服务内兜底判空,抛业务异常(不 NPE)
3 姓名为空 / 纯空格 DTO @NotBlank 拦截,提示「姓名不能为空」
4 姓名超长(>64) DTO @Size 拦截,提示「姓名过长」
5 身份证号为空 DTO @NotBlank 拦截,提示「身份证号不能为空」
6 身份证号格式非法(非 18 位 / 含非法字符 / 非 X 结尾) DTO @Pattern 拦截,提示「身份证号格式不正确」
7 已有待审核申请再次提交 服务层 countPendingApplyByUserId>0 时抛「已有实名认证申请正在审核中,请勿重复提交」
8 落库或状态联动失败 同一事务回滚,返回统一错误,不产生半更新
9 越权 会员身份取自令牌注入头,不接受前端传 userId,天然防水平越权
10 三方实名核验 本期明确不做,仅基础格式校验

九、规范符合性说明

  • 分层:Controller 只取身份 + 转发,业务在 Service,SQL 在 Mapper;Controller 不直接调 Mapper。
  • 命名:submitPersonalCertification / insertCertificationApply / countPendingApplyByUserId 见名知意;DTO 与 Entity 分离。
  • 注释:DTO / Mapper 方法 / Service 方法具备 Javadoc;核心流程按 // STEP x: 分步注释;实体状态字段注明取值。
  • 魔法值:subject_type、audit_status、auth_status 取值抽为常量;错误提示集中。
  • 事务:提交接口 insert + 联动更新 同事务,保证一致性。
  • 幂等/防重:以待审核申请为闸门,拒绝重复提交。
  • 数据库:无 DDL;无 SELECT *;无 ${} 拼接;无循环查库。
  • 单测:覆盖提交正常、重复提交拒绝、状态联动;Mockito Mock Mapper,不依赖真实库。

十、已确认决策

  1. 状态回显:**已定** —— 扩展 AccountUserInfoVo.authStatus,「我的资料」页徽标显示真实状态(未认证/认证中/已认证/认证失败)。
  2. 重复提交:**已定** —— 存在「待审核」申请时**拒绝并提示**,不新增重复单。
  3. 核验深度:**已定** —— 仅姓名必填 + 身份证号 18 位格式校验,**不做三方实名核验**。
  4. 入口范围:**已定** —— 本期只做「实名认证」(个人 subject_type='1');「企业/个体户认证」入口维持占位。

十一、增量需求:认证状态回填与只读(2026-10-13 补充)

11.1 需求描述

进入实名认证页时,按当前认证状态**回填**该会员已提交的资料,并区分交互:

当前状态 姓名 / 身份证号 底部按钮(文案 / 可点性) 驳回原因
未认证(无申请) 空,可编辑 「提交」/ 可点 不展示
认证中(待审核) 回填,**只读** 「审核中」/ 置灰不可点 不展示
认证成功(已通过) 回填,**只读** 「已认证」/ 置灰不可点 不展示
认证失败(已驳回) 回填,**可编辑** 「重新提交」/ 可点 在身份证号下方展示

已确认决策:认证成功同样置灰不可点(已认证无需重复提交);认证中 / 认证成功输入框只读不可编辑。

11.2 接口设计(新增:查询当前认证状态)

  • 外部路径:GET /miniapp/account/user/certification(网关 StripPrefix=2 → 服务内 GET /user/certification)
  • 与既有 POST /miniapp/account/user/certification(提交)**同路径不同方法**,无需新增路由。
  • 鉴权:需登录;会员身份取自网关注入的登录态,**不接受前端传 userId**。

取该会员**最近一次**申请(account_certification_apply 按 id desc limit 1,只取 del_flag='0'),出参 AccountCertificationStatusVo:

字段 类型 说明
realName String 姓名(回填用)
idCard String 身份证号(回填用)
auditStatus String 0待审核 1已通过 2已驳回;**为空表示从未提交**
auditRemark String 驳回原因(audit_status='2' 时有值)

无申请时返回**空字段对象**(各字段为 null),前端按「未认证」处理,不返回 404。

11.3 前端状态映射

auditStatus → 页面状态:'0'→认证中、'1'→认证成功、'2'→认证失败、空/缺省→未认证。

  • 只读 & 置灰条件:页面状态 ∈ {认证中, 认证成功};
  • 驳回原因展示条件:页面状态 = 认证失败 且 auditRemark 非空;
  • 按钮文案:未认证→「提交」、认证中→「审核中」、认证成功→「已认证」、认证失败→「重新提交」(未匹配兜底「提交」)。

11.4 影响范围(增量文件清单)

新增(ruoyi-user)

文件 说明
domain/vo/AccountCertificationStatusVo.java C 端认证状态回填出参(realName / idCard / auditStatus / auditRemark)

修改(ruoyi-user)

文件 改动
mapper/AccountCertificationApplyMapper.java 新增 selectLatestApplyByUserId(Long userId)
resources/mapper/user/AccountCertificationApplyMapper.xml 新增上述查询 SQL(取最近一条)
service/IAccountCertificationApplyService.java 新增 getCertificationStatus(Long userId)
service/impl/AccountCertificationApplyServiceImpl.java 实现查询:取最近一条申请映射为 VO,无则返回空对象
controller/AccountProfileController.java 新增 GET /user/certification
src/test/.../AccountCertificationApplyServiceImplTest.java 补充查询用例(有申请回填 / 无申请空对象)

修改(前端 hugray-user-miniapp)

文件 改动
api/index.js 新增 getCertificationStatus()(→ GET /miniapp/account/user/certification)
pages/profile/certification.vue 进入页面拉取状态并回填;认证中/成功输入框只读、按钮置灰且文案随状态变化(提交/审核中/已认证/重新提交);认证失败展示驳回原因

无需 DDL、无需网关改动(/miniapp/account/** 路由已存在)。

11.5 边界与异常(增量)

# 场景 处理
1 从未提交(查询返回空对象) 页面按未认证处理,空表单可编辑可提交
2 查询接口异常 静默降级为未认证(空表单),不阻塞页面
3 认证中 / 认证成功 输入框 disabled 只读,按钮置灰且点击直接 return
4 认证失败但驳回原因为空 不展示驳回原因区域(仅可编辑重提)
5 认证失败后重新提交成功 回退上一页;再次进入时按新的「待审核」状态回填只读