Skip to content

实名认证技术方案 ​

1. 设计原则 ​

  • 多态认证主体:认证记录通过 verifiable_type + verifiable_id 多态关联,当前接入 User,未来可扩展至其他主体(如商户、合作伙伴),认证记录层无需改动。
  • 状态同步到主体主表:用户表 (users) 直接存储 identity_type 和 identity_status 两个字段,查询认证状态零 Join;认证记录表 (identity_verifications) 仅保留完整审核轨迹。
  • 个人与企业双类型:支持 personal(个人)和 enterprise(企业)两种认证类型,共用同一张记录表,通过 type 字段区分必填字段。
  • 驳回原因字典化:驳回原因通过 identity_reject_reason 字典管理,管理员从预设原因中选择,支持灵活扩展。
  • 事件驱动通知:审核通过/驳回分别派发 IdentityVerified / IdentityRejected 事件,支持实时广播(Private Channel)和异步队列处理。
  • 认证有效期:审核通过时自动设置 3 年有效期(expires_at),到期后需重新认证。

2. 代码结构 ​

app/
├── Enums/
│   ├── IdentityStatus.php                        # 实名认证状态枚举(unsubmitted/pending/approved/rejected)
│   ├── IdentityType.php                          # 认证类型枚举(personal/enterprise)
│   └── ReviewStatus.php                          # 通用审核状态枚举(含 review,评论模块使用)
├── Models/User/
│   └── IdentityVerification.php                  # 认证记录模型(多态 verifiable + BelongsTo admin)
├── Services/
│   └── IdentityVerificationService.php           # 认证服务(提交/通过/驳回编排)
├── Events/User/
│   ├── IdentityVerified.php                      # 认证通过事件(ShouldBroadcast + ShouldQueue)
│   └── IdentityRejected.php                      # 认证驳回事件(ShouldBroadcast + ShouldQueue)
├── Http/Controllers/
│   ├── Api/V1/User/
│   │   └── IdentityController.php                 # 用户端认证接口(show/status/submit)
│   └── Admin/
│       └── IdentityVerificationController.php     # 管理端认证审核接口(index/stats/show/approve/reject)
├── Http/Requests/
│   ├── Api/V1/User/Identity/
│   │   └── SubmitIdentityRequest.php              # 用户提交认证请求校验
│   └── Admin/Identity/
│       ├── IdentityIndexRequest.php               # 管理端列表筛选请求
│       └── RejectIdentityRequest.php              # 管理端驳回请求校验
├── Http/Resources/
│   ├── Api/V1/
│   │   └── IdentityVerificationResource.php      # 用户端认证资源(脱敏展示)
│   └── Admin/
│       ├── IdentityVerificationResource.php       # 管理端认证资源(含完整信息 + 驳回原因标签)
│       └── IdentityVerificationStatResource.php   # 管理端认证统计资源
└── Policies/
    └── IdentityVerificationPolicy.php             # 认证记录策略

3. 数据模型 ​

3.1 users 表(主体表) ​

用户表直接存储认证状态热数据,查询零 Join:

字段名类型说明
identity_typeVARCHAR(16)实名认证类型:personal / enterprise,null 表示未认证
identity_statusVARCHAR(16)实名认证状态(枚举 ReviewStatus,默认 unsubmitted)

3.2 identity_verifications 表(认证记录表) ​

字段名类型说明
idBIGINT主键
verifiable_typeVARCHAR(100)申请人类型(多态,如 App\Models\User)
verifiable_idBIGINT申请人 ID
typeVARCHAR(16)认证类型(personal / enterprise)
statusVARCHAR(16)审核状态(pending / approved / rejected)
real_nameVARCHAR(64)真实姓名(个人认证)
id_card_noVARCHAR(32)身份证号(密文存储,响应隐藏)
id_card_frontVARCHAR身份证人像面图片路径
id_card_backVARCHAR身份证国徽面图片路径
handheldVARCHAR手持身份证图片路径
company_nameVARCHAR(128)企业名称(企业认证)
credit_codeVARCHAR(32)统一社会信用代码(18 位)
legal_person_nameVARCHAR(64)法人姓名(企业认证)
legal_person_id_card_noVARCHAR(32)法人身份证号(密文存储,响应隐藏)
business_licenseVARCHAR营业执照图片路径
opening_permitVARCHAR开户许可证图片路径
bank_accountVARCHAR(64)对公账户(响应隐藏)
bank_nameVARCHAR(64)开户行
admin_idBIGINT审核管理员 ID
reject_reasonVARCHAR(32)驳回原因(字典 identity_reject_reason 的 value)
reject_remarkTEXT驳回备注
submitted_atTIMESTAMP提交时间
reviewed_atTIMESTAMP审核时间
expires_atTIMESTAMP认证有效期(通过后自动设置 +3 年)
created_atTIMESTAMP创建时间
updated_atTIMESTAMP更新时间

