Skip to content

Commit f49fbe2

Browse files
Copilotbinarywang
andcommitted
新增微信小程序人脸核身服务接口实现
- 添加 WxMaFaceService 接口,包含 getVerifyId 和 queryVerifyInfo 方法 - 添加 WxMaFaceServiceImpl 实现,包含 cert_hash 计算工具方法 - 添加请求/响应 bean:WxMaFaceGetVerifyIdRequest/Response、WxMaFaceQueryVerifyInfoRequest/Response - 在 WxMaApiUrlConstants 中新增 Face 接口 URL 常量 - 在 WxMaService 和 BaseWxMaServiceImpl 中注册人脸核身服务 - 新增测试类 WxMaFaceServiceImplTest(含 cert_hash 单元测试) Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent 3d9ef97 commit f49fbe2

File tree

10 files changed

+542
-0
lines changed

10 files changed

+542
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cn.binarywang.wx.miniapp.api;
2+
3+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdRequest;
4+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdResponse;
5+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoRequest;
6+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoResponse;
7+
import me.chanjar.weixin.common.error.WxErrorException;
8+
9+
/**
10+
* 微信小程序人脸核身相关接口
11+
* <p>
12+
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/">微信人脸核身接口列表</a>
13+
* </p>
14+
*
15+
* @author <a href="https://github.com/github-copilot">Github Copilot</a>
16+
*/
17+
public interface WxMaFaceService {
18+
19+
/**
20+
* 获取用户人脸核身会话唯一标识
21+
* <p>
22+
* 业务方后台根据「用户实名信息(姓名+身份证)」调用 getVerifyId 接口获取人脸核身会话唯一标识 verifyId 字段,
23+
* 然后给到小程序前端调用 wx.requestFacialVerify 接口使用。
24+
* </p>
25+
* <p>
26+
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
27+
* </p>
28+
*
29+
* @param request 请求参数
30+
* @return 包含 verifyId 的响应实体
31+
* @throws WxErrorException 调用微信接口失败时抛出
32+
*/
33+
WxMaFaceGetVerifyIdResponse getVerifyId(WxMaFaceGetVerifyIdRequest request) throws WxErrorException;
34+
35+
/**
36+
* 查询用户人脸核身真实验证结果
37+
* <p>
38+
* 业务方后台根据人脸核身会话唯一标识 verifyId 字段调用 queryVerifyInfo 接口查询用户人脸核身真实验证结果。
39+
* 核身通过的判断条件:errcode=0 且 verify_ret=10000。
40+
* </p>
41+
* <p>
42+
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_queryverifyinfo">查询用户人脸核身真实验证结果</a>
43+
* </p>
44+
*
45+
* @param request 请求参数
46+
* @return 包含 verifyRet 的响应实体
47+
* @throws WxErrorException 调用微信接口失败时抛出
48+
*/
49+
WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRequest request) throws WxErrorException;
50+
51+
}

weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,4 +631,13 @@ WxMaApiResponse execute(
631631
* @return 用工关系服务对象WxMaEmployeeRelationService
632632
*/
633633
WxMaEmployeeRelationService getEmployeeRelationService();
634+
635+
/**
636+
* 获取人脸核身服务对象。
637+
* <br>
638+
* 文档:https://developers.weixin.qq.com/miniprogram/dev/server/API/face/
639+
*
640+
* @return 人脸核身服务对象WxMaFaceService
641+
*/
642+
WxMaFaceService getFaceService();
634643
}

weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
169169
private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this);
170170
private final WxMaEmployeeRelationService employeeRelationService =
171171
new WxMaEmployeeRelationServiceImpl(this);
172+
private final WxMaFaceService faceService = new WxMaFaceServiceImpl(this);
172173

