人脸识别,使用官方API:腾讯云人脸核身之独立H5接入。接口官方返回code = 0 表示成功,其他code码值均为对应码值信息,详见错误码。
注意:
1.合作方上送身份信息的计算签名参数与启动人脸核身计算签名参数不一致,有部分区别。
2.wbappid = webankAppId = app_id
前端入参:客户身份证号、客户姓名、用户 ID (userId)、from(App || browser)
controller
@Autowired
private PCH5SendIdentityService pch5SendIdentityService;
/**
* 合作方后台上送身份信息 PC H5
* 文档:https://cloud.tencent.com/document/product/1007/35893
*
* 请求 URL:https://miniprogram-kyc.tencentcloudapi.com/api/server/h5/geth5faceid?orderNo=xxx
* 请求方法:POST
* 报文格式:Content-Type: application/json
*
*
* @param faceDetectUserVO 身份信息
*/
@PostMapping("/sendH5IdentityInfoUserInfo")
public TXH5IdentityInfoDTO sendH5IdentityInfoUserInfo(@RequestBody FaceDetectUserVO faceDetectUserVO) {
return pch5SendIdentityService.sendH5IdentityInfoUserInfo(faceDetectUserVO);
}
entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FaceDetectUserVO {
// https://cloud.tencent.com/document/product/1007/35893
private String name;//姓名
private String idNo;//证件号码
private String userId;//用户 ID ,用户的唯一标识(不能带有特殊字符),需要跟生成签名的 userId 保持一致
private String from;//来源 App || browser)
}
后端固定参数:wbappid = webankAppId = app_id(API中介绍命名不同,注意)、orderNo(可自定义随机生成不唯一)、userId(可自定义随机生成不唯一)、version
https://cloud.tencent.com/document/product/1007/57603
/**
* 获取 access_token
* 文档: https://cloud.tencent.com/document/product/1007/37304
*
* @return
*/
@Override
public String getAccessTokenTencent() {
// 从redis中获取accessTokenTencent
String accessTokenTencent = redisUtils.get("accessTokenTencent");
log.info("获取redis中的accessToken,为:[{}]", accessTokenTencent);
if (StringUtils.isEmpty(accessTokenTencent)) {
String accessTokenUrl = String.format(TencentCloudConfig.ACCESS_TOKEN_URL, appId, secret);
String jsonStr = HttpUtil.doGet(accessTokenUrl, null);
log.info("返回报文;->{}", jsonStr);
Map<String, String> jsonMap = ConvertUtils.stringToMap(jsonStr);
if (!"0".equals(jsonMap.get("code"))) {
String msg = jsonMap.get("msg");
log.error("获取腾讯token信息错误,code:{},msg:{}", jsonMap.get("code"), msg);
GraceJSONResult.errorMsg(msg);
/**
* 错误响应示例:
* {
* "code": "66660000",
* "msg": "请求参数异常",
* "bizSeqNo": "22090720001184453210262184859700",
* "transactionTime": "20220907102621",
* "success": false,
* "expire_in": 0
* }
*/
}
/**
* 正确响应示例:
* {
* "code":"0","msg":"请求成功",
* "transactionTime":"20151022043831",
* "access_token":"accessToken_string",
* "expire_time":"20151022043831",
* "expire_in":"7200"
* }
*/
// 获取 access_token
accessTokenTencent = jsonMap.get("access_token");
// 过期时间 默认7200L 设置6800L提前重新获取
redisUtils.set("accessTokenTencent", accessTokenTencent, 6800L);
}
log.info("返回有效accessToken,为:[{}]", accessTokenTencent);
return accessTokenTencent;
}
https://cloud.tencent.com/document/product/1007/57613
通过token获取signTicket
/**
* 获取 SIGN ticket
* 请求地址: http://localhost:9900/getSignTicketTencent
* 文档: https://cloud.tencent.com/document/product/1007/37305
*
* @param accessTokenTencent access_token
* @return
*/
@Override
public String getSignTicketTencent(String accessTokenTencent) {
// 从redis中获取nonceTicketTencent
String signTicketTencent = redisUtils.get("signTicketTencent");
log.info("获取redis中的signTicketTencent,为:[{}]", signTicketTencent);
String signTicketValue = null;
if (StringUtils.isEmpty(signTicketTencent)) {
String getSignTicketUrl = String.format(TencentCloudConfig.SIGN_TICKET_URL, appId, accessTokenTencent);
String jsonStr = HttpUtil.doGet(getSignTicketUrl, null);
log.info("返回报文;->{}", jsonStr);
TicketDTO ticketDTO = JSON.parseObject(jsonStr, TicketDTO.class);
if (!"0".equals(ticketDTO.getCode())) {
String msg = ticketDTO.getMsg();
log.error("获取腾讯signTicket信息错误,code:{},msg:{}", ticketDTO.getCode(), msg);
GraceJSONResult.errorMsg(msg);
}
/**
* 正确响应示例:
* {
* "code": "0",
* "msg": "请求成功",
* "transactionTime": "20151022044027",
* "tickets": [
* {
* "value": "ticket_string",
* "expire_in": "3600",
* "expire_time": "20151022044027"
* }
* ]
* }
*/
signTicketValue = ticketDTO.getTickets().get(0).getValue();
// 过期时间 默认3600L 设置3200L提前重新获取
redisUtils.set("signTicketTencent", signTicketValue, 3000L);
}
return signTicketValue;
}
计算合作方上送身份信息签名,参数有:wbappid、orderNo、name、idNo、userId、version、signTicket
计算签名
/**
* PC 端 H5 接入 > 合作方上送身份信息计算签名
* 文档地址:https://cloud.tencent.com/document/product/1007/35893
*
* @param faceDetectUserVO
* @param signTicket
* @return
*/
public String signH5(FaceDetectUserVO faceDetectUserVO, String signTicket) {
//为计算签名做准备
//为计算签名做准备
List<String> list = new ArrayList<>();
list.add(appId);
list.add(faceDetectUserVO.getOrderNo());
list.add(faceDetectUserVO.getName());
list.add(faceDetectUserVO.getIdNo());
list.add(faceDetectUserVO.getUserId());
list.add(TencentCloudConfig.VERSION);
return SignUtils.getSign(list, signTicket);
}
/**
* 合作方后台上送身份信息 PC H5
* 文档:https://cloud.tencent.com/document/product/1007/35893
*
* 请求 URL:https://miniprogram-kyc.tencentcloudapi.com/api/server/h5/geth5faceid?orderNo=xxx
* 请求方法:POST
* 报文格式:Content-Type: application/json
*
*
* @param faceDetectUserVO 身份信息
* @return
*/
@Override
public TXH5IdentityInfoDTO sendH5IdentityInfoUserInfo(FaceDetectUserVO faceDetectUserVO) {
//获取accessToken
String accessToken = commonIdentityService.getAccessTokenTencent();
//获取signTicket
String signTicket = commonIdentityService.getSignTicketTencent(accessToken);
//订单号
String orderNo = SignUtils.GenerateRandom32Number(32);
faceDetectUserVO.setOrderNo(orderNo);
//合作方上送计算签名
String sign = signH5(faceDetectUserVO, signTicket);
Map<String, String> param = new HashMap<>(16);
param.put("webankAppId", appId);
param.put("orderNo", orderNo);
param.put("name", faceDetectUserVO.getName());
param.put("idNo", faceDetectUserVO.getIdNo());
param.put("userId", faceDetectUserVO.getUserId());
param.put("version", TencentCloudConfig.VERSION);
param.put("sign", sign);
log.debug("合作方上送身份信息参数有:[{}]", param);
String getFaceidUrl = String.format(TencentCloudConfig.GET_H5_FACEID_URL, orderNo);
String jsonStr = HttpUtil.doPost(getFaceidUrl, JSON.toJSONString(param));
log.info("返回报文;->{}", jsonStr);
TXH5IdentityInfoDTO txh5IdentityInfoDTO = JSON.parseObject(jsonStr, TXH5IdentityInfoDTO.class);
log.info("合作方上送身份信息接口返回:[{}]", txh5IdentityInfoDTO);
return txh5IdentityInfoDTO;
}
在合作方成功上送身份信息后,可以获取到h5faceId
(32位随机数)
获取nonceTicket(通过token & userId)
https://cloud.tencent.com/document/product/1007/61074
计算启动H5人脸核身签名,参数有:wbappid、orderNo、userId、version、h5faceId、nonce、nonceTicket
/**
* 启用 H5 人脸认证 人脸核身计算签名
* 文档:https://cloud.tencent.com/document/product/1007/35894
*
* @param orderNo 订单号,字母/数字组成的字符串,本次人脸验证合作伙伴上送的订单号,唯一标识
* @param userId 用户 ID ,用户的唯一标识(不要带有特殊字符)
* @param nonceTicket 合作伙伴服务端实时获取的 tikcet,注意是 NONCE 类型
* @param h5faceId h5/geth5faceid 接口返回的唯一标识
* @param nonce 随机数:32位随机串(字母+数字组成的随机数)
* @return
*/
private String faceSignH5(String orderNo, String userId, String nonceTicket, String h5faceId, String nonce) {
//为计算签名做准备
List<String> list = new ArrayList<>();
list.add(appId);
list.add(orderNo);
list.add(userId);
list.add(TencentCloudConfig.VERSION);
list.add(h5faceId);
list.add(nonce);
String sign = SignUtils.getSign(list, nonceTicket);
log.info("启动人脸核身返回签名为:[{}]", sign);
return sign;
}
将成功拉起人脸核身验证通过后的回调页面链接配置至配置文件,同时对该链接进行encode编码
获取到所有拉起人脸核身所需参数后,向链接https://ida.webank.com/api/web/login拼接上参数:webankAppId、version、nonce、orderNo、h5faceId、url、sign、from、userId。例如:
https://ida.webank.com/api/web/login?webankAppId=%s&version=1.0.0&nonce=%s&orderNo=%s&h5faceId=%s&url&userId=%s&sign=%s&from=%s
接好后,直接将该链接返回前端去打开即可拉起人脸核身。请注意,该链接仅一次有效!!!
/**
* 构造人脸核身获取启动链接
* 文档:https://cloud.tencent.com/document/product/1007/35894
*
* @param faceDetectUserVO
* @return
*/
@Override
public GraceJSONResult startCheckFace(FaceDetectUserVO faceDetectUserVO) {
//随机生成32位唯一用户ID和订单ID
String userId = SignUtils.GenerateRandom32Number(32);
String orderNo = SignUtils.GenerateRandom32Number(32);
faceDetectUserVO.setOrderNo(orderNo);
faceDetectUserVO.setUserId(userId);
String requestUrl = "";
try {
//获取accessToken
String accessToken = commonIdentityService.getAccessTokenTencent();
//上送合作方用户信息
TXH5IdentityInfoDTO txh5IdentityInfoDTO = sendH5IdentityInfoUserInfo(faceDetectUserVO);
if (!"0".equals(txh5IdentityInfoDTO.getCode())) {
String msg = txh5IdentityInfoDTO.getMsg();
log.info("启动人脸核身--上送合作方用户信息异常,异常原因为:[{}]]", msg);
GraceJSONResult.errorMsg(msg);
}
//获取h5/geth5faceid 接口返回的唯一标识
String h5faceId = txh5IdentityInfoDTO.getResult().getH5faceId();
//获取32位随机数
String nonce = SignUtils.GenerateRandom32Number(32);
//获取nonceTicket
String nonceTicket = commonIdentityService.getNonceTicketTencent(accessToken, userId);
//启动人脸核身计算签名
String sign = faceSignH5(orderNo, userId, nonceTicket, h5faceId, nonce);
//成功拉起人脸识别并识别成功或失败后的回调路径
String oauthCallback = TencentCloudConfig.OAUTH_CALLBACK_URL;
log.debug("人脸核身通过后的回调地址-拼接路径加密前:url = [{}]", oauthCallback);
String oauthRedirectUrl = URLEncoder.encode(oauthCallback, "utf-8");
log.debug("人脸核身通过后的回调地址-拼接路径加密后:url = [{}]", oauthRedirectUrl);
/**
* https://miniprogram-kyc.tencentcloudapi.com/api/pc/login?webankAppId=appId001
* &version=1.0.0
* &nonce=4bu6a5nv9t678m2t9je5819q46y9hf93
* &orderNo=161709188560917432576916585
* &h5faceId=wb04f10695c3651ce155fea7070b74c9
* &url=https%3a%2f%2fcloud.tencent.com
* &userId=23333333333333
* &sign=5DD4184F4FB26B7B9F6DC3D7D2AB3319E5F7415F
*/
requestUrl = String.format(TencentCloudConfig.REQUEST_URL, appId, nonce, orderNo, h5faceId, oauthRedirectUrl, userId, sign, faceDetectUserVO.getFrom());
} catch (Exception e) {
log.error("启动人脸核身异常,异常原因为:[{}]", e.getMessage());
}
log.info("启动人脸核身--请求路径为:[{}]]", requestUrl);
return GraceJSONResult.ok(requestUrl);
}
/**
* 前端获取结果验证签名
* API:https://cloud.tencent.com/document/product/1007/61302
*
* @param orderNo 订单号,字母/数字组成的字符串,本次人脸核身合作伙伴上送的订单号,唯一标识
* @return
*/
private String getCheckSign(String orderNo) {
//获取accessToken
String accessToken = commonIdentityService.getAccessTokenTencent();
//获取signTicket
String signTicket = commonIdentityService.getSignTicketTencent(accessToken);
List list = new ArrayList<>();
list.add(appId);
list.add(orderNo);
list.add(TencentCloudConfig.VERSION);
list.add(SignUtils.GenerateRandom32Number(32));
String sign = SignUtils.getSign(list, signTicket);
log.info("前端获取结果验证签名值为\"[{}]", sign);
return sign;
}