当前位置: 首页 > ai >正文

腾讯云《意愿核身移动 H5》 接入完整示例

文章目录

  • 腾讯云《意愿核身移动 H5》 文档
  • pom.xml
  • 接口配置参数
  • 获取 AccessToken
  • 获取 Ticket
  • 生成签名 sign
  • 启动 H5 意愿核身
  • 意愿核身查询结果

腾讯云《意愿核身移动 H5》 文档

  • https://cloud.tencent.com/document/product/1007/77303

在这里插入图片描述

pom.xml

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version>
</dependency>

接口配置参数

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;// Redis 缓存 Key
private final static String ACCESS_TOKEN_KEY = "face:tencent:access_token";
private final static String TICKET_KEY = "face:tencent:ticket";// 接口参数
private final static String BASE_URL = "https://kyc1.qcloud.com/api";
private final static String APP_ID = "xxxxxxxxx";
private final static String SECRET_KEY = "xxxxxxxxx";
private final static String VERSION = "1.0.0";// 意愿核身成功后回调 H5 页面地址
private final static String CALLBACK_URL = "https://test.domain/index";
private final static String QUESTION = "{0}是宇宙无敌帅。同意请回复“确认”,否则回复“不确认”。";
private final static String ANSWER = "确认";

获取 AccessToken

private String getAccessToken() {String accessToken = redisService.getCacheObject(ACCESS_TOKEN_KEY);if (accessToken != null) {return accessToken;}String url = MessageFormat.format(BASE_URL + "/oauth2/access_token?appId={0}&secret={1}&grant_type=client_credential&version={2}", APP_ID, SECRET_KEY, VERSION);HttpResponse response = HttpUtil.createGet(url).execute();String body = response.body();log.info("body: {}", body);JSONObject result = JSONUtil.parseObj(body);if (!result.containsKey("code") || !"0".equals(result.getStr("code"))) {throw new RuntimeException("获取Token失败:" + (result.containsKey("msg") ? result.getStr("msg") : "未知错误"));}accessToken = result.getStr("access_token");Long expireIn = result.getLong("expire_in");redisService.setCacheObject(ACCESS_TOKEN_KEY, accessToken, expireIn, TimeUnit.SECONDS);return accessToken;
}

获取 Ticket

private String getApiTicket() {String ticket = redisService.getCacheObject(TICKET_KEY);if (ticket != null) {return ticket;}String accessToken = getAccessToken();String url = MessageFormat.format(BASE_URL + "/oauth2/api_ticket?appId={0}&access_token={1}&type=SIGN&version={2}", APP_ID, accessToken, VERSION);HttpResponse response = HttpUtil.createGet(url).execute();String body = response.body();log.info("body: {}", body);JSONObject result = JSONUtil.parseObj(body);if (!result.containsKey("code") || !"0".equals(result.getStr("code"))) {throw new RuntimeException("获取Ticket失败:" + (result.containsKey("msg") ? result.getStr("msg") : "未知错误"));}JSONArray tickets = result.getJSONArray("tickets");if (tickets.isEmpty()) {throw new RuntimeException("获取Ticket失败,tickets为空");}JSONObject ticketJson = (JSONObject) tickets.get(0);ticket = ticketJson.getStr("value");Long expireIn = ticketJson.getLong("expire_in");redisService.setCacheObject(TICKET_KEY, ticket, expireIn, TimeUnit.SECONDS);return ticket;
}

生成签名 sign

private String sign(Map<String, Object> paramMap) {// 按值的字典序排List<Object> sortedValues = paramMap.values().stream().filter(Objects::nonNull).sorted(Comparator.comparing(Object::toString)).collect(Collectors.toList());String signStr = sortedValues.stream().map(Object::toString).collect(Collectors.joining(""));paramMap.remove("ticket");try {MessageDigest digest = MessageDigest.getInstance("SHA-1");byte[] hashBytes = digest.digest(signStr.getBytes(StandardCharsets.UTF_8));return bytesToHex(hashBytes).toUpperCase();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("SHA-1 algorithm not available.", e);}
}private String bytesToHex(byte[] bytes) {StringBuilder hexString = new StringBuilder();for (byte b : bytes) {String hex = Integer.toHexString(0xff & b);if (hex.length() == 1) {hexString.append('0');}hexString.append(hex);}return hexString.toString();
}

