diff --git a/zt-framework/zt-spring-boot-starter-biz-business/pom.xml b/zt-framework/zt-spring-boot-starter-biz-business/pom.xml
index 86e2487d..a410267a 100644
--- a/zt-framework/zt-spring-boot-starter-biz-business/pom.xml
+++ b/zt-framework/zt-spring-boot-starter-biz-business/pom.xml
@@ -36,6 +36,11 @@
zt-spring-boot-starter-biz-tenant
${revision}
+
+ com.zt.plat
+ zt-spring-boot-starter-mybatis
+ ${revision}
+
com.zt.plat
diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java
index 614cf30c..174049ad 100644
--- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java
+++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java
@@ -2,27 +2,54 @@ package com.zt.plat.framework.business.framework;
import com.zt.plat.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
import com.zt.plat.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
+import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
/**
- * @author chenbowen
+ * 自动为继承 BusinessBaseDO 的实体注册公司/部门数据权限字段。
*/
@Configuration(proxyBeanMethods = false)
public class BusinessDataPermissionConfiguration {
+
@Bean
- public CompanyDataPermissionRuleCustomizer sysCompanyDataPermissionRuleCustomizer() {
- return rule -> {
- // companyId
- rule.addCompanyColumn("demo_contract", "company_id");
- };
+ public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext) {
+ Set basePackages = new LinkedHashSet<>();
+ if (AutoConfigurationPackages.has(beanFactory)) {
+ basePackages.addAll(AutoConfigurationPackages.get(beanFactory));
+ }
+ if (basePackages.isEmpty()) {
+ basePackages.add("com.zt");
+ }
+ ClassLoader classLoader = applicationContext != null
+ ? applicationContext.getClassLoader()
+ : Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = BusinessDataPermissionConfiguration.class.getClassLoader();
+ }
+ return new BusinessDataPermissionEntityScanner(basePackages, classLoader);
}
@Bean
- public DeptDataPermissionRuleCustomizer businessDeptDataPermissionRuleCustomizer() {
- return rule -> {
- // dept
- rule.addDeptColumn("demo_contract", "dept_id");
- };
+ public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) {
+ return rule -> scanner.getEntityMetadata().forEach(metadata -> {
+ if (metadata.hasCompanyColumn()) {
+ rule.addCompanyColumn(metadata.getTableName(), metadata.getCompanyColumn());
+ }
+ });
+ }
+
+ @Bean
+ public DeptDataPermissionRuleCustomizer autoDeptDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) {
+ return rule -> scanner.getEntityMetadata().forEach(metadata -> {
+ if (metadata.hasDeptColumn()) {
+ rule.addDeptColumn(metadata.getTableName(), metadata.getDeptColumn());
+ }
+ });
}
}
diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java
new file mode 100644
index 00000000..93c306f5
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java
@@ -0,0 +1,159 @@
+package com.zt.plat.framework.business.framework;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.zt.plat.framework.mybatis.core.annotation.CompanyColumn;
+import com.zt.plat.framework.mybatis.core.annotation.DeptColumn;
+import com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+/**
+ * 扫描继承 {@link BusinessBaseDO} 的实体,自动提取公司/部门字段用于数据权限注册。
+ *
+ * @author chenbow
+ */
+@Slf4j
+public class BusinessDataPermissionEntityScanner {
+
+ private final Set basePackages;
+ private final ClassLoader classLoader;
+
+ private volatile List cachedEntities;
+
+ public BusinessDataPermissionEntityScanner(Collection basePackages, ClassLoader classLoader) {
+ Set packages = new LinkedHashSet<>();
+ if (!CollectionUtils.isEmpty(basePackages)) {
+ packages.addAll(basePackages);
+ }
+ if (packages.isEmpty()) {
+ packages.add("com.zt");
+ }
+ this.basePackages = Collections.unmodifiableSet(packages);
+ this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
+ }
+
+ public List getEntityMetadata() {
+ List result = cachedEntities;
+ if (result == null) {
+ synchronized (this) {
+ result = cachedEntities;
+ if (result == null) {
+ result = Collections.unmodifiableList(scanEntities());
+ cachedEntities = result;
+ }
+ }
+ }
+ return result;
+ }
+
+ private List scanEntities() {
+ Map metadataMap = new LinkedHashMap<>();
+ ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+ scanner.addIncludeFilter(new AssignableTypeFilter(BusinessBaseDO.class));
+
+ for (String basePackage : basePackages) {
+ for (BeanDefinition beanDefinition : scanner.findCandidateComponents(basePackage)) {
+ String className = beanDefinition.getBeanClassName();
+ if (!StringUtils.hasText(className)) {
+ continue;
+ }
+ try {
+ Class> clazz = ClassUtils.forName(className, classLoader);
+ if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) {
+ continue;
+ }
+ if (Modifier.isAbstract(clazz.getModifiers())) {
+ continue;
+ }
+ @SuppressWarnings("unchecked")
+ Class extends BusinessBaseDO> entityClass = (Class extends BusinessBaseDO>) clazz;
+ EntityMetadata metadata = buildMetadata(entityClass);
+ if (metadata != null && StringUtils.hasText(metadata.getTableName())) {
+ metadataMap.putIfAbsent(metadata.getTableName(), metadata);
+ }
+ } catch (ClassNotFoundException ex) {
+ log.warn("[scanEntities][无法加载类 {}]", className, ex);
+ }
+ }
+ }
+ return new ArrayList<>(metadataMap.values());
+ }
+
+ private EntityMetadata buildMetadata(Class extends BusinessBaseDO> entityClass) {
+ String tableName = resolveTableName(entityClass);
+ if (!StringUtils.hasText(tableName)) {
+ log.debug("[buildMetadata][实体 {} 缺少表名配置,跳过自动注册]", entityClass.getName());
+ return null;
+ }
+ String companyColumn = resolveCompanyColumn(entityClass);
+ String deptColumn = resolveDeptColumn(entityClass);
+ if (!StringUtils.hasText(companyColumn) && !StringUtils.hasText(deptColumn)) {
+ log.debug("[buildMetadata][实体 {} 未配置公司/部门字段,跳过]", entityClass.getName());
+ return null;
+ }
+ return new EntityMetadata(tableName, companyColumn, deptColumn);
+ }
+
+ private String resolveTableName(Class> entityClass) {
+ TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
+ if (tableInfo != null && StringUtils.hasText(tableInfo.getTableName())) {
+ return tableInfo.getTableName();
+ }
+ TableName tableName = AnnotatedElementUtils.findMergedAnnotation(entityClass, TableName.class);
+ if (StringUtils.hasText(tableName.value())) {
+ return tableName.value();
+ }
+ // 退化为根据类名猜测(驼峰转下划线)
+ String fallback = com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline(entityClass.getSimpleName());
+ return StringUtils.hasText(fallback) ? fallback : null;
+ }
+
+ private String resolveCompanyColumn(Class> entityClass) {
+ CompanyColumn annotation = AnnotatedElementUtils.findMergedAnnotation(entityClass, CompanyColumn.class);
+ return annotation.value();
+ }
+
+ private String resolveDeptColumn(Class> entityClass) {
+ DeptColumn annotation = AnnotatedElementUtils.findMergedAnnotation(entityClass, DeptColumn.class);
+ return annotation.value();
+ }
+
+ @Getter
+ @RequiredArgsConstructor
+ public static class EntityMetadata {
+ private final String tableName;
+ private final String companyColumn;
+ private final String deptColumn;
+
+ public boolean hasCompanyColumn() {
+ return StringUtils.hasText(companyColumn);
+ }
+
+ public boolean hasDeptColumn() {
+ return StringUtils.hasText(deptColumn);
+ }
+
+ @Override
+ public String toString() {
+ return "EntityMetadata{" +
+ "table='" + tableName + '\'' +
+ ", company='" + companyColumn + '\'' +
+ ", dept='" + deptColumn + '\'' +
+ '}';
+ }
+
+ }
+}
diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/CompanyColumn.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/CompanyColumn.java
new file mode 100644
index 00000000..386036db
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/CompanyColumn.java
@@ -0,0 +1,23 @@
+package com.zt.plat.framework.mybatis.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标记业务实体对应表中的公司字段名称,默认 company_id。
+ *
+ * @author chenbow
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CompanyColumn {
+
+ /**
+ * 表中公司字段名称
+ */
+ String value() default "company_id";
+}
diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/DeptColumn.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/DeptColumn.java
new file mode 100644
index 00000000..d8300aab
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/DeptColumn.java
@@ -0,0 +1,23 @@
+package com.zt.plat.framework.mybatis.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标记业务实体对应表中的部门字段名称,默认 dept_id。
+ *
+ * @author chenbow
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DeptColumn {
+
+ /**
+ * 表中部门字段名称
+ */
+ String value() default "dept_id";
+}
diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/dataobject/BusinessBaseDO.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/dataobject/BusinessBaseDO.java
index 56252a0e..c983a463 100644
--- a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/dataobject/BusinessBaseDO.java
+++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/dataobject/BusinessBaseDO.java
@@ -2,6 +2,8 @@ package com.zt.plat.framework.mybatis.core.dataobject;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
+import com.zt.plat.framework.mybatis.core.annotation.CompanyColumn;
+import com.zt.plat.framework.mybatis.core.annotation.DeptColumn;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.ibatis.type.JdbcType;
@@ -13,6 +15,8 @@ import java.util.List;
*/
@Data
@EqualsAndHashCode(callSuper = true)
+@CompanyColumn
+@DeptColumn
public class BusinessBaseDO extends BaseDO {
/** 公司编号 */
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 8528c16d..bce32b79 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
@@ -3,9 +3,12 @@ package com.zt.plat.module.databus.framework.integration.gateway.core;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.framework.common.util.monitor.TracerUtils;
+import com.zt.plat.framework.common.util.servlet.ServletUtils;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
+import com.zt.plat.module.databus.framework.integration.gateway.security.CachedBodyHttpServletRequest;
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
+import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
@@ -20,8 +23,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
/**
* 将 API 调用上下文持久化为访问日志。
@@ -33,6 +35,8 @@ public class ApiGatewayAccessLogger {
public static final String ATTR_LOG_ID = "ApiAccessLogId";
public static final String ATTR_EXCEPTION_STACK = "ApiAccessLogExceptionStack";
+ public static final String HEADER_ACCESS_LOG_ID = "X-Databus-AccessLog-Id";
+ private static final String ATTR_REQUEST_START = "ApiAccessLogRequestStart";
private static final int MAX_TEXT_LENGTH = 4000;
@@ -44,24 +48,15 @@ public class ApiGatewayAccessLogger {
*/
public void onRequest(ApiInvocationContext context) {
try {
- String traceId = TracerUtils.getTraceId();
- ApiAccessLogDO logDO = new ApiAccessLogDO();
- logDO.setTraceId(traceId);
- logDO.setApiCode(context.getApiCode());
- logDO.setApiVersion(context.getApiVersion());
- logDO.setRequestMethod(context.getHttpMethod());
- logDO.setRequestPath(context.getRequestPath());
- logDO.setRequestQuery(toJson(context.getRequestQueryParams()));
- logDO.setRequestHeaders(toJson(context.getRequestHeaders()));
- logDO.setRequestBody(toJson(context.getRequestBody()));
- logDO.setClientIp(firstNonBlank(context.getClientIp(),
- GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), "X-Forwarded-For")));
- logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
- logDO.setStatus(3); // 默认未知
- logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
- logDO.setTenantId(parseTenantId(context.getTenantId()));
- Long logId = apiAccessLogService.create(logDO);
- context.getAttributes().put(ATTR_LOG_ID, logId);
+ ApiAccessLogDO logDO = buildRequestSnapshot(context);
+ Long existingLogId = getLogId(context);
+ if (existingLogId != null) {
+ logDO.setId(existingLogId);
+ apiAccessLogService.update(logDO);
+ } else {
+ Long logId = apiAccessLogService.create(logDO);
+ context.getAttributes().put(ATTR_LOG_ID, logId);
+ }
} catch (Exception ex) {
log.warn("记录 API 访问日志开始阶段失败, traceId={}", TracerUtils.getTraceId(), ex);
}
@@ -111,15 +106,97 @@ public class ApiGatewayAccessLogger {
}
}
+ /**
+ * 安全过滤阶段的第一时间记录请求元数据,保证被快速拒绝的请求也能查询。
+ */
+ public Long logEntrance(HttpServletRequest request) {
+ if (request == null) {
+ return null;
+ }
+ Object existing = request.getAttribute(ATTR_LOG_ID);
+ if (existing instanceof Long logId) {
+ return logId;
+ }
+ try {
+ ApiAccessLogDO logDO = new ApiAccessLogDO();
+ logDO.setTraceId(TracerUtils.getTraceId());
+ logDO.setRequestMethod(request.getMethod());
+ logDO.setRequestPath(request.getRequestURI());
+ logDO.setRequestQuery(truncate(request.getQueryString()));
+ logDO.setRequestHeaders(toJson(collectHeaders(request)));
+ logDO.setClientIp(ServletUtils.getClientIP(request));
+ logDO.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
+ logDO.setStatus(3);
+ logDO.setRequestTime(LocalDateTime.now());
+ Long logId = apiAccessLogService.create(logDO);
+ request.setAttribute(ATTR_LOG_ID, logId);
+ request.setAttribute(ATTR_REQUEST_START, Instant.now());
+ return logId;
+ } catch (Exception ex) {
+ log.warn("记录入口 API 访问日志失败", ex);
+ return null;
+ }
+ }
+
+ /**
+ * 编排前即结束的请求在此补写状态码、耗时等关键信息。
+ */
+ public void finalizeEarly(HttpServletRequest request, int status, String message) {
+ if (request == null) {
+ return;
+ }
+ Object existing = request.getAttribute(ATTR_LOG_ID);
+ if (!(existing instanceof Long logId)) {
+ return;
+ }
+ try {
+ ApiAccessLogDO update = new ApiAccessLogDO();
+ update.setId(logId);
+ update.setResponseStatus(status);
+ update.setResponseMessage(truncate(message));
+ update.setStatus(resolveStatus(status));
+ update.setResponseTime(LocalDateTime.now());
+ update.setDuration(calculateDuration(request));
+ apiAccessLogService.update(update);
+ } catch (Exception ex) {
+ log.warn("更新入口 API 访问日志失败, logId={}", logId, ex);
+ }
+ }
+
+ /**
+ * 将入口阶段生成的 logId 通过请求头继续传递,供后续流程关联合并。
+ */
+ public static void propagateLogIdHeader(CachedBodyHttpServletRequest requestWrapper, Long logId) {
+ if (requestWrapper == null || logId == null) {
+ return;
+ }
+ requestWrapper.setHeader(HEADER_ACCESS_LOG_ID, String.valueOf(logId));
+ }
+
private Long getLogId(ApiInvocationContext context) {
Object value = context.getAttributes().get(ATTR_LOG_ID);
- if (value instanceof Long) {
- return (Long) value;
- }
- if (value instanceof Number number) {
- return number.longValue();
- }
- return null;
+ return value instanceof Long ? (Long) value : null;
+ }
+
+ /**
+ * 根据编排上下文构建请求侧快照,用于访问日志首段信息。
+ */
+ private ApiAccessLogDO buildRequestSnapshot(ApiInvocationContext context) {
+ ApiAccessLogDO logDO = new ApiAccessLogDO();
+ logDO.setTraceId(TracerUtils.getTraceId());
+ logDO.setApiCode(context.getApiCode());
+ logDO.setApiVersion(context.getApiVersion());
+ logDO.setRequestMethod(context.getHttpMethod());
+ logDO.setRequestPath(context.getRequestPath());
+ logDO.setRequestQuery(toJson(context.getRequestQueryParams()));
+ logDO.setRequestHeaders(toJson(context.getRequestHeaders()));
+ logDO.setRequestBody(toJson(context.getRequestBody()));
+ logDO.setClientIp(context.getClientIp());
+ logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
+ logDO.setStatus(3);
+ logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
+ logDO.setTenantId(parseTenantId(context.getTenantId()));
+ return logDO;
}
private Long calculateDuration(ApiInvocationContext context) {
@@ -130,6 +207,14 @@ public class ApiGatewayAccessLogger {
return Duration.between(start, Instant.now()).toMillis();
}
+ private Long calculateDuration(HttpServletRequest request) {
+ Object startAttr = request.getAttribute(ATTR_REQUEST_START);
+ if (startAttr instanceof Instant start) {
+ return Duration.between(start, Instant.now()).toMillis();
+ }
+ return null;
+ }
+
private Integer resolveStatus(Integer httpStatus) {
if (httpStatus == null) {
return 3;
@@ -214,6 +299,19 @@ public class ApiGatewayAccessLogger {
return extra;
}
+ @SafeVarargs
+ private T firstNonNull(T... candidates) {
+ if (candidates == null) {
+ return null;
+ }
+ for (T candidate : candidates) {
+ if (candidate != null) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
private String toJson(Object value) {
if (value == null) {
return null;
@@ -265,27 +363,20 @@ public class ApiGatewayAccessLogger {
}
}
- private String firstNonBlank(String... values) {
- if (values == null) {
- return null;
+ private Map collectHeaders(HttpServletRequest request) {
+ if (request == null) {
+ return Collections.emptyMap();
}
- for (String value : values) {
- if (StringUtils.hasText(value)) {
- return value;
+ Map headers = new LinkedHashMap<>();
+ Enumeration names = request.getHeaderNames();
+ while (names != null && names.hasMoreElements()) {
+ String name = names.nextElement();
+ Enumeration values = request.getHeaders(name);
+ if (values == null || !values.hasMoreElements()) {
+ continue;
}
+ headers.put(name, values.nextElement());
}
- return null;
+ return headers;
}
-
- private Object firstNonNull(Object... values) {
- if (values == null) {
- return null;
- }
- for (Object value : values) {
- if (value != null) {
- return value;
- }
- }
- return null;
- }
-}
+}
\ No newline at end of file
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
index fadfe510..5d813d52 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
@@ -3,6 +3,7 @@ package com.zt.plat.module.databus.framework.integration.gateway.core;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
+import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayAccessLogger;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
@@ -90,6 +91,7 @@ public class ApiGatewayRequestMapper {
});
context.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
context.setClientIp(resolveClientIp(headers, context.getRequestHeaders()));
+ captureAccessLogId(context);
populateQueryParams(headers, context, originalRequestUri);
if (properties.isEnableTenantHeader()) {
Object tenantHeaderValue = context.getRequestHeaders().get(properties.getTenantHeader());
@@ -114,6 +116,21 @@ public class ApiGatewayRequestMapper {
return context;
}
+ private void captureAccessLogId(ApiInvocationContext context) {
+ String headerValue = GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), ApiGatewayAccessLogger.HEADER_ACCESS_LOG_ID);
+ if (!StringUtils.hasText(headerValue)) {
+ return;
+ }
+ try {
+ Long logId = Long.valueOf(headerValue);
+ context.getAttributes().put(ApiGatewayAccessLogger.ATTR_LOG_ID, logId);
+ } catch (NumberFormatException ex) {
+ // 忽略格式问题,仅在属性中保留原文以便排查
+ context.getAttributes().put(ApiGatewayAccessLogger.ATTR_LOG_ID, headerValue);
+ }
+ context.getRequestHeaders().remove(ApiGatewayAccessLogger.HEADER_ACCESS_LOG_ID);
+ }
+
private boolean isInternalHeader(String headerName) {
if (!StringUtils.hasText(headerName)) {
return true;
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 81e19c6a..b2bff142 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
@@ -13,6 +13,7 @@ import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
+import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayAccessLogger;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
import com.zt.plat.module.databus.service.gateway.ApiAnonymousUserService;
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
@@ -56,6 +57,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private final ApiClientCredentialService credentialService;
private final ApiAnonymousUserService anonymousUserService;
private final ObjectMapper objectMapper;
+ private final ApiGatewayAccessLogger accessLogger;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private static final TypeReference