From 457e43fca832192c1f2c00ff297e932502a6dc8e Mon Sep 17 00:00:00 2001
From: wangxiu <1422795692@qq.com>
Date: Thu, 9 Jul 2026 12:28:44 +0800
Subject: [PATCH] init commit
---
.gitignore | 35 ++++
Dockerfile | 8 +
pom.xml | 81 ++++++++++
.../docking/LyxhApiDockingApplication.java | 14 ++
.../lyxh/api/docking/auth/AuthStrategy.java | 15 ++
.../api/docking/auth/AuthStrategyFactory.java | 30 ++++
.../auth/FortispayHmacAuthStrategy.java | 59 +++++++
.../api/docking/client/ApiCallResult.java | 24 +++
.../api/docking/client/GenericApiClient.java | 151 ++++++++++++++++++
.../lyxh/api/docking/config/CorsConfig.java | 27 ++++
.../api/docking/config/MybatisPlusConfig.java | 20 +++
.../lyxh/api/docking/config/QuartzConfig.java | 103 ++++++++++++
.../controller/ftapi/FortispayController.java | 38 +++++
.../thirdparty/ApiCacheDataController.java | 62 +++++++
.../thirdparty/ApiInvokeLogController.java | 66 ++++++++
.../ThirdPartyConfigController.java | 64 ++++++++
.../lyxh/api/docking/entity/ApiCacheData.java | 50 ++++++
.../lyxh/api/docking/entity/ApiInvokeLog.java | 68 ++++++++
.../api/docking/entity/ThirdPartyConfig.java | 70 ++++++++
.../com/lyxh/api/docking/enums/ApiType.java | 31 ++++
.../docking/exception/ApiCallException.java | 26 +++
.../api/docking/exception/ErrorResponse.java | 17 ++
.../exception/GlobalExceptionHandler.java | 46 ++++++
.../lyxh/api/docking/job/CacheCleanupJob.java | 33 ++++
.../lyxh/api/docking/job/LogCleanupJob.java | 42 +++++
.../docking/mapper/ApiCacheDataMapper.java | 18 +++
.../docking/mapper/ApiInvokeLogMapper.java | 9 ++
.../mapper/ThirdPartyConfigMapper.java | 9 ++
.../docking/model/ftapi/FortispayRequest.java | 20 +++
.../model/ftapi/PaymentProfileRequest.java | 23 +++
.../model/ftapi/PaymentProfileResponse.java | 14 ++
.../docking/service/ApiCacheDataService.java | 20 +++
.../docking/service/ApiInvokeLogService.java | 7 +
.../docking/service/GenericApiService.java | 150 +++++++++++++++++
.../service/PaymentProfileService.java | 135 ++++++++++++++++
.../api/docking/service/SignatureService.java | 30 ++++
.../service/ThirdPartyConfigService.java | 10 ++
.../service/impl/ApiCacheDataServiceImpl.java | 51 ++++++
.../service/impl/ApiInvokeLogServiceImpl.java | 13 ++
.../impl/ThirdPartyConfigServiceImpl.java | 24 +++
.../api/docking/util/AuthConfigCrypto.java | 56 +++++++
.../api/docking/util/AuthConfigEncryptor.java | 45 ++++++
.../api/docking/util/HeaderDesensitizer.java | 44 +++++
src/main/resources/application.yml | 34 ++++
.../resources/mapper/ApiCacheDataMapper.xml | 22 +++
.../resources/mapper/ApiInvokeLogMapper.xml | 5 +
.../mapper/ThirdPartyConfigMapper.xml | 5 +
src/main/resources/sql/init_data.sql | 36 +++++
src/main/resources/sql/schema.sql | 71 ++++++++
对接接口文档.md | 146 +++++++++++++++++
50 files changed, 2177 insertions(+)
create mode 100644 .gitignore
create mode 100644 Dockerfile
create mode 100644 pom.xml
create mode 100644 src/main/java/com/lyxh/api/docking/LyxhApiDockingApplication.java
create mode 100644 src/main/java/com/lyxh/api/docking/auth/AuthStrategy.java
create mode 100644 src/main/java/com/lyxh/api/docking/auth/AuthStrategyFactory.java
create mode 100644 src/main/java/com/lyxh/api/docking/auth/FortispayHmacAuthStrategy.java
create mode 100644 src/main/java/com/lyxh/api/docking/client/ApiCallResult.java
create mode 100644 src/main/java/com/lyxh/api/docking/client/GenericApiClient.java
create mode 100644 src/main/java/com/lyxh/api/docking/config/CorsConfig.java
create mode 100644 src/main/java/com/lyxh/api/docking/config/MybatisPlusConfig.java
create mode 100644 src/main/java/com/lyxh/api/docking/config/QuartzConfig.java
create mode 100644 src/main/java/com/lyxh/api/docking/controller/ftapi/FortispayController.java
create mode 100644 src/main/java/com/lyxh/api/docking/controller/thirdparty/ApiCacheDataController.java
create mode 100644 src/main/java/com/lyxh/api/docking/controller/thirdparty/ApiInvokeLogController.java
create mode 100644 src/main/java/com/lyxh/api/docking/controller/thirdparty/ThirdPartyConfigController.java
create mode 100644 src/main/java/com/lyxh/api/docking/entity/ApiCacheData.java
create mode 100644 src/main/java/com/lyxh/api/docking/entity/ApiInvokeLog.java
create mode 100644 src/main/java/com/lyxh/api/docking/entity/ThirdPartyConfig.java
create mode 100644 src/main/java/com/lyxh/api/docking/enums/ApiType.java
create mode 100644 src/main/java/com/lyxh/api/docking/exception/ApiCallException.java
create mode 100644 src/main/java/com/lyxh/api/docking/exception/ErrorResponse.java
create mode 100644 src/main/java/com/lyxh/api/docking/exception/GlobalExceptionHandler.java
create mode 100644 src/main/java/com/lyxh/api/docking/job/CacheCleanupJob.java
create mode 100644 src/main/java/com/lyxh/api/docking/job/LogCleanupJob.java
create mode 100644 src/main/java/com/lyxh/api/docking/mapper/ApiCacheDataMapper.java
create mode 100644 src/main/java/com/lyxh/api/docking/mapper/ApiInvokeLogMapper.java
create mode 100644 src/main/java/com/lyxh/api/docking/mapper/ThirdPartyConfigMapper.java
create mode 100644 src/main/java/com/lyxh/api/docking/model/ftapi/FortispayRequest.java
create mode 100644 src/main/java/com/lyxh/api/docking/model/ftapi/PaymentProfileRequest.java
create mode 100644 src/main/java/com/lyxh/api/docking/model/ftapi/PaymentProfileResponse.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/ApiCacheDataService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/ApiInvokeLogService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/GenericApiService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/PaymentProfileService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/SignatureService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/ThirdPartyConfigService.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/impl/ApiCacheDataServiceImpl.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/impl/ApiInvokeLogServiceImpl.java
create mode 100644 src/main/java/com/lyxh/api/docking/service/impl/ThirdPartyConfigServiceImpl.java
create mode 100644 src/main/java/com/lyxh/api/docking/util/AuthConfigCrypto.java
create mode 100644 src/main/java/com/lyxh/api/docking/util/AuthConfigEncryptor.java
create mode 100644 src/main/java/com/lyxh/api/docking/util/HeaderDesensitizer.java
create mode 100644 src/main/resources/application.yml
create mode 100644 src/main/resources/mapper/ApiCacheDataMapper.xml
create mode 100644 src/main/resources/mapper/ApiInvokeLogMapper.xml
create mode 100644 src/main/resources/mapper/ThirdPartyConfigMapper.xml
create mode 100644 src/main/resources/sql/init_data.sql
create mode 100644 src/main/resources/sql/schema.sql
create mode 100644 对接接口文档.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1ae0b7b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+sh
+.claude/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..46613a1
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+FROM openjdk:17.0.2-slim
+
+WORKDIR /app/api
+COPY target/lyxh-api-docking-1.0.0.jar /app/api/lyxh-api-docking.jar
+
+EXPOSE 8049
+
+ENTRYPOINT ["java", "-jar", "/app/api/lyxh-api-docking.jar"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..299529e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.7
+
+
+
+ com.lyxh
+ lyxh-api-docking
+ 1.0.0
+ lyxh-api-docking
+ Fortispay API docking service
+
+
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ 3.5.7
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+ commons-codec
+ commons-codec
+
+
+ org.springframework.boot
+ spring-boot-starter-quartz
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
diff --git a/src/main/java/com/lyxh/api/docking/LyxhApiDockingApplication.java b/src/main/java/com/lyxh/api/docking/LyxhApiDockingApplication.java
new file mode 100644
index 0000000..201a625
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/LyxhApiDockingApplication.java
@@ -0,0 +1,14 @@
+package com.lyxh.api.docking;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.lyxh.api.docking.mapper")
+public class LyxhApiDockingApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(LyxhApiDockingApplication.class, args);
+ }
+}
diff --git a/src/main/java/com/lyxh/api/docking/auth/AuthStrategy.java b/src/main/java/com/lyxh/api/docking/auth/AuthStrategy.java
new file mode 100644
index 0000000..2ebb23e
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/auth/AuthStrategy.java
@@ -0,0 +1,15 @@
+package com.lyxh.api.docking.auth;
+
+import org.springframework.http.HttpHeaders;
+
+/**
+ * 认证策略接口 - 根据 auth_config JSON 构建请求头
+ */
+public interface AuthStrategy {
+
+ /** 返回此策略对应的 auth_type 标识 */
+ String getAuthType();
+
+ /** 根据 auth_config、请求唯一标识、语言标识构建认证相关的 HTTP 请求头 */
+ HttpHeaders prepareHeaders(String authConfigJson, String requestId, String language);
+}
diff --git a/src/main/java/com/lyxh/api/docking/auth/AuthStrategyFactory.java b/src/main/java/com/lyxh/api/docking/auth/AuthStrategyFactory.java
new file mode 100644
index 0000000..19a68a9
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/auth/AuthStrategyFactory.java
@@ -0,0 +1,30 @@
+package com.lyxh.api.docking.auth;
+
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 认证策略工厂 - 根据 auth_type 返回对应的 AuthStrategy 实现
+ */
+@Component
+public class AuthStrategyFactory {
+
+ private final Map strategyMap;
+
+ public AuthStrategyFactory(List strategies) {
+ this.strategyMap = strategies.stream()
+ .collect(Collectors.toMap(AuthStrategy::getAuthType, Function.identity()));
+ }
+
+ public AuthStrategy getStrategy(String authType) {
+ AuthStrategy strategy = strategyMap.get(authType);
+ if (strategy == null) {
+ throw new IllegalArgumentException("Unsupported auth_type: " + authType);
+ }
+ return strategy;
+ }
+}
diff --git a/src/main/java/com/lyxh/api/docking/auth/FortispayHmacAuthStrategy.java b/src/main/java/com/lyxh/api/docking/auth/FortispayHmacAuthStrategy.java
new file mode 100644
index 0000000..36926db
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/auth/FortispayHmacAuthStrategy.java
@@ -0,0 +1,59 @@
+package com.lyxh.api.docking.auth;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.lyxh.api.docking.exception.ApiCallException;
+import com.lyxh.api.docking.service.SignatureService;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+
+/**
+ * HMAC-SHA256 签名认证(富通等第三方接口)
+ *
+ * auth_config 格式: {"secret_id":"xxx","secret_key":"yyy"}
+ * 在请求头中添加: requestId, secretId, timestamp, signature
+ */
+@Component
+public class FortispayHmacAuthStrategy implements AuthStrategy {
+
+ private final SignatureService signatureService;
+ private final ObjectMapper objectMapper;
+
+ public FortispayHmacAuthStrategy(SignatureService signatureService, ObjectMapper objectMapper) {
+ this.signatureService = signatureService;
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String getAuthType() {
+ return "FORTISPAY_HMAC";
+ }
+
+ @Override
+ public HttpHeaders prepareHeaders(String authConfigJson, String requestId, String language) {
+ long timestamp = signatureService.currentTimestamp();
+
+ JsonNode node;
+ try {
+ node = objectMapper.readTree(authConfigJson);
+ } catch (Exception e) {
+ throw new ApiCallException("AUTH_CONFIG_ERROR",
+ "Failed to parse auth_config: " + e.getMessage(), "");
+ }
+ String secretId = node.get("secret_id").asText();
+ String secretKey = node.get("secret_key").asText();
+ String signature = signatureService.generateSignature(requestId, timestamp, secretId, secretKey);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("requestId", requestId);
+ headers.set("secretId", secretId);
+ headers.set("timestamp", String.valueOf(timestamp));
+ headers.set("signature", signature);
+ if (language != null && !language.isBlank()) {
+ headers.set("language", language);
+ }
+ return headers;
+ }
+}
diff --git a/src/main/java/com/lyxh/api/docking/client/ApiCallResult.java b/src/main/java/com/lyxh/api/docking/client/ApiCallResult.java
new file mode 100644
index 0000000..c121cde
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/client/ApiCallResult.java
@@ -0,0 +1,24 @@
+package com.lyxh.api.docking.client;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class ApiCallResult {
+
+ /** 请求唯一标识 */
+ private final String requestId;
+ /** 实际请求URL */
+ private final String url;
+ /** 请求头JSON */
+ private final String reqHeadersJson;
+ /** 请求体JSON */
+ private final String reqBodyJson;
+ /** 响应体原始JSON */
+ private final String respBodyJson;
+ /** HTTP状态码 */
+ private final int httpStatusCode;
+ /** 请求耗时(毫秒) */
+ private final long costMillis;
+}
diff --git a/src/main/java/com/lyxh/api/docking/client/GenericApiClient.java b/src/main/java/com/lyxh/api/docking/client/GenericApiClient.java
new file mode 100644
index 0000000..933032b
--- /dev/null
+++ b/src/main/java/com/lyxh/api/docking/client/GenericApiClient.java
@@ -0,0 +1,151 @@
+package com.lyxh.api.docking.client;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.lyxh.api.docking.auth.AuthStrategy;
+import com.lyxh.api.docking.auth.AuthStrategyFactory;
+import com.lyxh.api.docking.entity.ThirdPartyConfig;
+import com.lyxh.api.docking.exception.ApiCallException;
+import com.lyxh.api.docking.util.AuthConfigCrypto;
+import com.lyxh.api.docking.util.HeaderDesensitizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.*;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.time.Duration;
+
+@Component
+public class GenericApiClient {
+
+ private static final Logger log = LoggerFactory.getLogger(GenericApiClient.class);
+
+ private final AuthStrategyFactory authFactory;
+ private final AuthConfigCrypto crypto;
+ private final HeaderDesensitizer desensitizer;
+ private final ObjectMapper objectMapper;
+
+ public GenericApiClient(AuthStrategyFactory authFactory, AuthConfigCrypto crypto,
+ HeaderDesensitizer desensitizer, ObjectMapper objectMapper) {
+ this.authFactory = authFactory;
+ this.crypto = crypto;
+ this.desensitizer = desensitizer;
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * 根据 DB 配置调用第三方 API,支持超时、代理、重试,auth_config 解密,请求头脱敏
+ */
+ public ApiCallResult call(ThirdPartyConfig config, Object requestBody,
+ String requestId, String language, String authType) {
+ String url = config.getApiUrl();
+ String authConfigDecrypted = crypto.decrypt(config.getAuthConfig());
+ AuthStrategy authStrategy = authFactory.getStrategy(authType);
+ HttpHeaders headers = authStrategy.prepareHeaders(authConfigDecrypted, requestId, language);
+
+ String reqBodyJson = toJson(requestBody);
+ String reqHeadersJson = toJson(desensitizer.mask(headersToMap(headers)));
+
+ int timeoutSeconds = config.getTimeoutSeconds() != null ? config.getTimeoutSeconds() : 30;
+ int maxRetries = config.getRetryTimes() != null ? config.getRetryTimes() : 0;
+ RestTemplate rt = buildRestTemplate(config, timeoutSeconds);
+
+ HttpEntity