索引:(verifiable_type, verifiable_id) 复合索引、type 索引、status 索引、admin_id 索引。

敏感字段(id_card_no、legal_person_id_card_no、bank_account)通过模型 #[Hidden] 属性在所有响应中自动隐藏。

3.3 相关枚举 ​

IdentityType(认证类型):

值说明
personal个人认证
enterprise企业认证

IdentityStatus(实名认证状态):

实名认证模块使用独立的 IdentityStatus 枚举,不复用 ReviewStatus(后者包含 review 状态,供评论模块使用)。

值说明标签样式
unsubmitted未提交info
pending待审核warning
approved已通过success
rejected已驳回danger

3.4 驳回原因字典(identity_reject_reason) ​

迁移文件预置以下字典项,管理员也可通过后台字典管理动态扩展:

值标签标签样式
id_blurry证件模糊不清warning
mismatch证件信息不符danger
expired证件已过期info
invalid证件无效danger
incomplete证件资料不完整warning
other其他原因info

4. 业务流程 ​

4.1 用户提交认证 ​

  1. 用户通过 POST /api/v1/user/identity 提交认证信息,type 指定个人或企业。
  2. 服务层在事务内加行锁检查:同一用户同时仅允许一条 pending 记录,否则返回 422。
  3. 创建认证记录(状态 pending),同步用户表 identity_status 为 pending。
  4. 返回认证记录资源。

个人认证必填字段:real_name、id_card_no、id_card_front企业认证必填字段:company_name、credit_code、legal_person_name、legal_person_id_card_no、business_license

身份证号通过 IdCardRule 校验合法性;统一社会信用代码通过正则校验 18 位格式。

4.2 管理员审核通过 ​

  1. 管理员通过 POST /admin/identity-verifications/{verification}/approve 审核通过。
  2. 服务层在事务内:
    • 更新记录状态为 approved,记录 admin_id、reviewed_at、expires_at(+3 年),清空驳回字段。
    • 同步用户表 identity_type 和 identity_status 为 approved。
    • 派发 IdentityVerified 事件。
  3. 前置校验:已通过或已驳回的记录不可重复审核,返回 422。

4.3 管理员审核驳回 ​

  1. 管理员通过 POST /admin/identity-verifications/{verification}/reject 审核驳回。
  2. 请求参数:reason(必填,须为字典 identity_reject_reason 中已启用的 value)、remark(可选,最多 500 字)。
  3. 服务层在事务内:
    • 更新记录状态为 rejected,记录 admin_id、reviewed_at、reject_reason、reject_remark。
    • 同步用户表 identity_status 为 rejected。
    • 派发 IdentityRejected 事件。
  4. 用户可在驳回后重新提交认证。

4.4 状态流转图 ​

              提交认证
                 │
                 ▼
            ┌─────────┐
            │ pending  │
            └────┬────┘
                 │
          管理员审核
        ├──────────────┐
        ▼              ▼
  ┌──────────┐   ┌──────────┐
  │ approved │   │ rejected │(可重新提交)
  └──────────┘   └──────────┘
     (终态,设置有效期)

5. 事件与广播 ​

5.1 IdentityVerified(认证通过) ​

  • 实现:ShouldBroadcast + ShouldQueue + ShouldHandleEventsAfterCommit
  • 广播频道:PrivateChannel("User.{id}")
  • 用途:触发站内信通知、推送等异步处理

