diff --git a/.gitignore b/.gitignore index e55eb64b..3d91d080 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ package-lock.json # visual studio code .history *.log +logs/** functions/mock .temp/** diff --git a/zt-dependencies/pom.xml b/zt-dependencies/pom.xml index ceb1881b..69fbe2fd 100644 --- a/zt-dependencies/pom.xml +++ b/zt-dependencies/pom.xml @@ -72,7 +72,7 @@ 1.18.1 1.18.36 1.6.3 - 5.8.35 + 5.8.43 6.0.0-M19 4.0.3 2.4.1 diff --git a/zt-module-databus/pom.xml b/zt-module-databus/pom.xml index 904627a5..c06c914b 100644 --- a/zt-module-databus/pom.xml +++ b/zt-module-databus/pom.xml @@ -11,6 +11,7 @@ zt-module-databus-api zt-module-databus-server zt-module-databus-server-app + zt-module-databus-client 4.0.0 diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java new file mode 100644 index 00000000..fb3536bb --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java @@ -0,0 +1,152 @@ +package com.zt.plat.module.databus.api.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zt.plat.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 新增Databus API 访问日志请求体。 + */ +@Data +@EqualsAndHashCode +public class ApiAccessLogCreateReq { + + /** + * 主键 + */ + @Schema(description = "主键") + private Long id; + + /** + * HTTP 方法 + */ + @Schema(description = "HTTP 方法") + @NotNull(message = "HTTP 方法不能为空") + private String requestMethod; + + /** + * 请求路径 + */ + @Schema(description = "请求路径") + @NotNull(message = "请求路径不能为空") + private String requestPath; + + /** + * 调用使用的应用标识 + */ + @Schema(description = "调用使用的应用标识") + @NotNull(message = "调用使用的应用标识不能为空") + private String credentialAppId; + + /** + * 多租户编号 + */ + @Schema(description = "多租户编号") + @NotNull(message = "多租户编号不能为空") + private Long tenantId; + + /** + * 查询参数(JSON 字符串) + */ + @Schema(description = "查询参数(JSON 字符串)") + private String requestQuery; + + /** + * 请求头信息(JSON 字符串) + */ + @Schema(description = "请求头信息(JSON 字符串)") + private String requestHeaders; + + /** + * 请求体(JSON 字符串) + */ + @Schema(description = "请求体(JSON 字符串)") + private String requestBody; + + /** + * 响应 HTTP 状态码 + */ + @Schema(description = "响应 HTTP 状态码") + private Integer responseStatus; + + /** + * 响应提示信息 + */ + @Schema(description = "响应提示信息") + private String responseMessage; + + /** + * 响应体(JSON 字符串) + */ + @Schema(description = "响应体(JSON 字符串)") + private String responseBody; + + /** + * 访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知 + */ + @Schema(description = "访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知") + @NotNull(message = "访问状态不能为空") + private Integer status; + + /** + * 业务错误码 + */ + @Schema(description = "业务错误码") + private String errorCode; + + /** + * 错误信息 + */ + @Schema(description = "错误信息") + private String errorMessage; + + /** + * 异常堆栈 + */ + @Schema(description = "异常堆栈") + private String exceptionStack; + + /** + * 客户端 IP + */ + @Schema(description = "客户端 IP") + private String clientIp; + + /** + * User-Agent + */ + @Schema(description = "User-Agent") + private String userAgent; + + /** + * 请求耗时(毫秒) + */ + @Schema(description = "请求耗时(毫秒)") + private Long duration; + + /** + * 请求时间 + */ + @Schema(description = "请求时间") + @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) + private LocalDateTime requestTime; + + /** + * 响应时间 + */ + @Schema(description = "响应时间") + @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) + private LocalDateTime responseTime; + + /** + * 额外调试信息(JSON 字符串) + */ + @Schema(description = "额外调试信息(JSON 字符串)") + private String extra; + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java new file mode 100644 index 00000000..a2f80c6e --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.api.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +/** + * Databus API访问日志接口 + * 2026/1/20 16:26 + */ +@FeignClient(name = "${databus.provider.log.service:databus-server}") +@Tag(name = "RPC 服务 - Databus API访问日志接口") +public interface DatabusAccessLogProviderApi { + + String PREFIX = "/databus/api/portal/access-log"; + + @PostMapping(PREFIX + "/add") + @Operation(summary = "新增访问日志") + CommonResult add(@RequestHeader Map headers, @RequestBody ApiAccessLogCreateReq req); + +} diff --git a/zt-module-databus/zt-module-databus-client/pom.xml b/zt-module-databus/zt-module-databus-client/pom.xml new file mode 100644 index 00000000..a8abc82f --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + zt-module-databus + com.zt.plat + ${revision} + + zt-module-databus-server-client + jar + ${project.artifactId} + + Databus client, 提供调用第三方服务的能力并记录调用日志。 + + + + + + + com.zt.plat + zt-module-databus-api + ${revision} + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + org.springframework.boot + spring-boot-starter-aop + provided + + + + org.apache.commons + commons-lang3 + + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + diff --git a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java new file mode 100644 index 00000000..ad921ceb --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java @@ -0,0 +1,242 @@ +package com.zt.plat.module.databus.client; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 数据总线提供的 http 客户端, 通过此客户端发起接口调用,会自动记录请求日志到数据总线 + * 2026/1/20 09:44 + */ +@Component +@Slf4j +public class DatabusClient { + + /** + * 多租户编号 + */ + @Value("${zt.plat.databus.client.tenantId:1}") + private Long tenantId; + + @Resource + private DatabusAccessLogProviderApi databusAccessLogProviderApi; + + private static final int MAX_TEXT_LENGTH = 4000; + + /** + * 发送 get 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求参数 + * @param headers 请求头 + * @return 响应结果 + */ + public String get(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.GET, appId, authToken); + } + + /** + * 发送 post 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String post(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.POST, appId, authToken); + } + + /** + * 发送 put 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String put(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.PUT, appId, authToken); + } + + /** + * 发送 delete 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String delete(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.DELETE, appId, authToken); + } + + + /** + * 发送请求到门户(token模式,不证书加密) + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @param method 请求方式,默认为 GET + * @return 响应结果 + */ + public String doRequest(String urlString, Map data, Map headers, Method method, String appId, String authToken) { + if (method == null) { + method = Method.GET; + } + Assert.hasText(urlString, "接口地址不能为空"); + HttpRequest request; + ApiAccessLogCreateReq logReq = new ApiAccessLogCreateReq(); + if (Method.GET.equals(method) || Method.DELETE.equals(method)) { + logReq.setRequestQuery(JSONUtil.toJsonStr(data)); + } else { + logReq.setRequestBody(JSONUtil.toJsonStr(data)); + } + request = HttpUtil.createRequest(method, urlString).form(data); + if (headers != null && !headers.isEmpty()) { + for (Map.Entry entry : headers.entrySet()) { + request.header(entry.getKey(), entry.getValue(), true); + } + } + + logReq.setTenantId(tenantId); + + logReq.setRequestMethod(method.name()); + logReq.setRequestPath(urlString); + logReq.setCredentialAppId(appId); + logReq.setRequestHeaders(JSONUtil.toJsonStr(headers)); + logReq.setUserAgent(request.header("User-Agent")); + String result; + logReq.setRequestTime(LocalDateTime.now()); + long requestTime = System.currentTimeMillis(); + try (HttpResponse response = request.execute()) { + logReq.setDuration(System.currentTimeMillis() - requestTime); + logReq.setResponseTime(LocalDateTime.now()); + result = response.body(); + logReq.setResponseStatus(response.getStatus()); + logReq.setResponseBody(result); + logReq.setStatus(resolveStatus(response.getStatus())); + Map errorCodeAndMsg = extractErrorCodeAndMsg(result, response.getStatus()); + logReq.setErrorCode(errorCodeAndMsg.get("errorCode")); + logReq.setErrorMessage(errorCodeAndMsg.get("errorMessage")); + addAccessLog(logReq, appId, authToken); + } catch (Exception e) { + // 错误的日志服务端记录了,这里就不再记录了 +// logReq.setStatus(1); +// logReq.setExceptionStack(buildStackTrace( e)); +// addAccessLog(logReq, appId, authToken); + throw new RuntimeException(e); + } + return result; + } + + private void addAccessLog(ApiAccessLogCreateReq logReq, String appId, String authToken) { + String nonce = randomNonce(); + Map headers = new HashMap<>(); + headers.put("tenant-id", String.valueOf(tenantId)); + headers.put("ZT-App-Id", appId); + headers.put("ZT-Timestamp", Long.toString(System.currentTimeMillis())); + headers.put("ZT-Nonce", nonce); + headers.put("ZT-Auth-Token", authToken); + CommonResult response = databusAccessLogProviderApi.add(headers, logReq); + if (response.getCode() != 0) { + throw new RuntimeException("添加访问日志失败: " + response); + } + } + + private static String randomNonce() { + return UUID.randomUUID().toString().replace("-", ""); + } + + private String buildStackTrace(Throwable throwable) { + try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { + throwable.printStackTrace(pw); + return truncate(sw.toString()); + } catch (Exception ex) { + return throwable.getMessage(); + } + } + + private Integer resolveStatus(Integer httpStatus) { + if (httpStatus == null) { + return 3; + } + if (httpStatus >= 200 && httpStatus < 400) { + return 0; + } + if (httpStatus >= 400 && httpStatus < 500) { + return 1; + } + if (httpStatus >= 500) { + return 2; + } + return 3; + } + + private Map extractErrorCodeAndMsg(String responseBody, Integer responseStatus) { + Map result = new HashMap<>(); + if (!isErrorStatus(responseStatus)) { + return result; + } + if (JSONUtil.isTypeJSONObject(responseBody)) { + JSONObject map = JSONUtil.parseObj(responseBody); + Object errorCode = firstNonNull(map.get("errorCode"), map.get("code")); + errorCode = errorCode == null ? null : truncate(String.valueOf(errorCode)); + if (errorCode != null) { + result.put("errorCode", errorCode.toString()); + } + Object message = firstNonNull(map.get("errorMessage"), map.get("message")); + if (message != null) { + message = truncate(String.valueOf(message)); + result.put("errorMessage", message.toString()); + } + + } + return result; + } + + + private boolean isErrorStatus(Integer responseStatus) { + return responseStatus != null && responseStatus >= 400; + } + + @SafeVarargs + private T firstNonNull(T... candidates) { + if (candidates == null) { + return null; + } + for (T candidate : candidates) { + if (candidate != null) { + return candidate; + } + } + return null; + } + + private String truncate(String text) { + if (!StringUtils.hasText(text)) { + return text; + } + if (text.length() <= MAX_TEXT_LENGTH) { + return text; + } + return text.substring(0, MAX_TEXT_LENGTH); + } + +} diff --git a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java new file mode 100644 index 00000000..08c69d96 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java @@ -0,0 +1,16 @@ +package com.zt.plat.module.databus.client; + +/** + * + * 2026/1/21 10:48 + */ + +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {DatabusAccessLogProviderApi.class}) +public class RpcConfiguration { + +} diff --git a/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories b/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..5fe820ee --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.zt.plat.module.databus.client.DatabusClient,\ +com.zt.plat.module.databus.client.RpcConfiguration \ No newline at end of file diff --git a/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java new file mode 100644 index 00000000..b26c74d8 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.databus; + +import com.zt.plat.module.databus.client.DatabusClient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * + * 2026/1/20 14:29 + */ +@SpringBootTest(classes = TestApplication.class) +public class DatabusClientTest { + + @Autowired + private DatabusClient databusClient; + + @Test + void test() { + String result = databusClient.get("https://www.baidu.com/", null, null, "jwyw2", "a5d7cf609c0b47038ea405c660726ee9"); + System.out.println(result); + } + +} diff --git a/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java new file mode 100644 index 00000000..fa8ca7d9 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.databus; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * + * 2026/1/20 14:26 + */ +@SpringBootTest +@SpringBootApplication(exclude = { + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + HibernateJpaAutoConfiguration.class, + JdbcTemplateAutoConfiguration.class, +}) +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml b/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml new file mode 100644 index 00000000..a47ddc71 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml @@ -0,0 +1,11 @@ +spring: + cloud: + nacos: + server-addr: 172.16.46.63:30848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: P@ssword25 # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: klw # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 \ No newline at end of file diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java new file mode 100644 index 00000000..a700c240 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.databus.api; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.monitor.TracerUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.framework.common.util.servlet.ServletUtils; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO; +import com.zt.plat.module.databus.service.gateway.ApiAccessLogService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.Map; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * + * 2026/1/21 10:05 + */ +@RestController +public class DatabusAccessLogProviderApiImpl implements DatabusAccessLogProviderApi { + + @Resource + private ApiAccessLogService apiAccessLogService; + + @Override + @PermitAll + public CommonResult add(Map headers, ApiAccessLogCreateReq req) { + ApiAccessLogDO logDO = new ApiAccessLogDO(); + BeanUtils.copyProperties(req, logDO); + logDO.setTraceId(TracerUtils.getTraceId()); + logDO.setClientIp(ServletUtils.getClientIP()); + logDO.setCreateTime(LocalDateTime.now()); + logDO.setUpdateTime(LocalDateTime.now()); + logDO.setCreator("1"); + logDO.setUpdater("1"); + + apiAccessLogService.create(logDO); + return success(Boolean.TRUE); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java index 741db3f2..d56e56a6 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java @@ -159,6 +159,7 @@ public class ApiGatewayAccessLogger { update.setStatus(resolveStatus(status)); update.setResponseTime(LocalDateTime.now()); update.setDuration(calculateDuration(request)); + update.setUpdater("1"); apiAccessLogService.update(update); } catch (Exception ex) { log.warn("更新入口 API 访问日志失败, logId={}", logId, ex); diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java index 14a5ed11..6804d03b 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java @@ -89,6 +89,7 @@ public class ApiGatewayExecutionService { } else { responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context); } + responseContext.setResponseStatus(resolveStatus(responseContext)); } catch (ServiceException ex) { errorProcessor.applyServiceException(context, ex); accessLogger.onException(context, ex); diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java index 00025b83..d0839a7d 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java @@ -77,6 +77,9 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); return; } + + // 添加日志接口需要特殊处理 + boolean isNormalProcess = !pathMatcher.match("/databus/api/portal/access-log/**", pathWithinApplication); Long accessLogId = null; ApiGatewayProperties.Security security = properties.getSecurity(); ApiClientCredentialDO credential = null; @@ -95,8 +98,10 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); return; } - // IP 校验通过后再补录入口日志,避免无租户信息写库 - accessLogId = accessLogger.logEntrance(request); + // IP 校验通过后再补录入口日志,避免无租户信息写库, 非日志添加接口才记录日志 + if (isNormalProcess) { + accessLogId = accessLogger.logEntrance(request); + } if (!security.isEnabled()) { byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream()); CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody); @@ -128,13 +133,17 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { if (nonce.length() < 8) { throw new SecurityValidationException(HttpStatus.BAD_REQUEST, "随机数长度不足"); } - String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名"); - // 尝试按凭证配置解密请求体,并构建签名载荷进行校验 - byte[] decryptedBody = decryptRequestBody(requestBody, credential, security); - verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader); + if (isNormalProcess) { + // 非日志添加接口才处理 + String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名"); + // 尝试按凭证配置解密请求体,并构建签名载荷进行校验 + byte[] decryptedBody = decryptRequestBody(requestBody, credential, security); + verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader); + requestBody = decryptedBody; + } ensureNonce(tenantId, appId, nonce, security); - requestBody = decryptedBody; + } // 使用可重复读取的请求包装,供后续过滤器继续消费 @@ -154,7 +163,9 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { try { filterChain.doFilter(securedRequest, responseWrapper); dispatchedToGateway = true; - encryptResponse(responseWrapper, credential, security); + if (isNormalProcess) { + encryptResponse(responseWrapper, credential, security); + } } finally { responseWrapper.copyBodyToResponse(); } @@ -163,7 +174,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogId = accessLogger.logEntrance(request); } log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage()); - writeErrorResponse(response, security, credential, ex.status(), ex.getMessage()); + writeErrorResponse(response, security, credential, ex.status(), ex.getMessage(), isNormalProcess); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage()); } @@ -172,7 +183,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogId = accessLogger.logEntrance(request); } log.error("[API-PORTAL] 处理安全校验时出现异常", ex); - writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败"); + writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败", isNormalProcess); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败"); } @@ -479,6 +490,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { String token = tokenOptional.get(); securedRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); securedRequest.setHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token); + } private static final class SecurityValidationException extends RuntimeException { @@ -499,7 +511,8 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { ApiGatewayProperties.Security security, ApiClientCredentialDO credential, HttpStatus status, - String message) { + String message, + boolean isNormalProcess) { if (response.isCommitted()) { log.warn("[API-PORTAL] 响应已提交,无法写入安全校验错误: {}", message); return; @@ -514,7 +527,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { .response(null) .traceId(traceId) .build(); - if (shouldEncryptErrorResponse(security, credential)) { + if (shouldEncryptErrorResponse(security, credential) && isNormalProcess) { String encryptionKey = credential.getEncryptionKey(); String encryptionType = resolveEncryptionType(credential, security); try {