前端代码

const generateOutSeqNo = () => {
  const timestamp = Date.now();
  const random = Math.random().toString(36).substring(2, 8).toUpperCase();
  return `FR_${timestamp}_${random}`;
};

onShow(() => {
  getUserInfo()
  checkFacialRecognitionDevice().then(() => {
    return checkCameraAuth();
  }).catch((err) => {
    console.warn('设备预检查提示:', err.message);
  });
})

onMounted(() => {

})

const getUserInfo = () => {
  loginApi.getLoginUser().then(data => {
    openId.value = data.openid
  }).catch(error => {

  })
}



// 检查设备是否支持人脸识别能力
const checkFacialRecognitionDevice = () => {
  return new Promise((resolve, reject) => {
    wx.checkIsSupportFacialRecognition({
      success: () => {
        resolve(true);
      },
      fail: (err) => {
        console.error('设备不支持人脸识别:', err);
        reject(new Error('设备不支持人脸识别'));
      }
    });
  });
};


//检查摄像头权限
const checkCameraAuth = () => {
  return new Promise((resolve, reject) => {
    wx.getSetting({
      success: (res) => {
        if (res.authSetting['scope.camera'] === true) {
          // 已授权,直接通过
          resolve(true);
        } else if (res.authSetting['scope.camera'] === false) {
          // 曾经拒绝过,需要引导开启
          uni.showModal({
            title: '需要相机权限',
            content: '人脸识别需要使用您的相机,请前往设置开启权限',
            confirmText: '去设置',
            cancelText: '取消',
            success: (modalRes) => {
              if (modalRes.confirm) {
                // 跳转到小程序设置页
                wx.openSetting({
                  success: (settingRes) => {
                    if (settingRes.authSetting['scope.camera']) {
                      resolve(true);
                    } else {
                      reject(new Error('用户未开启相机权限'));
                    }
                  },
                  fail: () => {
                    reject(new Error('打开设置页失败'));
                  }
                });
              } else {
                reject(new Error('用户取消授权'));
              }
            }
          });
        } else {
          // 从未申请过,尝试静默申请(第一次会弹框)
          wx.authorize({
            scope: 'scope.camera',
            success: () => resolve(true),
            fail: () => {
              // 用户点了拒绝,下次 getSetting 会返回 false
              reject(new Error('用户拒绝相机权限'));
            }
          });
        }
      },
      fail: (err) => {
        console.error('获取权限设置失败:', err);
        reject(err);
      }
    });
  });
}



const submit =  () => {
  if (isEmpty(form.value.name)) {
    uni.$u.toast('请输入姓名')
    return
  }
  if (!/^[\u4e00-\u9fa5·]+$/.test(form.value.name)) {
    uni.$u.toast('姓名不能包含空格、字母、数字或特殊符号!')
    return;
  }
  if (isEmpty(form.value.idCard)) {
    uni.$u.toast('请输入身份证号码')
    return;
  }
  if (/\s/.test(form.value.idCard)) {
    uni.$u.toast('身份证号码不能包含空格')
    return;
  }

  if (!idCardTool.verify(form.value.idCard)) {
    uni.$u.toast('不是有效身份证号码')
    return;
  }
  if (!agreementChecked.value) {
    uni.$u.toast('为了保障您的权益,请务必阅读并勾选《隐私政策》和《用户协议》')
    return;
  }

  uni.showLoading({
    title: '检测中...',
    mask: true
  });

  checkFacialRecognitionDevice().then(() => {
    console.log('设备检查通过');
    return checkCameraAuth();
  }).then(() => {
    uni.hideLoading();
    console.log('摄像头权限通过');
    //只有 checkCameraAuth() 成功执行人脸识别
    executeFacialRecognition(form.value.name, form.value.idCard, openId.value);
  }).catch((err) => {
    uni.hideLoading();
    console.error('认证前置检查失败:', err);
    if (err.message === '设备不支持人脸识别') {
      uni.showToast({
        title: '您的设备不支持人脸识别功能',
        icon: 'none',
        duration: 2000
      });
    } else if (err.message && err.message.includes('相机权限')) {
      uni.showToast({
        title: '需要相机权限才能进行人脸识别',
        icon: 'none',
        duration: 2000
      });
    } else {
      uni.showToast({
        title: '初始化失败,请重试',
        icon: 'none',
        duration: 2000
      });
    }
  });

}