启动 H5 意愿核身

public String getFaceUrl(String userId, String name, String idNo, Long depositAmount) {// 生成 32 位长度的订单号String orderNo = DateUtils.dateTimeNow() + StringUtils.getRandomString(18);String url = MessageFormat.format(BASE_URL + "/server/getWillFaceId?orderNo={0}", orderNo);Map<String, Object> paramMap = new HashMap<>();paramMap.put("appId", APP_ID);paramMap.put("userId", userId);paramMap.put("version", VERSION);// 生成 32 位长度的随机字符串paramMap.put("nonce", StringUtils.getRandomString(32));paramMap.put("ticket", getApiTicket());// 生成签名paramMap.put("sign", sign(paramMap));paramMap.put("name", name);paramMap.put("idNo", idNo);paramMap.put("orderNo", orderNo);paramMap.put("liveService", 2);// 参数值为1时,表示仅使用实时检测模式,不兼容的情况下回调错误码3005paramMap.put("liveInterType", 1);// 0:问答模式 1:播报模式 2:点头模式paramMap.put("willType", 0);paramMap.put("willLanguage", 0);// 系统播报问题文本/问题Map<String, String> questionMap = new LinkedHashMap<>();questionMap.put("id", "0");questionMap.put("question", MessageFormat.format(QUESTION, name));questionMap.put("answer", ANSWER);List<Map<String, String>> willContentList = new ArrayList<>();willContentList.add(questionMap);paramMap.put("willContentList", willContentList);paramMap.put("willMidAnswer", ANSWER);HttpRequest request = HttpUtil.createPost(url);request.header("Content-Type", "application/json");request.body(JSONUtil.toJsonStr(paramMap));HttpResponse response = request.execute();String body = response.body();log.info("body: {}", body);JSONObject result = JSONUtil.parseObj(body);if (!result.containsKey("code") || !"0".equals(result.getStr("code"))) {throw new RuntimeException("合作方后台上传信息失败:" + (result.containsKey("msg") ? result.getStr("msg") : "未知错误"));}// {//   "code": "0",//   "msg": "请求成功",//   "bizSeqNo": "xxxxxxxxxxxxxxx",//   "result": {//     "bizSeqNo": "xxxxxxxxxxxxxxx",//     "transactionTime": "20250903155605",//     "oriCode": "0",//     "orderNo": "xxxxxxxxxxxxxxx",//     "faceId": "xxxxxxxxxxxxxxx",//     "optimalDomain": "kyc1.qcloud.com",//     "success": true//   },//   "transactionTime": "20250903155605"// }result = result.getJSONObject("result");orderNo = result.getStr("orderNo");String faceId = result.getStr("faceId");String optimalDomain = "kyc1.qcloud.com";if (result.containsKey("optimalDomain")) {optimalDomain = result.getStr("optimalDomain");}return willLogin(orderNo, userId, faceId, optimalDomain);
}private String willLogin(String orderNo, String userId, String faceId, String optimalDomain) {Map<String, Object> paramMap = new HashMap<>();paramMap.put("appId", APP_ID);paramMap.put("orderNo", orderNo);paramMap.put("userId", userId);paramMap.put("version", VERSION);paramMap.put("faceId", faceId);paramMap.put("ticket", getApiTicket());String nonceStr = StringUtils.getRandomString(32);paramMap.put("nonce", nonceStr);// 生成签名String signStr = sign(paramMap);try {String url = MessageFormat.format("https://{0}/api/web/willLogin?appId={1}&version={2}&nonce={3}&orderNo={4}&faceId={5}&url={6}&from=browser&userId={7}&sign={8}&redirectType=1",optimalDomain,APP_ID,VERSION,nonceStr,orderNo,faceId,// 回调时所带的参数:https://test.domain/index?orderNo=xxxx&code=0&newSignature=xxxx&liveRate=99&h5faceId=xxxx&willCode=0&faceCode=0&similarity=95.29URLEncoder.encode(CALLBACK_URL, String.valueOf(StandardCharsets.UTF_8)),userId,signStr);log.info("url: {}", url);return url;} catch (UnsupportedEncodingException e) {e.printStackTrace();log.error("{}", e.getMessage());throw new RuntimeException("启动 H5 意愿核身失败");}
}

