前端代码
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("获取人脸核身信息失败"); } }
浙公网安备 33010602011771号