const executeFacialRecognition = (name, idCard, openid) => {
  const outSeqNo = generateOutSeqNo();
  //获取人脸核身会话唯一标识(小程序后台根据「用户实名信息(姓名+身份证)」调用微信后台 getVerifyId 接口获取)
  loginApi.getFacialRecognitionVerifyId({name: name, idCard: idCard, openid: openid, outSeqNo: outSeqNo}).then((res) => {
    if (res.errcode !== 0) {
      uni.showToast({
        title: '获取人脸核身会话Id失败:' + res.errmsg,
        icon: 'error',
        duration: 2000
      });
      return;
    }
    // 调用微信人脸核身接口进行人脸识别
    wx.requestFacialVerify({
      verifyId: res.verify_id,
      success: (verifyRes) => {
        // 人脸核身验证成功,需要通知小程序后台 根据本次人脸核身会话唯一标识 verifyId 字段调用微信后台 queryVerifyInfo 接口查询人脸核身真实验证结果。
        loginApi.getFacialRecognitionVerifyInfo({name: name, idCard: idCard, openid: openid, outSeqNo: outSeqNo, verifyId: res.verify_id}).then((result) => {
          if (result.errcode === 0) {
            //业务操作
            Verify(name, idCard, openid, result.verify_ret);
          } else {
            uni.showToast({
              title: '获取人脸核身结果失败:' + result.errmsg,
              icon: 'error',
              duration: 2000
            });
          }
        }).catch((err) => {
          console.error('查询人脸核身结果失败:', err);
          uni.showToast({
            title: '查询人脸核身结果失败',
            icon: 'error',
            duration: 2000
          });
        });
      },
      fail: (err) => {
        console.error('人脸核身验证失败:', err);
        uni.showToast({
          title: '人脸核身验证失败',
          icon: 'error',
          duration: 2000
        });
      }
    });
  }).catch((err) => {
    console.error('获取 verifyId 接口调用失败:', err);
    uni.showToast({
      title: '获取验证标识失败',
      icon: 'error',
      duration: 2000
    });
  });
};


const Verify = (name, idCard, openid, verifyResult) => {
  loginApi.doFacialRecognitionVerify({
    name: name,
    idCard: idCard,
    openid: openid,
    verifyResult: verifyResult
  }).then((res) => {
    if (res === '认证成功') {
      Promise.all([
        store.dispatch('GetUserInfo'),
      ]).then(([dictData, userInfo]) => {
        uni.navigateBack({
          delta: 1
        })
      }).catch(err => {

      })
    }
  }).catch(err => {

  })

}

  