注: 将生成的 FaceUrl 链接放到浏览器访问,就会跳到下方的意愿核身页面。

在这里插入图片描述

意愿核身查询结果

public FaceResult getFaceResult(String orderNo) {String url = MessageFormat.format(BASE_URL + "/server/getWillFaceResult?orderNo={0}", orderNo);Map<String, Object> paramMap = new HashMap<>();paramMap.put("appId", APP_ID);paramMap.put("orderNo", orderNo);paramMap.put("version", VERSION);paramMap.put("ticket", getApiTicket());String nonceStr = StringUtils.getRandomString(32);paramMap.put("nonce", nonceStr);// 生成签名paramMap.put("sign", sign(paramMap));paramMap.put("getFile", 1);paramMap.put("getWillFile", 0);HttpRequest request = HttpUtil.createPost(url);request.header("Content-Type", "application/json");request.body(JSONUtil.toJsonStr(paramMap));HttpResponse response = request.execute();String body = response.body();log.info("body: {}", body);JSONObject result = JSONUtil.parseObj(body);if (!result.containsKey("code") || !"0".equals(result.getStr("code"))) {throw new RuntimeException("识别结果查询失败:" + (result.containsKey("msg") ? result.getStr("msg") : "未知错误"));}return JSONUtil.toBean(result.getJSONObject("result"), FaceResult.class);
}

注: orderNo 参数从 回调地址 参数中获取 https://test.domain/index?orderNo=xxxx&code=0&newSignature=xxxx&liveRate=99&h5faceId=xxxx&willCode=0&faceCode=0&similarity=95.29

http://www.xdnf.cn/news/19712.html

相关文章:

  • 【51单片机】【protues仿真】基于51单片机压力测量仪系统
  • 机器学习从入门到精通 - KNN与SVM实战指南:高维空间中的分类奥秘
  • 深度学习入门:从神经网络基础到 BP 算法全解析
  • 快速搭建一个Vue+TS+Vite项目
  • CMake构建学习笔记24-使用通用脚本构建PROJ和GEOS
  • Unity开发保姆级教程:C#脚本+物理系统+UI交互,3大模块带你通关游戏开发
  • Spring Boot配置error日志发送至企业微信
  • char、short、int等整型类型取值范围
  • Java继承
  • 【YOLO】数据增强bug
  • mysql第五天学习 Mysql全局优化总结
  • AI+教育:用BERT构建个性化错题推荐系统
  • 多线程同步安全机制
  • 进程管理和IPC
  • 嵌入式|RTOS教学——FreeRTOS基础1:准备工作
  • 解锁产品说明书的“视觉密码”:多模态 RAG 与 GPT-4 的深度融合 (AI应用与技术系列)
  • 深度学习与 OpenCV 的深度羁绊:从技术协同到代码实践
  • k8s知识点总结3
  • 数据结构_循环队列_牺牲一个存储空间_不牺牲额外的存储空间 Circular Queue(C语言实现_超详细)
  • 【Linux】Linux开发必备:Git版本控制与GDB调试全指南
  • 物联网时序数据存储方案:Apache IoTDB 集群部署全流程 + TimechoDB 优势解读
  • 代码质量保障:使用Jest和React Testing Library进行单元测试
  • 服务器固件全景地图:从BIOS到BMC,升级背后的安全与性能革命
  • 日志分析与安全数据上传脚本
  • 飞算JavaAI真能帮小白搞定在线图书借阅系统?开发效果大揭秘!
  • PgManage:一款免费开源、跨平台的数据库管理工具
  • 什么是 Java 的反射机制?它有什么优缺点?
  • 普通大学生的 Web3 实习怎么找?行业指南与实践技巧这里看
  • Redis 哨兵 (基于 Docker)
  • 梯度波导_FDTD_学习_代码