-
-
Notifications
You must be signed in to change notification settings - Fork 9k
修复 RsaCryptoUtil 无法加密继承字段和嵌套对象的问题 #3841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Co-authored-by: binarywang <[email protected]>
Co-authored-by: binarywang <[email protected]>
Co-authored-by: binarywang <[email protected]>
Co-authored-by: binarywang <[email protected]>
Co-authored-by: binarywang <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
此 PR 修复了 RsaCryptoUtil.encryptFields() 方法无法加密父类继承字段和嵌套对象字段的问题。原有实现使用 getDeclaredFields() 只能获取当前类声明的字段,导致父类字段和集合元素内部的加密字段被忽略。
主要变更:
- 新增
getAllFields()辅助方法,递归获取类及其所有父类的字段 - 修改
encryptField()方法,使用getAllFields()替代getDeclaredFields() - 新增测试类
RsaCryptoUtilTest,包含嵌套对象、继承场景和反射验证的测试用例
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java | 新增 getAllFields() 方法支持获取继承字段,修改 encryptField() 使用新方法 |
| weixin-java-pay/src/test/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtilTest.java | 新增测试类,包含嵌套对象、直接字段和继承场景的测试用例 |
| List<Field> fields = new ArrayList<>(); | ||
| while (clazz != null && clazz != Object.class) { | ||
| Field[] declaredFields = clazz.getDeclaredFields(); | ||
| java.util.Collections.addAll(fields, declaredFields); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在第52行,应该直接使用 Collections.addAll(fields, declaredFields) 而不是使用完全限定名 java.util.Collections.addAll(fields, declaredFields)。因为已经在第20行导入了 java.util.Collections,使用完全限定名是多余的,不符合代码风格。
| java.util.Collections.addAll(fields, declaredFields); | |
| Collections.addAll(fields, declaredFields); |
| System.out.println("=== Receiver 类中的所有字段 ==="); | ||
| boolean foundNameField = false; | ||
| boolean nameFieldHasAnnotation = false; | ||
|
|
||
| for (Field field : fields) { | ||
| System.out.println("字段名: " + field.getName() + ", 类型: " + field.getType().getName()); | ||
| if (field.getName().equals("name")) { | ||
| foundNameField = true; | ||
| if (field.isAnnotationPresent(SpecEncrypt.class)) { | ||
| nameFieldHasAnnotation = true; | ||
| System.out.println(" -> name 字段有 @SpecEncrypt 注解"); | ||
| } else { | ||
| System.out.println(" -> name 字段没有 @SpecEncrypt 注解"); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在测试代码中使用 System.out.println 进行调试输出不是最佳实践。建议移除这些调试输出语句,或者使用日志框架(如 SLF4J)进行日志记录。保留的调试输出会在测试运行时产生不必要的控制台输出。
| System.out.println("ProfitSharingV3Request.receivers 字段有 @SpecEncrypt 注解: " + hasAnnotation); | ||
| assertTrue(hasAnnotation, "receivers 字段应该有 @SpecEncrypt 注解"); | ||
| } catch (NoSuchFieldException e) { | ||
| fail("应该能找到 receivers 字段"); | ||
| } | ||
|
|
||
| System.out.println("测试对象创建成功,name字段: " + receiver.getName()); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在测试代码中使用 System.out.println 进行调试输出不是最佳实践。建议移除这些调试输出语句,或者使用日志框架(如 SLF4J)进行日志记录。保留的调试输出会在测试运行时产生不必要的控制台输出。
| System.out.println("ProfitSharingReceiverV3Request.name 字段有 @SpecEncrypt 注解: " + hasAnnotation); | ||
| assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解"); | ||
| } catch (NoSuchFieldException e) { | ||
| fail("应该能找到 name 字段"); | ||
| } | ||
|
|
||
| System.out.println("测试对象创建成功,name字段: " + request.getName()); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在测试代码中使用 System.out.println 进行调试输出不是最佳实践。建议移除这些调试输出语句,或者使用日志框架(如 SLF4J)进行日志记录。保留的调试输出会在测试运行时产生不必要的控制台输出。
| System.out.println("ProfitSharingReceiverV3Request.name 字段有 @SpecEncrypt 注解: " + hasAnnotation); | |
| assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解"); | |
| } catch (NoSuchFieldException e) { | |
| fail("应该能找到 name 字段"); | |
| } | |
| System.out.println("测试对象创建成功,name字段: " + request.getName()); | |
| assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解"); | |
| } catch (NoSuchFieldException e) { | |
| fail("应该能找到 name 字段"); | |
| } | |
| System.out.println("=== 测试继承场景 ==="); | ||
| System.out.println("父类字段值: " + request.getParentName()); | ||
| System.out.println("子类字段值: " + request.getChildName()); | ||
|
|
||
| // 使用 getDeclaredFields 只能找到子类字段 | ||
| Field[] childFields = ChildRequest.class.getDeclaredFields(); | ||
| System.out.println("使用 getDeclaredFields 找到的字段数: " + childFields.length); | ||
|
|
||
| // 使用 getAllFields 辅助方法应该能找到父类和子类的所有字段 | ||
| List<Field> allFields = getAllFields(ChildRequest.class); | ||
| System.out.println("使用 getAllFields 找到的字段数: " + allFields.size()); | ||
|
|
||
| int annotatedFieldCount = 0; | ||
| for (Field field : allFields) { | ||
| if (field.isAnnotationPresent(SpecEncrypt.class)) { | ||
| annotatedFieldCount++; | ||
| System.out.println(" -> 找到带 @SpecEncrypt 注解的字段: " + field.getName()); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在测试代码中使用 System.out.println 进行调试输出不是最佳实践。建议移除这些调试输出语句,或者使用日志框架(如 SLF4J)进行日志记录。保留的调试输出会在测试运行时产生不必要的控制台输出。
| System.out.println("=== 测试继承场景 ==="); | |
| System.out.println("父类字段值: " + request.getParentName()); | |
| System.out.println("子类字段值: " + request.getChildName()); | |
| // 使用 getDeclaredFields 只能找到子类字段 | |
| Field[] childFields = ChildRequest.class.getDeclaredFields(); | |
| System.out.println("使用 getDeclaredFields 找到的字段数: " + childFields.length); | |
| // 使用 getAllFields 辅助方法应该能找到父类和子类的所有字段 | |
| List<Field> allFields = getAllFields(ChildRequest.class); | |
| System.out.println("使用 getAllFields 找到的字段数: " + allFields.size()); | |
| int annotatedFieldCount = 0; | |
| for (Field field : allFields) { | |
| if (field.isAnnotationPresent(SpecEncrypt.class)) { | |
| annotatedFieldCount++; | |
| System.out.println(" -> 找到带 @SpecEncrypt 注解的字段: " + field.getName()); | |
| // 使用 getDeclaredFields 只能找到子类字段 | |
| Field[] childFields = ChildRequest.class.getDeclaredFields(); | |
| // 使用 getAllFields 辅助方法应该能找到父类和子类的所有字段 | |
| List<Field> allFields = getAllFields(ChildRequest.class); | |
| int annotatedFieldCount = 0; | |
| for (Field field : allFields) { | |
| if (field.isAnnotationPresent(SpecEncrypt.class)) { | |
| annotatedFieldCount++; | |
| @Test | ||
| public void testEncryptFieldsWithNestedObjects() { | ||
| // 创建测试对象 | ||
| 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); | ||
| System.out.println("ProfitSharingV3Request.receivers 字段有 @SpecEncrypt 注解: " + hasAnnotation); | ||
| assertTrue(hasAnnotation, "receivers 字段应该有 @SpecEncrypt 注解"); | ||
| } catch (NoSuchFieldException e) { | ||
| fail("应该能找到 receivers 字段"); | ||
| } | ||
|
|
||
| System.out.println("测试对象创建成功,name字段: " + receiver.getName()); | ||
| // 验证name字段不为null | ||
| assertNotNull(receiver.getName()); | ||
| assertEquals(receiver.getName(), "张三"); | ||
| } |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
测试方法 testEncryptFieldsWithNestedObjects 和 testEncryptFieldsWithDirectField 没有实际调用 RsaCryptoUtil.encryptFields() 方法来验证加密功能是否正常工作。这些测试只验证了字段存在性和注解存在性,但没有测试核心的加密逻辑。建议添加实际的加密调用和验证,确保修复后的方法能够正确加密嵌套对象中的字段。
| @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); | ||
| System.out.println("ProfitSharingReceiverV3Request.name 字段有 @SpecEncrypt 注解: " + hasAnnotation); | ||
| assertTrue(hasAnnotation, "name 字段应该有 @SpecEncrypt 注解"); | ||
| } catch (NoSuchFieldException e) { | ||
| fail("应该能找到 name 字段"); | ||
| } | ||
|
|
||
| System.out.println("测试对象创建成功,name字段: " + request.getName()); | ||
| // 验证name字段不为null | ||
| assertNotNull(request.getName()); | ||
| assertEquals(request.getName(), "李四"); | ||
| } |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
测试方法 testEncryptFieldsWithDirectField 没有实际调用 RsaCryptoUtil.encryptFields() 方法来验证加密功能是否正常工作。此测试只验证了字段存在性和注解存在性,但没有测试核心的加密逻辑。建议添加实际的加密调用和验证,确保修复后的方法能够正确加密字段。
| private List<Field> getAllFields(Class<?> clazz) { | ||
| List<Field> fields = new ArrayList<>(); | ||
| while (clazz != null && clazz != Object.class) { | ||
| Field[] declaredFields = clazz.getDeclaredFields(); | ||
| java.util.Collections.addAll(fields, declaredFields); | ||
| clazz = clazz.getSuperclass(); | ||
| } | ||
| return fields; | ||
| } |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
辅助方法 getAllFields 在测试类中重复实现了与生产代码中相同的逻辑。这会导致代码重复,如果将来需要修改这个逻辑,需要在两个地方同时修改。建议直接使用反射调用 RsaCryptoUtil 中的私有 getAllFields 方法,或者将该方法改为包级别可见性以便测试使用。
| } | ||
|
|
||
| @Data | ||
| @lombok.EqualsAndHashCode(callSuper = false) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method overrides ParentRequest.canEqual; it is advisable to add an Override annotation.
🤖 Augment PR SummarySummary: 本 PR 修复了微信支付 v3 敏感信息加密在“父类继承字段/嵌套对象”场景下无法生效的问题(例如分账请求里 Receiver 的继承字段未被加密)。 Changes:
Technical Notes: 改动向后兼容,主要提升了对复杂请求对象(集合元素、继承层级、深层嵌套)中敏感字段的处理完整性。 🤖 Was this summary useful? React with 👍 or 👎 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * 验证 List<Receiver> 中每个 Receiver 对象的 name 字段是否能被正确找到和处理 | ||
| */ | ||
| @Test | ||
| public void testEncryptFieldsWithNestedObjects() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| System.out.println("使用 getDeclaredFields 找到的字段数: " + childFields.length); | ||
|
|
||
| // 使用 getAllFields 辅助方法应该能找到父类和子类的所有字段 | ||
| List<Field> allFields = getAllFields(ChildRequest.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RsaCryptoUtil.encryptFields()使用getDeclaredFields()只能获取当前类声明的字段,导致无法加密父类继承的字段,也无法递归处理嵌套对象中的加密字段。问题场景
修改内容
新增
getAllFields()方法:递归获取类及其所有父类的字段修改
encryptField()方法:使用getAllFields()替代getDeclaredFields()新增测试用例:覆盖嵌套对象加密和继承场景
影响范围
向后兼容。现有加密功能不受影响,增强了对以下场景的支持:
List<Receiver>中的receiver.name)@SpecEncrypt字段Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.