5.2 IdentityRejected(认证驳回) ​

  • 实现:ShouldBroadcast + ShouldQueue + ShouldHandleEventsAfterCommit
  • 广播频道:PrivateChannel("User.{id}")
  • 携带数据:reason(驳回原因)、remark(驳回备注)
  • 用途:触发站内信通知用户驳回原因

两个事件均在数据库事务提交后派发,确保数据一致性。广播频道通过 routes/channels.php 中的 Broadcast::channel('User.{id}', ...) 授权。

6. API 接口 ​

6.1 用户端接口(/api/v1/user/identity) ​

方法路径说明认证
GET/api/v1/user/identity当前用户最新一条认证记录Sanctum
GET/api/v1/user/identity/status简要认证状态Sanctum
POST/api/v1/user/identity提交/重新提交实名认证Sanctum

GET /api/v1/user/identity/status 响应:

json
{
  "is_verified": false,
  "identity_type": null,
  "identity_type_label": null,
  "latest_status": "rejected",
  "latest_status_label": "已驳回",
  "latest_reject_reason": "id_blurry",
  "latest_reject_remark": "身份证人像面照片模糊,请重新拍摄上传"
}

POST /api/v1/user/identity 请求参数:

字段类型必填条件说明
typestring必填认证类型:personal / enterprise
real_namestringtype=personal真实姓名(最长 64 字)
id_card_nostringtype=personal身份证号(校验合法性)
id_card_frontstringtype=personal身份证人像面图片路径
id_card_backstring可选身份证国徽面图片路径
handheldstring可选手持身份证图片路径
company_namestringtype=enterprise企业名称(最长 128 字)
credit_codestringtype=enterprise统一社会信用代码(18 位)
legal_person_namestringtype=enterprise法人姓名
legal_person_id_card_nostringtype=enterprise法人身份证号
business_licensestringtype=enterprise营业执照图片路径
opening_permitstring可选开户许可证图片路径
bank_accountstring可选对公账户
bank_namestring可选开户行

用户端响应通过 IdentityVerificationResource 返回,个人认证不返回企业字段,企业认证不返回个人字段,敏感字段(身份证号、对公账户)始终隐藏。

6.2 管理端接口(/admin/identity-verifications) ​

方法路径说明权限
GET/admin/identity-verifications认证记录列表(分页)identity-verifications.index
GET/admin/identity-verifications/stats认证统计identity-verifications.index
GET/admin/identity-verifications/{verification}认证记录详情identity-verifications.index
POST/admin/identity-verifications/{verification}/approve审核通过identity-verifications.edit
POST/admin/identity-verifications/{verification}/reject审核驳回identity-verifications.edit

列表筛选参数:

参数说明
verifiable_id申请人 ID
keyword关键词(匹配真实姓名/企业名称/信用代码/法人姓名/用户名/手机号)
type认证类型
status审核状态
start_date提交起始日期
end_date提交截止日期

统计接口响应:

json
{
  "total": 1280,
  "pending": 15,
  "today": 8,
  "approved": 1200,
  "rejected": 65
}

管理端响应通过 Admin\IdentityVerificationResource 返回,包含完整认证信息、审核管理员名称、驳回原因标签(从字典缓存读取,TTL 2 小时)。驳回原因标签映射通过 getRejectReasonMap() 方法带缓存读取,字典变更后可通过 flushRejectReasonMapCache() 清除缓存。

7. 安全与隐私 ​

  • 敏感字段隐藏:id_card_no、legal_person_id_card_no、bank_account 通过模型 #[Hidden] 在所有响应中自动隐藏。
  • 并发控制:用户提交认证时使用 lockForUpdate() 防止并发提交多条待审记录。
  • 事务一致性:提交、通过、驳回三个操作均在 DB::transaction 内执行,确保认证记录与用户表状态同步。
  • 权限控制:管理端审核接口通过 permission 中间件控制(identity-verifications.index / identity-verifications.edit)。
  • 驳回原因校验:驳回请求的 reason 字段在运行时从字典表动态读取可用值,字典禁用后旧值不可再选。
  • 认证有效期:审核通过时自动设置 expires_at(+3 年),到期后可通过定时任务或业务逻辑引导用户重新认证。

文档来源:PRD 实名认证模块