Skip to content

客户端小程序 — 人脸录入与重录

上级文档:客户端小程序


概述

对应主文档 §8.4、§9.4(合规)、§13.1

人脸录入是用户刷脸入场的前置条件。小程序负责采集照片并上传至云端,不在端上做识别。录入前需展示隐私说明。

防薅羊毛设计:人脸数据与用户账号强绑定,重录行为受到多层安全约束,防止用户通过更换人脸将会员权益转借他人。

页面路由

路由页面说明
/pages/face/guide人脸录入引导首次使用说明,可跳过
/pages/face/enroll人脸采集调起摄像头采集照片
/pages/face/liveness活体检测(可选)前置活体检测增强安全性
/pages/profile/face-manage人脸管理个人中心 → 重录入口(已入场成功后隐藏)

显示与隐藏规则

核心设计:用户不可查看自己已录入的人脸照片(避免隐私不安感),只展示录入状态和重录入口。

用户状态人脸管理入口重录按钮说明
未录脸显示「去录入」引导完成首次录入
已录脸、从未入场成功显示可点击(受安全约束)允许重录
已录脸、曾入场成功隐藏入口从个人中心菜单消失

入场成功判断:用户至少有一次刷脸进门记录(faceLog 存在成功的 enter 事件),即判定为「曾入场成功」。前端通过 GET /api/v1/user/face/status 接口的 hasSuccessfulEntry 字段判断。


录脸触发场景

场景行为强度
新用户首次登录引导页,可跳过弱引导
支付成功且未录脸支付结果页显示「完成人脸录入」按钮强引导
会员页查看且未录脸黄色提醒条「未录入人脸无法入场」中引导
个人中心 → 人脸管理始终显示人脸状态常驻入口

首次录入

引导页要素

  • 隐私说明文字(合规要求)
  • 拍摄要求提示(正对摄像头、确保光线充足、避免佩戴帽子墨镜)
  • 「开始录入」主操作按钮
  • 「暂时跳过」次操作

采集页要素

  • 摄像头实时预览(使用 camera 组件)
  • 人脸框辅助对齐
  • 底部提示文案(随质量检测结果动态变化)
  • 「拍照」按钮(通过前端质量检测后可点击)

前端质量检测

检测项条件不通过提示
正脸面部角度偏转 < 15°「请正对摄像头」
光线画面亮度在合理范围「光线太暗/太亮,请调整」
清晰度图像模糊度 < 阈值「请保持不动,画面模糊」
遮挡眼/鼻/嘴无遮挡「请摘下口罩/墨镜/帽子」
多人画面仅 1 张人脸「请确保只有您一人在画面中」

前端仅做基础质量检测,最终判断由云端 API 完成。前端通过后自动拍照上传。

录入结果

成功:提示「人脸录入成功」+ 「返回首页」按钮

失败:展示失败原因(如「未检测到人脸」)+ 「重新拍摄」按钮


重录人脸 — 安全机制

核心原则:用户可以合理地重录自己的脸(如剃须、发型变化等),但不能借此将会员权益转借给他人。以下多层机制协同防薅。

重录触发路径

  • 个人中心 → 人脸管理 → 「重新录入」(仅在从未入场成功时可见)

人脸数据存储策略

服务端永久保存用户的原始人脸最后一次录入的人脸,用于安全比对。

数据说明
originalFace用户首次录入时的人脸特征向量,永不覆盖
latestFace用户最近一次重录的人脸特征向量,每次重录覆盖更新

比对逻辑

重录时,云端同时比对新上传的人脸与 originalFacelatestFace

比对对象规则说明
新脸 vs originalFace相似度 ≥ 0.6确认仍是本人(防止用户 A 首次录入 → 重录换人 B)
新脸 vs latestFace相似度 ≥ 0.6确认与上次录入变化不大(如不满足,需走人工审核)

两项比对均通过才允许重录成功。任一不通过 → 拒绝并引导联系客服人工审核。

设计意图:原始人脸是最可信的基准(首次录入时攻击者尚未获利),最后一次人脸是最近的状态。双基准比对可同时防止「首次录入后换人」和「反复渐进换脸」。

重录安全约束(分层设计)

