🆕 #3902 【小程序】新增人脸核身服务的接口实现
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdRequest;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdResponse;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoRequest;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoResponse;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
|
||||
/**
|
||||
* 微信小程序人脸核身相关接口
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/">微信人脸核身接口列表</a>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
public interface WxMaFaceService {
|
||||
|
||||
/**
|
||||
* 获取用户人脸核身会话唯一标识
|
||||
* <p>
|
||||
* 业务方后台根据「用户实名信息(姓名+身份证)」调用 getVerifyId 接口获取人脸核身会话唯一标识 verifyId 字段,
|
||||
* 然后给到小程序前端调用 wx.requestFacialVerify 接口使用。
|
||||
* </p>
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
|
||||
* </p>
|
||||
*
|
||||
* @param request 请求参数
|
||||
* @return 包含 verifyId 的响应实体
|
||||
* @throws WxErrorException 调用微信接口失败时抛出
|
||||
*/
|
||||
WxMaFaceGetVerifyIdResponse getVerifyId(WxMaFaceGetVerifyIdRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 查询用户人脸核身真实验证结果
|
||||
* <p>
|
||||
* 业务方后台根据人脸核身会话唯一标识 verifyId 字段调用 queryVerifyInfo 接口查询用户人脸核身真实验证结果。
|
||||
* 核身通过的判断条件:errcode=0 且 verify_ret=10000。
|
||||
* </p>
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_queryverifyinfo">查询用户人脸核身真实验证结果</a>
|
||||
* </p>
|
||||
*
|
||||
* @param request 请求参数
|
||||
* @return 包含 verifyRet 的响应实体
|
||||
* @throws WxErrorException 调用微信接口失败时抛出
|
||||
*/
|
||||
WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRequest request) throws WxErrorException;
|
||||
|
||||
}
|
||||
@@ -631,4 +631,13 @@ public interface WxMaService extends WxService {
|
||||
* @return 用工关系服务对象WxMaEmployeeRelationService
|
||||
*/
|
||||
WxMaEmployeeRelationService getEmployeeRelationService();
|
||||
|
||||
/**
|
||||
* 获取人脸核身服务对象。
|
||||
* <br>
|
||||
* 文档:https://developers.weixin.qq.com/miniprogram/dev/server/API/face/
|
||||
*
|
||||
* @return 人脸核身服务对象WxMaFaceService
|
||||
*/
|
||||
WxMaFaceService getFaceService();
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this);
|
||||
private final WxMaEmployeeRelationService employeeRelationService =
|
||||
new WxMaEmployeeRelationServiceImpl(this);
|
||||
private final WxMaFaceService faceService = new WxMaFaceServiceImpl(this);
|
||||
|
||||
private Map<String, WxMaConfig> configMap = new HashMap<>();
|
||||
private int retrySleepMillis = 1000;
|
||||
@@ -1055,4 +1056,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
public WxMaEmployeeRelationService getEmployeeRelationService() {
|
||||
return this.employeeRelationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaFaceService getFaceService() {
|
||||
return this.faceService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaFaceService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdRequest;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdResponse;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoRequest;
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Face.GET_VERIFY_ID_URL;
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Face.QUERY_VERIFY_INFO_URL;
|
||||
|
||||
/**
|
||||
* 微信小程序人脸核身相关接口实现
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WxMaFaceServiceImpl implements WxMaFaceService {
|
||||
private final WxMaService service;
|
||||
|
||||
@Override
|
||||
public WxMaFaceGetVerifyIdResponse getVerifyId(WxMaFaceGetVerifyIdRequest request)
|
||||
throws WxErrorException {
|
||||
String responseContent = this.service.post(GET_VERIFY_ID_URL, request.toJson());
|
||||
return WxMaFaceGetVerifyIdResponse.fromJson(responseContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRequest request)
|
||||
throws WxErrorException {
|
||||
String responseContent = this.service.post(QUERY_VERIFY_INFO_URL, request.toJson());
|
||||
return WxMaFaceQueryVerifyInfoResponse.fromJson(responseContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.binarywang.wx.miniapp.bean.face;
|
||||
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 获取用户人脸核身会话唯一标识 请求实体
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WxMaFaceGetVerifyIdRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:业务方系统内部流水号
|
||||
* 是否必填:是
|
||||
* 描述:要求5-32个字符内,只能包含数字、大小写字母和_-字符,且在同一个appid下唯一
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("out_seq_no")
|
||||
private String outSeqNo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:用户身份信息
|
||||
* 是否必填:是
|
||||
* 描述:证件信息对象
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("cert_info")
|
||||
private CertInfo certInfo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:用户身份标识
|
||||
* 是否必填:是
|
||||
* 描述:用户的openid
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("openid")
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 用户身份信息
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CertInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:证件类型
|
||||
* 是否必填:是
|
||||
* 描述:证件类型,身份证填 IDENTITY_CARD
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("cert_type")
|
||||
private String certType;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:证件姓名
|
||||
* 是否必填:是
|
||||
* 描述:证件上的姓名,UTF-8编码
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("cert_name")
|
||||
private String certName;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:证件号码
|
||||
* 是否必填:是
|
||||
* 描述:证件号码
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("cert_no")
|
||||
private String certNo;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.binarywang.wx.miniapp.bean.face;
|
||||
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 获取用户人脸核身会话唯一标识 响应实体
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WxMaFaceGetVerifyIdResponse implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:错误码
|
||||
* 是否必填:是
|
||||
* 类型:number
|
||||
* 描述:0表示成功,其他值表示失败
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("errcode")
|
||||
private Integer errcode;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:错误信息
|
||||
* 是否必填:是
|
||||
* 类型:string
|
||||
* 描述:错误信息描述
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("errmsg")
|
||||
private String errmsg;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:人脸核身会话唯一标识
|
||||
* 是否必填:否
|
||||
* 类型:string
|
||||
* 描述:微信侧生成的人脸核身会话唯一标识,用于后续接口调用,长度不超过256字符
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("verify_id")
|
||||
private String verifyId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:有效期
|
||||
* 是否必填:否
|
||||
* 类型:number
|
||||
* 描述:verify_id有效期,过期后无法发起核身,默认值3600,单位:秒
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("expires_in")
|
||||
private Integer expiresIn;
|
||||
|
||||
public static WxMaFaceGetVerifyIdResponse fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaFaceGetVerifyIdResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.binarywang.wx.miniapp.bean.face;
|
||||
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 查询用户人脸核身真实验证结果 请求实体
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_queryverifyinfo">查询用户人脸核身真实验证结果</a>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WxMaFaceQueryVerifyInfoRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:人脸核身会话唯一标识
|
||||
* 是否必填:是
|
||||
* 描述:getVerifyId接口返回的人脸核身会话唯一标识
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("verify_id")
|
||||
private String verifyId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:业务方系统外部流水号
|
||||
* 是否必填:是
|
||||
* 描述:必须和getVerifyId接口传入的out_seq_no一致
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("out_seq_no")
|
||||
private String outSeqNo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:证件信息摘要
|
||||
* 是否必填:是
|
||||
* 描述:根据getVerifyId中传入的证件信息生成的信息摘要。
|
||||
* 计算方式:对cert_info中的cert_type、cert_name、cert_no字段内容进行标准base64编码,
|
||||
* 按顺序拼接:cert_type=xxx&cert_name=xxx&cert_no=xxx,再对拼接串进行SHA256输出十六进制小写结果
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("cert_hash")
|
||||
private String certHash;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:用户身份标识
|
||||
* 是否必填:是
|
||||
* 描述:必须和getVerifyId接口传入的openid一致
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("openid")
|
||||
private String openid;
|
||||
|
||||
public String toJson() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算证件信息摘要(cert_hash)
|
||||
* <p>
|
||||
* 计算规则(参见官方文档):
|
||||
* 1. 对 cert_type、cert_name、cert_no 字段内容进行标准 base64 编码(若含中文等 Unicode 字符,先进行 UTF-8 编码)
|
||||
* 2. 按顺序拼接各个字段:cert_type=xxx&cert_name=xxx&cert_no=xxx
|
||||
* 3. 对拼接串进行 SHA256 并输出十六进制小写结果
|
||||
* </p>
|
||||
*
|
||||
* @param certType 证件类型
|
||||
* @param certName 证件姓名
|
||||
* @param certNo 证件号码
|
||||
* @return cert_hash 十六进制小写字符串
|
||||
*/
|
||||
public static String calcCertHash(String certType, String certName, String certNo) {
|
||||
String encodedType = Base64.getEncoder().encodeToString(certType.getBytes(StandardCharsets.UTF_8));
|
||||
String encodedName = Base64.getEncoder().encodeToString(certName.getBytes(StandardCharsets.UTF_8));
|
||||
String encodedNo = Base64.getEncoder().encodeToString(certNo.getBytes(StandardCharsets.UTF_8));
|
||||
String raw = "cert_type=" + encodedType + "&cert_name=" + encodedName + "&cert_no=" + encodedNo;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hashBytes = digest.digest(raw.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder hex = new StringBuilder();
|
||||
for (byte b : hashBytes) {
|
||||
hex.append(String.format("%02x", b));
|
||||
}
|
||||
return hex.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("SHA-256 algorithm not available", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.binarywang.wx.miniapp.bean.face;
|
||||
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 查询用户人脸核身真实验证结果 响应实体
|
||||
* <p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_queryverifyinfo">查询用户人脸核身真实验证结果</a>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class WxMaFaceQueryVerifyInfoResponse implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:错误码
|
||||
* 是否必填:是
|
||||
* 类型:number
|
||||
* 描述:0表示成功,其他值表示失败
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("errcode")
|
||||
private Integer errcode;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:错误信息
|
||||
* 是否必填:是
|
||||
* 类型:string
|
||||
* 描述:错误信息描述
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("errmsg")
|
||||
private String errmsg;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:人脸核身验证结果
|
||||
* 是否必填:否
|
||||
* 类型:number
|
||||
* 描述:核身通过的判断条件:errcode=0 且 verify_ret=10000
|
||||
* 枚举值说明:
|
||||
* 10000 - 识别成功
|
||||
* 10001 - 参数错误
|
||||
* 10002 - 人脸特征检测失败
|
||||
* 10003 - 身份证号不匹配
|
||||
* 10004 - 比对人脸信息不匹配
|
||||
* 10005 - 正在检测中
|
||||
* 10006 - appid没有权限
|
||||
* 10300 - 未完成核身
|
||||
* 90001 - 设备不支持人脸检测
|
||||
* 90002 - 用户取消
|
||||
* 其他枚举值请参见官方文档
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName("verify_ret")
|
||||
private Integer verifyRet;
|
||||
|
||||
public static WxMaFaceQueryVerifyInfoResponse fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaFaceQueryVerifyInfoResponse.class);
|
||||
}
|
||||
}
|
||||
@@ -1018,4 +1018,17 @@ public class WxMaApiUrlConstants {
|
||||
/** 推送用工消息 */
|
||||
String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/employeerelationmsg/send";
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信人脸核身接口
|
||||
* <pre>
|
||||
* 文档地址: https://developers.weixin.qq.com/miniprogram/dev/server/API/face/
|
||||
* </pre>
|
||||
*/
|
||||
public interface Face {
|
||||
/** 获取用户人脸核身会话唯一标识 */
|
||||
String GET_VERIFY_ID_URL = "https://api.weixin.qq.com/cityservice/face/identify/getverifyid";
|
||||
/** 查询用户人脸核身真实验证结果 */
|
||||
String QUERY_VERIFY_INFO_URL = "https://api.weixin.qq.com/cityservice/face/identify/queryverifyinfo";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoRequest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* 微信小程序人脸核身服务本地计算测试类
|
||||
*
|
||||
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
|
||||
*/
|
||||
@Test
|
||||
public class WxMaFaceServiceImplTest {
|
||||
|
||||
@Test
|
||||
public void testCalcCertHash() {
|
||||
// 验证官方文档给出的测试用例:
|
||||
// cert_info: {"cert_type":"IDENTITY_CARD","cert_name":"张三","cert_no":"310101199801011234"}
|
||||
// 期望结果:3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b
|
||||
String certHash = WxMaFaceQueryVerifyInfoRequest.calcCertHash(
|
||||
"IDENTITY_CARD", "张三", "310101199801011234");
|
||||
assertEquals(certHash, "3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user