diff --git a/scfs-api-core/src/main/java/com/czcb/scfs/api/core/ApiClient.java b/scfs-api-core/src/main/java/com/czcb/scfs/api/core/ApiClient.java index ac4fa96..4d111b2 100644 --- a/scfs-api-core/src/main/java/com/czcb/scfs/api/core/ApiClient.java +++ b/scfs-api-core/src/main/java/com/czcb/scfs/api/core/ApiClient.java @@ -1,11 +1,19 @@ package com.czcb.scfs.api.core; +import com.czcb.scfs.api.core.cipher.PrivacyEncryptor; +import com.czcb.scfs.api.core.cipher.SecretCipher; import com.czcb.scfs.api.core.http.*; import com.czcb.scfs.api.core.util.Extractor; import com.czcb.scfs.api.core.util.Json; +import com.czcb.scfs.api.core.util.Strings; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import java.util.Map; +import static com.czcb.scfs.api.core.Constants.BANK_CERTIFICATE_SERIAL; +import static com.czcb.scfs.api.core.Constants.SECRET_KEY; + /** * @author wangwei * @since 2.0.0 @@ -70,4 +78,65 @@ public interface ApiClient { .build(); return exchange(httpRequest, responseClass); } + + /** + * 通知请求参数解密 + * + * @param requestJsonStr 请求参数JSON字符串 + * @param requestClass 请求参数Class + * @return 请求参数 + * @author H.T + * @since 2026/5/19 + */ + default T decryptRequest(String requestJsonStr, Class requestClass) { + // 解析JSON + JsonObject requestJson = JsonParser.parseString(requestJsonStr).getAsJsonObject(); + // 提取请求头 + JsonObject headerJson = requestJson.getAsJsonObject("header"); + // SECRET_KEY密钥 密文 + String secretKeyCiphertext = headerJson.get(SECRET_KEY).getAsString(); + // 非对称解密后SECRET_KEY密钥 明文 + String secretKeyPlain = getProfile().getPrivacy().getDecryptor().decrypt(secretKeyCiphertext); + // 提取请求体原始body 密文 + String bodyCiphertext = requestJson.get("body").getAsString(); + // 对称解密后body 明文 + String bodyPlain = getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(secretKeyPlain), Strings.toBytes(bodyCiphertext)); + return Json.fromJson(bodyPlain, requestClass); + } + + /** + * 通知应答参数加密 + * + * @param responseObject 应答参数Object + * @return 应答完整JSON + * @author H.T + * @since 2026/5/19 + */ + default String encryptResponse(Object responseObject) { + // 序列化body 明文 + String responseJsonStr = Json.toJson(responseObject); + // 对称加密器 + SecretCipher cipher = getProfile().getPrivacy().getSecretCipher(); + // 生成SECRET_KEY密钥 明文 + byte[] secretKeyPlain = cipher.getSecretKey(); + // 非对称加密器 + PrivacyEncryptor encryptor = getProfile().getPrivacy().getEncryptor(); + // 非对称加密后SECRET_KEY密钥 密文 + String secretKeyCiphertext = encryptor.encrypt(Strings.toStr(secretKeyPlain)); + // 对称加密后body 密文 + String bodyCiphertext = cipher.encrypt(secretKeyPlain, Strings.toBytes(responseJsonStr)); + // 应答JSON串 + JsonObject responseJson = new JsonObject(); + // 应答头JSON串 + JsonObject headerJson = new JsonObject(); + // 加密后的密钥放入到请求头 + headerJson.addProperty(SECRET_KEY, secretKeyCiphertext); + // 加密证书序列号放入到请求头 + headerJson.addProperty(BANK_CERTIFICATE_SERIAL, encryptor.getCertificateSerial()); + // 添加应答头 + responseJson.add("header", headerJson); + // 添加body + responseJson.addProperty("body", bodyCiphertext); + return responseJson.toString(); + } } diff --git a/scfs-api-service/src/main/java/com/czcb/scfs/api/service/handle/NotifyHandler.java b/scfs-api-service/src/main/java/com/czcb/scfs/api/service/handle/NotifyHandler.java new file mode 100644 index 0000000..dbf5d3f --- /dev/null +++ b/scfs-api-service/src/main/java/com/czcb/scfs/api/service/handle/NotifyHandler.java @@ -0,0 +1,43 @@ +package com.czcb.scfs.api.service.handle; + +import com.czcb.scfs.api.core.ApiClient; + +/** + * 客户端通知处理 + * + * @author H.T + * @since 2026-05-19 + */ +public class NotifyHandler { + + private final ApiClient apiClient; + + public NotifyHandler(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * 通知请求参数解密 + * + * @param requestJsonStr 请求参数JSON字符串 + * @param requestClass 请求参数Class + * @return 请求参数 + * @author H.T + * @since 2026/5/19 + */ + public T decryptRequest(String requestJsonStr, Class requestClass) { + return apiClient.decryptRequest(requestJsonStr, requestClass); + } + + /** + * 通知应答参数加密 + * + * @param responseObject 应答参数Object + * @return 应答完整JSON + * @author H.T + * @since 2026/5/19 + */ + public String encryptResponse(Object responseObject) { + return apiClient.encryptResponse(responseObject); + } +} diff --git a/scfs-api-spring-boot-starter/src/main/java/com/czcb/scfs/spring/boot/starter/ScfsAutoConfiguration.java b/scfs-api-spring-boot-starter/src/main/java/com/czcb/scfs/spring/boot/starter/ScfsAutoConfiguration.java index 402f97a..4c43d4a 100644 --- a/scfs-api-spring-boot-starter/src/main/java/com/czcb/scfs/spring/boot/starter/ScfsAutoConfiguration.java +++ b/scfs-api-spring-boot-starter/src/main/java/com/czcb/scfs/spring/boot/starter/ScfsAutoConfiguration.java @@ -4,6 +4,7 @@ import com.czcb.scfs.api.core.ApiClient; import com.czcb.scfs.api.core.Profile; import com.czcb.scfs.api.core.http.ApiClientBuilder; import com.czcb.scfs.api.service.echo.EchoService; +import com.czcb.scfs.api.service.handle.NotifyHandler; import com.czcb.scfs.api.service.v2.account.AccountService; import com.czcb.scfs.api.service.v2.ar.*; import com.czcb.scfs.api.service.v2.bills.BillService; @@ -348,4 +349,14 @@ public class ScfsAutoConfiguration { public SummaryOrderService summaryOrderService(ApiClient apiClient) { return new SummaryOrderService(apiClient); } + + /** + * 客户端通知处理 + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(NotifyHandler.class) + public NotifyHandler notifyHandler(ApiClient apiClient) { + return new NotifyHandler(apiClient); + } } diff --git a/scfs-api-test/src/test/java/com/czcb/scfs/api/test/service/NotifyHandlerTest.java b/scfs-api-test/src/test/java/com/czcb/scfs/api/test/service/NotifyHandlerTest.java new file mode 100644 index 0000000..6d62551 --- /dev/null +++ b/scfs-api-test/src/test/java/com/czcb/scfs/api/test/service/NotifyHandlerTest.java @@ -0,0 +1,44 @@ +package com.czcb.scfs.api.test.service; + +import com.czcb.scfs.api.service.handle.NotifyHandler; +import com.czcb.scfs.api.service.v2.pay.model.OfflineRechargeRefundRequest; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +import static com.czcb.scfs.api.core.Constants.SECRET_KEY; + +/** + * 客户端通知处理Test + * + * @author H.T + * @since 2026-05-19 + */ +@SpringBootTest +class NotifyHandlerTest { + + @Resource + private NotifyHandler notifyHandler; + + @Test + void decryptRequest() throws JSONException { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("body", "n/mpGUWGb9HpNvtyM1U7s8yTHP1oMBqAR4U+hTvw0gMtHFcXdvltJ/Dcw+U079FFiClExTP+d6skwnVVCftRXOfWlwOtQ9Ws1Soq7m+Gpr5CPuaMsRZeqzM4QL/RWhvGX36yE8BWZfICIBgmF8GdBd12iOjd6UO5qrt6ZZjqC0wr0gWpU9HK4JdcbJhaWOg45q1a3A76DVVTOsavwIfjF742WrVg3t42/2NsP7GNkMBJyuv1XHJQGgAUGP0Q6l5vPNU8nAFxyC+gKX/CyY+hmsqAr+jNOTVrcVj/AApQ3zf/pI1/zInVR6cq76ulv/5U2rBFrTJpZk6hpiFgr8/OfRYzrn0dQ/0QaSLFG8L0prIkMvV/"); + + JSONObject headerJson = new JSONObject(); + headerJson.put(SECRET_KEY, "MHoCIQC1ZJNfSItiTtyGrPY4xxHvTKiFMxSEJtlWlG+iKGGMnQIhAM4hAOEdCoKDZS/GH/l/kxtgmtC4we2Cxtt4SXQroxkOBCA2GwL0sfxtqc8I6MU6hbmgby1xEHAq61l+3OiOZOkKOAQQBPDf5gnp3cB701SGw9i7cg=="); + jsonObject.put("header", headerJson); + + + System.err.println(jsonObject); + System.out.println(notifyHandler.decryptRequest(jsonObject.toString(), OfflineRechargeRefundRequest.class)); + } + + @Test + void encryptResponse() { + System.out.println(notifyHandler.encryptResponse("通知成功")); + } +}