后端代码

  config.wx.baseUrl=https://api.weixin.qq.com


  /** * 获取令牌token * @return */ private String getAccessToken() { String url = "/cgi-bin/token"; Map<String, Object> params = new HashMap<>(); params.put("grant_type", "client_credential"); params.put("appid", wxMaProperties.getAppid()); params.put("secret", wxMaProperties.getSecret()); String token = null; try { String resStr = HttpUtil.get(wxMaProperties.getBaseUrl() + url, params); token = (String) JSONUtil.parseObj(resStr).get("access_token"); } catch (Exception e) { e.printStackTrace(); throw new CommonException("获取access_token失败"); } return token; } /** * 生成 cert_hash(微信规则) * @param certInfo 包含 cert_type、cert_name、cert_no * @return 小写十六进制 cert_hash */ public static String generateCertHash(Map<String, String> certInfo) { // 1. 获取字段 String certType = certInfo.get("cert_type"); String certName = certInfo.get("cert_name"); String certNo = certInfo.get("cert_no"); // 2. UTF-8 + Base64 编码 String certTypeB64 = Base64.getEncoder().encodeToString(certType.getBytes(StandardCharsets.UTF_8)); String certNameB64 = Base64.getEncoder().encodeToString(certName.getBytes(StandardCharsets.UTF_8)); String certNoB64 = Base64.getEncoder().encodeToString(certNo.getBytes(StandardCharsets.UTF_8)); // 3. 按规则拼接 String concatStr = String.format("cert_type=%s&cert_name=%s&cert_no=%s", certTypeB64, certNameB64, certNoB64); // 4. SHA256 计算 try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashBytes = digest.digest(concatStr.getBytes(StandardCharsets.UTF_8)); // 5. 转小写十六进制 StringBuilder sb = new StringBuilder(); for (byte b : hashBytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException("生成 cert_hash 失败", e); } } @Override public com.alibaba.fastjson.JSONObject getFacialRecognitionVerifyId(AuthFacialRecognitionVerifyParam authFacialRecognitionVerifyParam) { String openId =authFacialRecognitionVerifyParam.getOpenid(); String name = authFacialRecognitionVerifyParam.getName(); String idCard = authFacialRecognitionVerifyParam.getIdCard(); String outSeqNo = authFacialRecognitionVerifyParam.getOutSeqNo(); if (ObjectUtil.isEmpty(openId)||ObjectUtil.isEmpty(name)||ObjectUtil.isEmpty(idCard)||ObjectUtil.isEmpty(outSeqNo)){ throw new CommonException("获取人脸核身会话唯一标识参数不能为空"); } if (!IdcardUtil.isValidCard(idCard)){ throw new CommonException("获取人脸核身会话唯一标识身份证号码参数不是有效身份证"); } Map<String, Object> bodys = new HashMap<>(); Map<String, Object> certInfo = new HashMap<>(); certInfo.put("cert_type", "IDENTITY_CARD"); certInfo.put("cert_name", name); certInfo.put("cert_no", idCard); bodys.put("out_seq_no", outSeqNo); bodys.put("openid", openId); bodys.put("cert_info", certInfo); String accessToken = getAccessToken(); try { String url = "/cityservice/face/identify/getverifyid"; HttpRequest request = HttpUtil.createRequest(Method.POST, wxMaProperties.getBaseUrl() + url + "?access_token=" + accessToken); request.body(JSONUtil.toJsonStr(bodys)); HttpResponse response = request.execute(); String resStr = response.body(); com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(resStr); return jsonObject; } catch (Exception e) { e.printStackTrace(); throw new CommonException("获取人脸核身会话唯一标识失败"); } } @Override public com.alibaba.fastjson.JSONObject getFacialRecognitionVerifyInfo(AuthFacialRecognitionVerifyParam authFacialRecognitionVerifyParam) { String openId =authFacialRecognitionVerifyParam.getOpenid(); String name = authFacialRecognitionVerifyParam.getName(); String idCard = authFacialRecognitionVerifyParam.getIdCard(); String verifyId = authFacialRecognitionVerifyParam.getVerifyId(); String outSeqNo = authFacialRecognitionVerifyParam.getOutSeqNo(); if (ObjectUtil.isEmpty(openId)||ObjectUtil.isEmpty(name)||ObjectUtil.isEmpty(idCard)||ObjectUtil.isEmpty(outSeqNo)||ObjectUtil.isEmpty(verifyId)){ throw new CommonException("查询人脸核身真实验证结果参数不能为空"); } if (!IdcardUtil.isValidCard(idCard)){ throw new CommonException("查询人脸核身真实验证结果参数身份证号码不是有效身份证"); } Map<String, String> certInfo = Map.of( "cert_type", "IDENTITY_CARD", "cert_name", name, "cert_no", idCard ); String certHash=generateCertHash(certInfo);; Map<String, Object> bodys = new HashMap<>(); bodys.put("verify_id", verifyId); bodys.put("out_seq_no", outSeqNo); bodys.put("openid", openId); bodys.put("cert_hash", certHash); String accessToken = getAccessToken(); try { String url = "/cityservice/face/identify/queryverifyinfo"; HttpRequest request = HttpUtil.createRequest(Method.POST, wxMaProperties.getBaseUrl() + url + "?access_token=" + accessToken); request.body(JSONUtil.toJsonStr(bodys)); HttpResponse response = request.execute(); String resStr = response.body(); com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(resStr); return jsonObject; } catch (Exception e) { e.printStackTrace(); throw new CommonException("获取人脸核身信息失败"); } }

  

 

posted on 2026-04-13 16:20  西门夜说  阅读(20)  评论(0)    收藏  举报