第 1 层:时间冷却

规则说明
冷却期两次重录之间至少间隔 24 小时
触发点上一次重录成功的时间戳
前端展示冷却期内显示「XX 小时后可重新录入」,按钮置灰
后端兜底即使前端绕过,后端同样校验并拒绝

目的:防止用户高频重录,限制薅羊毛的操作窗口。

第 2 层:有效期/体验卡冻结期

规则说明
有效会员期间如用户有未到期的会员权益,不允许重录人脸
体验卡已使用体验卡已进入健身房(次数已耗尽)后,不允许重录人脸
无权益 / 已过期允许重录

目的:最核心的防薅规则。有有效权益时冻结重录,从源头杜绝「录脸 → 换脸 → 他人进入」的漏洞。

前端展示逻辑

用户状态人脸管理入口重录按钮提示文案
无权益、无录脸显示「去录入」
无权益、已录脸、从未入场显示正常可点击「重新录入」
无权益、已录脸、曾入场成功隐藏
有有效月卡/季卡/年卡显示置灰 + 不可点击「会员有效期内不支持更换人脸」
有有效次卡(剩余次数 > 0)显示置灰 + 不可点击「次卡有效期内不支持更换人脸」
有效次卡但次数为 0显示正常可点击「重新录入」
体验卡已进入(次数耗尽)显示置灰 + 不可点击「体验卡已使用,不支持更换人脸」
体验卡已退款显示正常可点击「重新录入」
体验卡未使用(待生效)显示置灰 + 不可点击「体验卡有效期内不支持更换人脸」

有有效权益时用户尚未入场成功属于异常状态(刚购买还没来),此时保留入口但冻结重录。

第 3 层:人脸相似度比对(双基准)

规则说明
比对方式云端 API 同时对比新上传的人脸与原始人脸originalFace)和最后录入人脸latestFace
阈值两项比对均需 ≥ 0.6(行业通用阈值,可配置)
通过两项均 ≥ 0.6,允许重录
部分通过新脸 vs 原始 ≥ 0.6 但 vs 最后一次 < 0.6 → 提示「人脸变化较大,如需更换请联系客服人工审核」
不通过新脸 vs 原始 < 0.6 → 直接拒绝,「人脸验证失败,请联系客服人工审核」

目的:双基准比对。原始人脸是最可信的基准(首次录入时尚未产生权益),最后一次人脸反映近期状态。同时通过两项比对可防止「首次录本人 → 多次重录逐步换成他人」的渐进式攻击。

第 4 层:云端黑名单比对

规则说明
比对方式云端比对新上传的人脸特征与全局黑名单库
触发条件每次录入/重录时
命中处理拒绝录入,提示「人脸录入失败,请联系客服」
黑名单来源管理后台手动添加(因薅羊毛、违规等被封禁的用户)

第 5 层:工控机特征同步延迟

规则说明
同步机制重录成功后,最新特征(latestFace)通过 MQTT 推送至全部门店工控机
延迟容忍推送消息设计为替换型(非追加),确保旧特征被覆盖
离线门店工控机下次上线时全量同步,期间使用旧特征
并发保护同一用户同时只能有一个有效特征,新特征覆盖旧特征

重录完整流程

个人中心 → 人脸管理 → 点击「重新录入」(仅从未入场成功时可见)

  ├─ 前端检查冷却期(24h)
  │   └─ 未冷却 → 置灰按钮 + 倒计时提示

  ├─ 前端请求用户状态接口,检查权益冻结期
  │   └─ 有有效权益 → 置灰按钮 + 冻结提示

  ├─ 弹出确认:「重新录入将覆盖您当前的人脸数据,确认?」
  │   └─ 确认 → 进入采集页

  ├─ 拍照 + 前端质量检测
  │   └─ 通过 → 上传至云端

  ├─ 云端处理(按顺序执行)
  │   ├─ ❶ 冷却期校验(兜底)
  │   ├─ ❷ 权益冻结期校验(兜底)
  │   ├─ ❸ 人脸相似度比对(双基准:originalFace + latestFace,均 ≥ 0.6 通过)
  │   ├─ ❹ 全局黑名单比对
  │   ├─ ❺ 更新 latestFace,保留 originalFace 不变
  │   └─ ❻ MQTT 推送所有门店工控机同步最新特征

  ├─ 重录成功 → 提示「人脸更新成功」

  └─ 重录失败 → 提示原因 + 引导联系客服(如适用)

