From 3d9ef9750cf76429f46ce311fc6c2b2a8a20dab5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:26:09 +0000 Subject: [PATCH 1/6] Initial plan From f49fbe2f94b8297527c35ef0ccd7f941c165a5b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:34:21 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E4=BA=BA=E8=84=B8=E6=A0=B8=E8=BA=AB?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 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> --- .../wx/miniapp/api/WxMaFaceService.java | 51 +++++++++ .../wx/miniapp/api/WxMaService.java | 9 ++ .../miniapp/api/impl/BaseWxMaServiceImpl.java | 6 ++ .../miniapp/api/impl/WxMaFaceServiceImpl.java | 74 +++++++++++++ .../bean/face/WxMaFaceGetVerifyIdRequest.java | 101 ++++++++++++++++++ .../face/WxMaFaceGetVerifyIdResponse.java | 72 +++++++++++++ .../face/WxMaFaceQueryVerifyInfoRequest.java | 72 +++++++++++++ .../face/WxMaFaceQueryVerifyInfoResponse.java | 73 +++++++++++++ .../miniapp/constant/WxMaApiUrlConstants.java | 13 +++ .../api/impl/WxMaFaceServiceImplTest.java | 71 ++++++++++++ 10 files changed, 542 insertions(+) create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java new file mode 100644 index 000000000..ad15950d4 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java @@ -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; + +/** + * 微信小程序人脸核身相关接口 + *

+ * 文档地址:微信人脸核身接口列表 + *

+ * + * @author Github Copilot + */ +public interface WxMaFaceService { + + /** + * 获取用户人脸核身会话唯一标识 + *

+ * 业务方后台根据「用户实名信息(姓名+身份证)」调用 getVerifyId 接口获取人脸核身会话唯一标识 verifyId 字段, + * 然后给到小程序前端调用 wx.requestFacialVerify 接口使用。 + *

+ *

+ * 文档地址:获取用户人脸核身会话唯一标识 + *

+ * + * @param request 请求参数 + * @return 包含 verifyId 的响应实体 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + WxMaFaceGetVerifyIdResponse getVerifyId(WxMaFaceGetVerifyIdRequest request) throws WxErrorException; + + /** + * 查询用户人脸核身真实验证结果 + *

+ * 业务方后台根据人脸核身会话唯一标识 verifyId 字段调用 queryVerifyInfo 接口查询用户人脸核身真实验证结果。 + * 核身通过的判断条件:errcode=0 且 verify_ret=10000。 + *

+ *

+ * 文档地址:查询用户人脸核身真实验证结果 + *

+ * + * @param request 请求参数 + * @return 包含 verifyRet 的响应实体 + * @throws WxErrorException 调用微信接口失败时抛出 + */ + WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRequest request) throws WxErrorException; + +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java index 37a6ca8de..730a8c584 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java @@ -631,4 +631,13 @@ WxMaApiResponse execute( * @return 用工关系服务对象WxMaEmployeeRelationService */ WxMaEmployeeRelationService getEmployeeRelationService(); + + /** + * 获取人脸核身服务对象。 + *
+ * 文档:https://developers.weixin.qq.com/miniprogram/dev/server/API/face/ + * + * @return 人脸核身服务对象WxMaFaceService + */ + WxMaFaceService getFaceService(); } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java index c0e1ff4a4..47ce08bef 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java @@ -169,6 +169,7 @@ public abstract class BaseWxMaServiceImpl 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 configMap = new HashMap<>(); private int retrySleepMillis = 1000; @@ -1055,4 +1056,9 @@ public WxMaComplaintService getComplaintService() { public WxMaEmployeeRelationService getEmployeeRelationService() { return this.employeeRelationService; } + + @Override + public WxMaFaceService getFaceService() { + return this.faceService; + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java new file mode 100644 index 000000000..92528def4 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java @@ -0,0 +1,74 @@ +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 java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +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 Github Copilot + */ +@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); + } + + /** + * 计算证件信息摘要(cert_hash) + *

+ * 计算规则: + * 1. 对 cert_type、cert_name、cert_no 字段内容进行标准 base64 编码(若含中文等Unicode字符,先进行UTF-8编码) + * 2. 按顺序拼接各个字段:cert_type=xxx&cert_name=xxx&cert_no=xxx + * 3. 对拼接串进行 SHA256 并输出十六进制小写结果 + *

+ * + * @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); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java new file mode 100644 index 000000000..6053f042f --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java @@ -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; + +/** + * 获取用户人脸核身会话唯一标识 请求实体 + *

+ * 文档地址:获取用户人脸核身会话唯一标识 + *

+ * + * @author Github Copilot + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaFaceGetVerifyIdRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:业务方系统内部流水号
+   * 是否必填:是
+   * 描述:要求5-32个字符内,只能包含数字、大小写字母和_-字符,且在同一个appid下唯一
+   * 
+ */ + @SerializedName("out_seq_no") + private String outSeqNo; + + /** + *
+   * 字段名:用户身份信息
+   * 是否必填:是
+   * 描述:证件信息对象
+   * 
+ */ + @SerializedName("cert_info") + private CertInfo certInfo; + + /** + *
+   * 字段名:用户身份标识
+   * 是否必填:是
+   * 描述:用户的openid
+   * 
+ */ + @SerializedName("openid") + private String openid; + + /** + * 用户身份信息 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CertInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+     * 字段名:证件类型
+     * 是否必填:是
+     * 描述:证件类型,身份证填 IDENTITY_CARD
+     * 
+ */ + @SerializedName("cert_type") + private String certType; + + /** + *
+     * 字段名:证件姓名
+     * 是否必填:是
+     * 描述:证件上的姓名,UTF-8编码
+     * 
+ */ + @SerializedName("cert_name") + private String certName; + + /** + *
+     * 字段名:证件号码
+     * 是否必填:是
+     * 描述:证件号码
+     * 
+ */ + @SerializedName("cert_no") + private String certNo; + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java new file mode 100644 index 000000000..86f114efd --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java @@ -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; + +/** + * 获取用户人脸核身会话唯一标识 响应实体 + *

+ * 文档地址:获取用户人脸核身会话唯一标识 + *

+ * + * @author Github Copilot + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WxMaFaceGetVerifyIdResponse implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:错误码
+   * 是否必填:是
+   * 类型:number
+   * 描述:0表示成功,其他值表示失败
+   * 
+ */ + @SerializedName("errcode") + private Integer errcode; + + /** + *
+   * 字段名:错误信息
+   * 是否必填:是
+   * 类型:string
+   * 描述:错误信息描述
+   * 
+ */ + @SerializedName("errmsg") + private String errmsg; + + /** + *
+   * 字段名:人脸核身会话唯一标识
+   * 是否必填:否
+   * 类型:string
+   * 描述:微信侧生成的人脸核身会话唯一标识,用于后续接口调用,长度不超过256字符
+   * 
+ */ + @SerializedName("verify_id") + private String verifyId; + + /** + *
+   * 字段名:有效期
+   * 是否必填:否
+   * 类型:number
+   * 描述:verify_id有效期,过期后无法发起核身,默认值3600,单位:秒
+   * 
+ */ + @SerializedName("expires_in") + private Integer expiresIn; + + public static WxMaFaceGetVerifyIdResponse fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaFaceGetVerifyIdResponse.class); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java new file mode 100644 index 000000000..59f23e588 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java @@ -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.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 查询用户人脸核身真实验证结果 请求实体 + *

