Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* 微信支付敏感信息加密
Expand All @@ -36,10 +39,26 @@ public static void encryptFields(Object encryptObject, X509Certificate certifica
}
}

/**
* 递归获取类的所有字段,包括父类中的字段
*
* @param clazz 要获取字段的类
* @return 所有字段的列表
*/
private static List<Field> getAllFields(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
while (clazz != null && clazz != Object.class) {
Field[] declaredFields = clazz.getDeclaredFields();
Collections.addAll(fields, declaredFields);
clazz = clazz.getSuperclass();
}
return fields;
}

private static void encryptField(Object encryptObject, X509Certificate certificate) throws IllegalAccessException, IllegalBlockSizeException {
Class<?> infoClass = encryptObject.getClass();
Field[] infoFieldArray = infoClass.getDeclaredFields();
for (Field field : infoFieldArray) {
List<Field> infoFieldList = getAllFields(infoClass);
for (Field field : infoFieldList) {
if (field.isAnnotationPresent(SpecEncrypt.class)) {
//字段使用了@SpecEncrypt进行标识
if (field.getType().getTypeName().equals(JAVA_LANG_STRING)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.github.binarywang.wxpay.v3.util;

import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingReceiverV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.v3.SpecEncrypt;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import org.testng.annotations.Test;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import static org.testng.Assert.*;

/**
* RsaCryptoUtil 测试类
*/
public class RsaCryptoUtilTest {

/**
* 测试反射能否找到嵌套类中的 @SpecEncrypt 注解字段
*/
@Test
public void testFindAnnotatedFieldsInNestedClass() {
// 创建 Receiver 对象
ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
receiver.setName("测试姓名");

// 使用反射查找带有 @SpecEncrypt 注解的字段
Class<?> receiverClass = receiver.getClass();
Field[] fields = receiverClass.getDeclaredFields();

boolean foundNameField = false;
boolean nameFieldHasAnnotation = false;

for (Field field : fields) {
if (field.getName().equals("name")) {
foundNameField = true;
if (field.isAnnotationPresent(SpecEncrypt.class)) {
nameFieldHasAnnotation = true;
}
}
}

// 验证能够找到 name 字段并且它有 @SpecEncrypt 注解
assertTrue(foundNameField, "应该能找到 name 字段");
assertTrue(nameFieldHasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
}

/**
* 测试嵌套对象中的字段加密
* 验证 List<Receiver> 中每个 Receiver 对象的 name 字段是否能被正确找到和处理
*/
@Test
public void testEncryptFieldsWithNestedObjects() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个测试目前只验证注解存在/字段赋值,并未实际调用 RsaCryptoUtil.encryptFields() 或断言字段已被加密,因此即使修复逻辑回退测试也可能仍然通过(同类问题也出现在本文件的其他测试方法中)。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

// 创建测试对象
ProfitSharingV3Request request = ProfitSharingV3Request.newBuilder()
.appid("test-appid")
.subMchId("test-submchid")
.transactionId("test-transaction")
.outOrderNo("test-order-no")
.unfreezeUnsplit(true)
.build();

List<ProfitSharingV3Request.Receiver> receivers = new ArrayList<>();
ProfitSharingV3Request.Receiver receiver = new ProfitSharingV3Request.Receiver();
receiver.setName("张三"); // 设置需要加密的字段
receiver.setAccount("test-account");
receiver.setType("PERSONAL_OPENID");
receiver.setAmount(100);
receiver.setRelationType("STORE");
receiver.setDescription("测试分账");

receivers.add(receiver);
request.setReceivers(receivers);

// 验证 receivers 字段有 @SpecEncrypt 注解
try {
Field receiversField = ProfitSharingV3Request.class.getDeclaredField("receivers");
boolean hasAnnotation = receiversField.isAnnotationPresent(SpecEncrypt.class);
assertTrue(hasAnnotation, "receivers 字段应该有 @SpecEncrypt 注解");
} catch (NoSuchFieldException e) {
fail("应该能找到 receivers 字段");
}

// 验证name字段不为null
assertNotNull(receiver.getName());
assertEquals(receiver.getName(), "张三");
}

/**
* 测试单个对象中的字段加密
* 验证直接在对象上的 @SpecEncrypt 字段是否能被正确找到
*/
@Test
public void testEncryptFieldsWithDirectField() {
// 创建测试对象
ProfitSharingReceiverV3Request request = ProfitSharingReceiverV3Request.newBuilder()
.appid("test-appid")
.subMchId("test-submchid")
.type("PERSONAL_OPENID")
.account("test-account")
.name("李四")
.relationType("STORE")
.build();

// 验证 name 字段有 @SpecEncrypt 注解
try {
Field nameField = ProfitSharingReceiverV3Request.class.getDeclaredField("name");
boolean hasAnnotation = nameField.isAnnotationPresent(SpecEncrypt.class);
assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解");
} catch (NoSuchFieldException e) {
fail("应该能找到 name 字段");
}

// 验证name字段不为null
assertNotNull(request.getName());
assertEquals(request.getName(), "李四");
}

/**
* 测试类继承场景下的字段加密
* 验证父类中带 @SpecEncrypt 注解的字段是否能被正确找到和加密
*/
@Test
public void testEncryptFieldsWithInheritance() {
// 定义测试用的父类和子类
@Data
class ParentRequest {
@SpecEncrypt
@SerializedName("parent_name")
private String parentName;
}

@Data
@lombok.EqualsAndHashCode(callSuper = false)
class ChildRequest extends ParentRequest {
@SpecEncrypt
@SerializedName("child_name")
private String childName;

@Override
protected boolean canEqual(final Object other) {
return other instanceof ChildRequest;
}
}

// 创建子类实例
ChildRequest request = new ChildRequest();
request.setParentName("父类字段");
request.setChildName("子类字段");

// 验证能够找到父类和子类的字段
// 使用 getDeclaredFields 只能找到子类字段
Field[] childFields = ChildRequest.class.getDeclaredFields();

// 使用反射调用 RsaCryptoUtil 的私有 getAllFields 方法
int annotatedFieldCount = 0;
try {
java.lang.reflect.Method getAllFieldsMethod = RsaCryptoUtil.class.getDeclaredMethod("getAllFields", Class.class);
getAllFieldsMethod.setAccessible(true);
@SuppressWarnings("unchecked")
List<Field> allFields = (List<Field>) getAllFieldsMethod.invoke(null, ChildRequest.class);

for (Field field : allFields) {
if (field.isAnnotationPresent(SpecEncrypt.class)) {
annotatedFieldCount++;
}
}
} catch (Exception e) {
fail("无法调用 getAllFields 方法: " + e.getMessage());
}

// 应该找到2个带注解的字段(parentName 和 childName)
assertTrue(annotatedFieldCount >= 2, "应该能找到至少2个带 @SpecEncrypt 注解的字段");
}
}