人脸冻结期的运营放行

管理后台提供人工审核重录功能,用于特殊场景(如用户确实面貌变化很大、整容等)。

管理后台 — 人脸重录审核

功能说明
审核入口会员管理 → 用户详情 → 人脸管理 → 「审核重录申请」
用户申请路径用户在小程序点击「更换人脸被拒绝」→ 联系客服 → 客服在管理后台提交审核
审核流程管理员上传用户本人持身份证照片(或人工核验)→ 确认后解锁重录权限
审核通过用户 24 小时内可重录一次,过期需重新申请
审核拒绝告知用户原因

特殊场景处理

场景处理方式
用户面貌自然变化(剃须、发型)正常重录,相似度比对通常可通过
用户整容需联系客服,管理后台人工审核解锁
用户手机被盗 + 账号泄露用户联系客服冻结账号 → 客服清除人脸数据 → 用户重新登录录入
非本人操作(账号借给他人)相似度比对不通过,拒绝重录

合规要求

  • 录入前必须展示隐私说明,明确告知人脸数据用途
  • 用户可跳过首次录入,不影响其他功能使用
  • 重录受安全约束(冷却期 + 冻结期 + 相似度比对 + 黑名单),但无权益时用户仍可自由操作
  • 生物特征数据的删除路径需在帮助或客服流程中可被告知
  • 用户协议和隐私政策需可随时查看
  • 人脸黑名单机制需在隐私政策中披露

异常处理

异常处理
相机权限被拒提示开启方式(设置 → 隐私 → 相机)
质量不达标提示具体原因,引导调整后重试
上传失败/超时支持重试,设置 3 次上限
录入失败(服务端)展示具体错误原因
冷却期内重录前端置灰 + 后端拒绝,显示剩余时间
有效权益期重录前端置灰 + 后端拒绝,提示冻结原因
人脸相似度不通过拒绝重录 + 引导联系客服人工审核
命中黑名单拒绝录入 + 「请联系客服」
工控机同步失败MQTT 重试 + 工控机下次上线全量同步

接口

text
# 首次录入
POST /api/v1/user/face/enroll
  Auth: JWT
  Body: { image: string }   // Base64
  Response: { success: boolean, message: string }

# 检查是否允许重录(前端在进入重录前调用)
GET /api/v1/user/face/re-enroll/check
  Auth: JWT
  Response: {
    allowed: boolean,
    reason: string | null,          // 不允许时的原因
    cooldownExpiresAt: string | null, // 冷却期到期时间(ISO 8601)
    freezeReason: string | null      // 冻结原因:active_membership / used_trial
  }

# 重录人脸
POST /api/v1/user/face/re-enroll
  Auth: JWT
  Body: { image: string }   // Base64
  Response: {
    success: boolean,
    message: string,
    similarityScores: {            // 双基准相似度分数
      vsOriginal: number | null,   // 与原始人脸的相似度
      vsLatest: number | null      // 与最后一次录入人脸的相似度
    }
  }

# 人脸管理信息
GET /api/v1/user/face/status
  Auth: JWT
  Response: {
    enrolled: boolean,
    enrolledAt: string | null,
    lastReEnrollAt: string | null,
    canReEnroll: boolean,
    reEnrollReason: string | null,
    hasSuccessfulEntry: boolean     // 是否曾刷脸成功入场(控制人脸管理入口显隐)
  }

管理后台接口(新增)

text
# 提交人脸重录审核申请
POST /api/v1/admin/users/:userId/face/re-enroll-approve
  Auth: JWT (管理员)
  Body: { reason: string, identityProofUrl?: string }
  Response: {
    success: boolean,
    unlockExpiresAt: string  // 解锁截止时间(24h 后过期)
  }

# 添加/移除人脸黑名单
POST /api/v1/admin/face/blacklist
DELETE /api/v1/admin/face/blacklist/:id
  Auth: JWT (管理员)

飞创 Fitron 内部规划文档