+ * 文档地址:查询用户人脸核身真实验证结果 + *

+ * + * @author Github Copilot + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaFaceQueryVerifyInfoRequest implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:人脸核身会话唯一标识
+   * 是否必填:是
+   * 描述:getVerifyId接口返回的人脸核身会话唯一标识
+   * 
+ */ + @SerializedName("verify_id") + private String verifyId; + + /** + *
+   * 字段名:业务方系统外部流水号
+   * 是否必填:是
+   * 描述:必须和getVerifyId接口传入的out_seq_no一致
+   * 
+ */ + @SerializedName("out_seq_no") + private String outSeqNo; + + /** + *
+   * 字段名:证件信息摘要
+   * 是否必填:是
+   * 描述:根据getVerifyId中传入的证件信息生成的信息摘要。
+   *       计算方式:对cert_info中的cert_type、cert_name、cert_no字段内容进行标准base64编码,
+   *       按顺序拼接:cert_type=xxx&cert_name=xxx&cert_no=xxx,再对拼接串进行SHA256输出十六进制小写结果
+   * 
+ */ + @SerializedName("cert_hash") + private String certHash; + + /** + *
+   * 字段名:用户身份标识
+   * 是否必填:是
+   * 描述:必须和getVerifyId接口传入的openid一致
+   * 
+ */ + @SerializedName("openid") + private String openid; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java new file mode 100644 index 000000000..60845a364 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java @@ -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; + +/** + * 查询用户人脸核身真实验证结果 响应实体 + *

+ * 文档地址:查询用户人脸核身真实验证结果 + *

+ * + * @author Github Copilot + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WxMaFaceQueryVerifyInfoResponse implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *
+   * 字段名:错误码
+   * 是否必填:是
+   * 类型:number
+   * 描述:0表示成功,其他值表示失败
+   * 
+ */ + @SerializedName("errcode") + private Integer errcode; + + /** + *
+   * 字段名:错误信息
+   * 是否必填:是
+   * 类型:string
+   * 描述:错误信息描述
+   * 
+ */ + @SerializedName("errmsg") + private String errmsg; + + /** + *
+   * 字段名:人脸核身验证结果
+   * 是否必填:否
+   * 类型:number
+   * 描述:核身通过的判断条件:errcode=0 且 verify_ret=10000
+   *       枚举值说明:
+   *       10000 - 识别成功
+   *       10001 - 参数错误
+   *       10002 - 人脸特征检测失败
+   *       10003 - 身份证号不匹配
+   *       10004 - 比对人脸信息不匹配
+   *       10005 - 正在检测中
+   *       10006 - appid没有权限
+   *       10300 - 未完成核身
+   *       90001 - 设备不支持人脸检测
+   *       90002 - 用户取消
+   *       其他枚举值请参见官方文档
+   * 
+ */ + @SerializedName("verify_ret") + private Integer verifyRet; + + public static WxMaFaceQueryVerifyInfoResponse fromJson(String json) { + return WxMaGsonBuilder.create().fromJson(json, WxMaFaceQueryVerifyInfoResponse.class); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java index 58b10039a..40633ea6d 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java @@ -1018,4 +1018,17 @@ public interface Employee { /** 推送用工消息 */ String SEND_EMPLOYEE_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/employeerelationmsg/send"; } + + /** + * 微信人脸核身接口 + *
+   * 文档地址: https://developers.weixin.qq.com/miniprogram/dev/server/API/face/
+   * 
+ */ + 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"; + } } diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java new file mode 100644 index 000000000..e21deb3e7 --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java @@ -0,0 +1,71 @@ +package cn.binarywang.wx.miniapp.api.impl; + +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 cn.binarywang.wx.miniapp.test.ApiTestModule; +import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; + +/** + * 微信小程序人脸核身服务测试类 + * + * @author Github Copilot + */ +@Test +@Guice(modules = ApiTestModule.class) +public class WxMaFaceServiceImplTest { + + @Inject + private WxMaService wxService; + + @Test + public void testGetVerifyId() throws WxErrorException { + WxMaFaceGetVerifyIdRequest request = WxMaFaceGetVerifyIdRequest.builder() + .outSeqNo("TEST20240101001") + .certInfo(WxMaFaceGetVerifyIdRequest.CertInfo.builder() + .certType("IDENTITY_CARD") + .certName("张三") + .certNo("310101199801011234") + .build()) + .openid("test_openid_001") + .build(); + + WxMaFaceGetVerifyIdResponse response = this.wxService.getFaceService().getVerifyId(request); + assertNotNull(response); + } + + @Test + public void testQueryVerifyInfo() throws WxErrorException { + String certType = "IDENTITY_CARD"; + String certName = "张三"; + String certNo = "310101199801011234"; + String certHash = WxMaFaceServiceImpl.calcCertHash(certType, certName, certNo); + + WxMaFaceQueryVerifyInfoRequest request = WxMaFaceQueryVerifyInfoRequest.builder() + .verifyId("test_verify_id_001") + .outSeqNo("TEST20240101001") + .certHash(certHash) + .openid("test_openid_001") + .build(); + + WxMaFaceQueryVerifyInfoResponse response = this.wxService.getFaceService().queryVerifyInfo(request); + assertNotNull(response); + } + + @Test + public void testCalcCertHash() { + // 验证官方文档给出的测试用例: + // cert_info: {"cert_type":"IDENTITY_CARD","cert_name":"张三","cert_no":"310101199801011234"} + // 期望结果:3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b + String certHash = WxMaFaceServiceImpl.calcCertHash("IDENTITY_CARD", "张三", "310101199801011234"); + assertEquals(certHash, "3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b"); + } +} From 5ab453ce25a8cbced01ed318c73cc04e43c941b3 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Thu, 5 Mar 2026 12:18:56 +0800 Subject: [PATCH 3/6] Update weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java index e21deb3e7..8da7764c1 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java @@ -17,7 +17,7 @@ /** * 微信小程序人脸核身服务测试类 * - * @author Github Copilot + * @author GitHub Copilot */ @Test @Guice(modules = ApiTestModule.class) From 0523de4584869bb99dbc7d0b1332d96827adc290 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Thu, 5 Mar 2026 12:19:09 +0800 Subject: [PATCH 4/6] Update weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java index ad15950d4..1b8d3cd74 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaFaceService.java @@ -12,7 +12,7 @@ * 文档地址:微信人脸核身接口列表 *

* - * @author Github Copilot + * @author GitHub Copilot */ public interface WxMaFaceService { From 4b8c02548bf876f8585ede1de21f2f61511ca034 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Thu, 5 Mar 2026 12:19:22 +0800 Subject: [PATCH 5/6] Update weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java index 86f114efd..8fe8b191a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdResponse.java @@ -14,7 +14,7 @@ * 文档地址:获取用户人脸核身会话唯一标识 *

* - * @author Github Copilot + * @author GitHub Copilot */ @Data @NoArgsConstructor From 6297422b0ea40855df30d7ed7f08291aed5aab76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:22:55 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=20PR=20=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E6=84=8F=E8=A7=81=E4=BC=98=E5=8C=96=E4=BA=BA=E8=84=B8?= =?UTF-8?q?=E6=A0=B8=E8=BA=AB=E6=9C=8D=E5=8A=A1=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 calcCertHash 工具方法从 WxMaFaceServiceImpl 移到 WxMaFaceQueryVerifyInfoRequest 中 - 修复所有文件中 Github → GitHub 拼写错误 - 重构测试类:移除依赖真实接口的测试,只保留本地 calcCertHash 计算测试 - 修复测试类断言库混用问题,统一使用 TestNG 的 org.testng.Assert Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com> --- .../miniapp/api/impl/WxMaFaceServiceImpl.java | 39 +------------- .../bean/face/WxMaFaceGetVerifyIdRequest.java | 2 +- .../face/WxMaFaceQueryVerifyInfoRequest.java | 38 +++++++++++++- .../face/WxMaFaceQueryVerifyInfoResponse.java | 2 +- .../api/impl/WxMaFaceServiceImplTest.java | 52 ++----------------- 5 files changed, 43 insertions(+), 90 deletions(-) diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java index 92528def4..7b3e2a1a5 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImpl.java @@ -9,18 +9,13 @@ import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; - 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 Github Copilot + * @author GitHub Copilot */ @RequiredArgsConstructor public class WxMaFaceServiceImpl implements WxMaFaceService { @@ -39,36 +34,4 @@ public WxMaFaceQueryVerifyInfoResponse queryVerifyInfo(WxMaFaceQueryVerifyInfoRe String responseContent = this.service.post(QUERY_VERIFY_INFO_URL, request.toJson()); return WxMaFaceQueryVerifyInfoResponse.fromJson(responseContent); } - - /** - * 计算证件信息摘要(cert_hash) - *

- * 计算规则: - * 1. 对 cert_type、cert_name、cert_no 字段内容进行标准 base64 编码(若含中文等Unicode字符,先进行UTF-8编码) - * 2. 按顺序拼接各个字段:cert_type=xxx&cert_name=xxx&cert_no=xxx - * 3. 对拼接串进行 SHA256 并输出十六进制小写结果 - *

- * - * @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); - } - } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java index 6053f042f..4d83ba808 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceGetVerifyIdRequest.java @@ -15,7 +15,7 @@ * 文档地址:获取用户人脸核身会话唯一标识 *

* - * @author Github Copilot + * @author GitHub Copilot */ @Data @Builder diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java index 59f23e588..7ab8f7fbf 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoRequest.java @@ -8,6 +8,10 @@ import lombok.NoArgsConstructor; import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; /** * 查询用户人脸核身真实验证结果 请求实体 @@ -15,7 +19,7 @@ * 文档地址:查询用户人脸核身真实验证结果 *

* - * @author Github Copilot + * @author GitHub Copilot */ @Data @Builder @@ -69,4 +73,36 @@ public class WxMaFaceQueryVerifyInfoRequest implements Serializable { public String toJson() { return WxMaGsonBuilder.create().toJson(this); } + + /** + * 计算证件信息摘要(cert_hash) + *

+ * 计算规则(参见官方文档): + * 1. 对 cert_type、cert_name、cert_no 字段内容进行标准 base64 编码(若含中文等 Unicode 字符,先进行 UTF-8 编码) + * 2. 按顺序拼接各个字段:cert_type=xxx&cert_name=xxx&cert_no=xxx + * 3. 对拼接串进行 SHA256 并输出十六进制小写结果 + *

+ * + * @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); + } + } } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java index 60845a364..85d984e1d 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/face/WxMaFaceQueryVerifyInfoResponse.java @@ -14,7 +14,7 @@ * 文档地址:查询用户人脸核身真实验证结果 *

* - * @author Github Copilot + * @author GitHub Copilot */ @Data @NoArgsConstructor diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java index 8da7764c1..77dea4610 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaFaceServiceImplTest.java @@ -1,71 +1,25 @@ package cn.binarywang.wx.miniapp.api.impl; -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 cn.binarywang.wx.miniapp.test.ApiTestModule; -import com.google.inject.Inject; -import me.chanjar.weixin.common.error.WxErrorException; -import org.testng.annotations.Guice; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; /** - * 微信小程序人脸核身服务测试类 + * 微信小程序人脸核身服务本地计算测试类 * * @author GitHub Copilot */ @Test -@Guice(modules = ApiTestModule.class) public class WxMaFaceServiceImplTest { - @Inject - private WxMaService wxService; - - @Test - public void testGetVerifyId() throws WxErrorException { - WxMaFaceGetVerifyIdRequest request = WxMaFaceGetVerifyIdRequest.builder() - .outSeqNo("TEST20240101001") - .certInfo(WxMaFaceGetVerifyIdRequest.CertInfo.builder() - .certType("IDENTITY_CARD") - .certName("张三") - .certNo("310101199801011234") - .build()) - .openid("test_openid_001") - .build(); - - WxMaFaceGetVerifyIdResponse response = this.wxService.getFaceService().getVerifyId(request); - assertNotNull(response); - } - - @Test - public void testQueryVerifyInfo() throws WxErrorException { - String certType = "IDENTITY_CARD"; - String certName = "张三"; - String certNo = "310101199801011234"; - String certHash = WxMaFaceServiceImpl.calcCertHash(certType, certName, certNo); - - WxMaFaceQueryVerifyInfoRequest request = WxMaFaceQueryVerifyInfoRequest.builder() - .verifyId("test_verify_id_001") - .outSeqNo("TEST20240101001") - .certHash(certHash) - .openid("test_openid_001") - .build(); - - WxMaFaceQueryVerifyInfoResponse response = this.wxService.getFaceService().queryVerifyInfo(request); - assertNotNull(response); - } - @Test public void testCalcCertHash() { // 验证官方文档给出的测试用例: // cert_info: {"cert_type":"IDENTITY_CARD","cert_name":"张三","cert_no":"310101199801011234"} // 期望结果:3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b - String certHash = WxMaFaceServiceImpl.calcCertHash("IDENTITY_CARD", "张三", "310101199801011234"); + String certHash = WxMaFaceQueryVerifyInfoRequest.calcCertHash( + "IDENTITY_CARD", "张三", "310101199801011234"); assertEquals(certHash, "3c241f7ff324977aeb91f173bb2a7b06569e6fd784d5573db34a636d8671108b"); } }