173174
private Map<String, WxMaConfig> configMap = new HashMap<>();
174175
private int retrySleepMillis = 1000;
@@ -1055,4 +1056,9 @@ public WxMaComplaintService getComplaintService() {
10551056
public WxMaEmployeeRelationService getEmployeeRelationService() {
10561057
return this.employeeRelationService;
10571058
}
1059+
1060+
@Override
1061+
public WxMaFaceService getFaceService() {
1062+
return this.faceService;
1063+
}
10581064
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cn.binarywang.wx.miniapp.api.impl;
2+
3+
import cn.binarywang.wx.miniapp.api.WxMaFaceService;
4+
import cn.binarywang.wx.miniapp.api.WxMaService;
5+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdRequest;
6+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceGetVerifyIdResponse;
7+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoRequest;
8+
import cn.binarywang.wx.miniapp.bean.face.WxMaFaceQueryVerifyInfoResponse;
9+
import lombok.RequiredArgsConstructor;
10+
import me.chanjar.weixin.common.error.WxErrorException;
11+
12+
import java.nio.charset.StandardCharsets;
13+
import java.security.MessageDigest;
14+
import java.security.NoSuchAlgorithmException;
15+
import java.util.Base64;
16+
17+
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Face.GET_VERIFY_ID_URL;
18+
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Face.QUERY_VERIFY_INFO_URL;
19+
20+
/**
21+
* 微信小程序人脸核身相关接口实现
22+
*
23+
* @author <a href="https://github.com/github-copilot">Github Copilot</a>
24+
*/
25+
@RequiredArgsConstructor
26+
public class WxMaFaceServiceImpl implements WxMaFaceService {
27+
private final WxMaService service;
28+
29+
@Override
30+
public WxMaFaceGetVerifyIdResponse getVerifyId(WxMaFaceGetVerifyIdRequest request)
31+
throws WxErrorException {
32+
String responseContent = this.service.post(GET_VERIFY_ID_URL, request.toJson());
33+
return WxMaFaceGetVerifyIdResponse.fromJson(responseContent);
34+
}
35+
36+
@Override
37+
public WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRequest request)
38+
throws WxErrorException {
39+
String responseContent = this.service.post(QUERY_VERIFY_INFO_URL, request.toJson());
40+
return WxMaFaceQueryVerifyInfoResponse.fromJson(responseContent);
41+
}
42+
43+
/**
44+
* 计算证件信息摘要(cert_hash)
45+
* <p>
46+
* 计算规则:
47+
* 1. 对 cert_type、cert_name、cert_no 字段内容进行标准 base64 编码(若含中文等Unicode字符,先进行UTF-8编码)
48+
* 2. 按顺序拼接各个字段:cert_type=xxx&amp;cert_name=xxx&amp;cert_no=xxx
49+
* 3. 对拼接串进行 SHA256 并输出十六进制小写结果
50+
* </p>
51+
*
52+
* @param certType 证件类型
53+
* @param certName 证件姓名
54+
* @param certNo 证件号码
55+
* @return cert_hash 十六进制小写字符串
56+
*/
57+
public static String calcCertHash(String certType, String certName, String certNo) {
58+
String encodedType = Base64.getEncoder().encodeToString(certType.getBytes(StandardCharsets.UTF_8));
59+
String encodedName = Base64.getEncoder().encodeToString(certName.getBytes(StandardCharsets.UTF_8));
60+
String encodedNo = Base64.getEncoder().encodeToString(certNo.getBytes(StandardCharsets.UTF_8));
61+
String raw = "cert_type=" + encodedType + "&cert_name=" + encodedName + "&cert_no=" + encodedNo;
62+
try {
63+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
64+
byte[] hashBytes = digest.digest(raw.getBytes(StandardCharsets.UTF_8));
65+
StringBuilder hex = new StringBuilder();
66+
for (byte b : hashBytes) {
67+
hex.append(String.format("%02x", b));
68+
}
69+
return hex.toString();
70+
} catch (NoSuchAlgorithmException e) {
71+
throw new IllegalStateException("SHA-256 algorithm not available", e);
72+
}
73+
}
74+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package cn.binarywang.wx.miniapp.bean.face;
2+
3+
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
4+
import com.google.gson.annotations.SerializedName;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Builder;
7+
import lombok.Data;
8+
import lombok.NoArgsConstructor;
9+
10+
import java.io.Serializable;
11+
12+
/**
13+
* 获取用户人脸核身会话唯一标识 请求实体
14+
* <p>
15+
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
16+
* </p>
17+
*
18+
* @author <a href="https://github.com/github-copilot">Github Copilot</a>
19+
*/
20+
@Data
21+
@Builder
22+
@NoArgsConstructor
23+
@AllArgsConstructor
24+
public class WxMaFaceGetVerifyIdRequest implements Serializable {
25+
private static final long serialVersionUID = 1L;
26+
27+
/**
28+
* <pre>
29+
* 字段名:业务方系统内部流水号
30+
* 是否必填:是
31+
* 描述:要求5-32个字符内,只能包含数字、大小写字母和_-字符,且在同一个appid下唯一
32+
* </pre>
33+
*/
34+
@SerializedName("out_seq_no")
35+
private String outSeqNo;
36+
37+
/**
38+
* <pre>
39+
* 字段名:用户身份信息
40+
* 是否必填:是
41+
* 描述:证件信息对象
42+
* </pre>
43+
*/
44+
@SerializedName("cert_info")
45+
private CertInfo certInfo;
46+
47+
/**
48+
* <pre>
49+
* 字段名:用户身份标识
50+
* 是否必填:是
51+
* 描述:用户的openid
52+
* </pre>
53+
*/
54+
@SerializedName("openid")
55+
private String openid;
56+
57+
/**
58+
* 用户身份信息
59+
*/
60+
@Data
61+
@Builder
62+
@NoArgsConstructor
63+
@AllArgsConstructor
64+
public static class CertInfo implements Serializable {
65+
private static final long serialVersionUID = 1L;
66+
67+
/**
68+
* <pre>
69+
* 字段名:证件类型
70+
* 是否必填:是
71+
* 描述:证件类型,身份证填 IDENTITY_CARD
72+
* </pre>
73+
*/
74+
@SerializedName("cert_type")
75+
private String certType;
76+
77+
/**
78+
* <pre>
79+
* 字段名:证件姓名
80+
* 是否必填:是
81+
* 描述:证件上的姓名,UTF-8编码
82+
* </pre>
83+
*/
84+
@SerializedName("cert_name")
85+
private String certName;
86+
87+
/**
88+
* <pre>
89+
* 字段名:证件号码
90+
* 是否必填:是
91+
* 描述:证件号码
92+
* </pre>
93+
*/
94+
@SerializedName("cert_no")
95+
private String certNo;
96+
}
97+
98+
public String toJson() {
99+
return WxMaGsonBuilder.create().toJson(this);
100+
}
101+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cn.binarywang.wx.miniapp.bean.face;
2+
3+
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
4+
import com.google.gson.annotations.SerializedName;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.io.Serializable;
10+
11+
/**
12+
* 获取用户人脸核身会话唯一标识 响应实体
13+
* <p>
14+
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/server/API/face/api_getverifyid">获取用户人脸核身会话唯一标识</a>
15+
* </p>
16+
*
17+
* @author <a href="https://github.com/github-copilot">Github Copilot</a>
18+
*/
19+
@Data
20+
@NoArgsConstructor
21+
@AllArgsConstructor
22+
public class WxMaFaceGetVerifyIdResponse implements Serializable {
23+
private static final long serialVersionUID = 1L;
24+
25+
/**
26+
* <pre>
27+
* 字段名:错误码
28+
* 是否必填:是
29+
* 类型:number
30+
* 描述:0表示成功,其他值表示失败
31+
* </pre>
32+
*/
33+
@SerializedName("errcode")
34+
private Integer errcode;
35+
36+
/**
37+
* <pre>
38+
* 字段名:错误信息
39+
* 是否必填:是
40+
* 类型:string
41+
* 描述:错误信息描述
42+
* </pre>
43+
*/
44+
@SerializedName("errmsg")
45+
private String errmsg;
46+
47+
/**
48+
* <pre>
49+
* 字段名:人脸核身会话唯一标识
50+
* 是否必填:否
51+
* 类型:string
52+
* 描述:微信侧生成的人脸核身会话唯一标识,用于后续接口调用,长度不超过256字符
53+
* </pre>
54+
*/
55+
@SerializedName("verify_id")
56+
private String verifyId;
57+
58+
/**
59+
* <pre>
60+
* 字段名:有效期
61+
* 是否必填:否
62+
* 类型:number
63+
* 描述:verify_id有效期,过期后无法发起核身,默认值3600,单位:秒
64+
* </pre>
65+
*/
66+
@SerializedName("expires_in")
67+
private Integer expiresIn;
68+
69+
public static WxMaFaceGetVerifyIdResponse fromJson(String json) {
70+
return WxMaGsonBuilder.create().fromJson(json, WxMaFaceGetVerifyIdResponse.class);
71+
}
72+
}

0 commit comments

Comments
 (0)