From 95d905e76fbda6587577ec487e810420530e420f Mon Sep 17 00:00:00 2001 From: chenbowen Date: Mon, 1 Dec 2025 17:46:42 +0800 Subject: [PATCH 01/13] =?UTF-8?q?1.=20=E9=99=90=E5=88=B6=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=20api=20=E6=97=A5=E5=BF=97=E7=9A=84=E5=AD=97=E6=AE=B5=E9=95=BF?= =?UTF-8?q?=E5=BA=A6=202.=20=E5=AE=8C=E6=95=B4=E8=AE=B0=E5=BD=95=E6=89=80?= =?UTF-8?q?=E6=9C=89=E7=9A=84=20databus=20api=20=E7=9A=84=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=97=A5=E5=BF=97=203.=20=E6=96=B0=E5=A2=9E=20iwork?= =?UTF-8?q?=20=E5=90=8C=E6=AD=A5=E5=8F=AF=E4=BB=A5=E6=8C=89=20id=20?= =?UTF-8?q?=E7=BB=B4=E5=BA=A6=E8=BF=9B=E8=A1=8C=204.=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=89=AB=E6=8F=8F=20BusinessBaseDO=20?= =?UTF-8?q?=E7=9A=84=20=E5=85=AC=E5=8F=B8=E9=83=A8=E9=97=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9D=83=E9=99=90=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 5 + .../BusinessDataPermissionConfiguration.java | 49 +++-- .../BusinessDataPermissionEntityScanner.java | 159 +++++++++++++++ .../core/annotation/CompanyColumn.java | 23 +++ .../mybatis/core/annotation/DeptColumn.java | 23 +++ .../core/dataobject/BusinessBaseDO.java | 4 + .../gateway/core/ApiGatewayAccessLogger.java | 185 +++++++++++++----- .../gateway/core/ApiGatewayRequestMapper.java | 17 ++ .../security/GatewaySecurityFilter.java | 18 +- .../dal/dataobject/logger/ApiErrorLogDO.java | 65 ++++++ .../logger/ApiErrorLogServiceImpl.java | 32 ++- .../logger/ApiErrorLogServiceImplTest.java | 64 +++++- .../iwork/vo/IWorkFullSyncReqVO.java | 3 + .../iwork/impl/IWorkSyncServiceImpl.java | 21 ++ .../src/main/resources/application.yaml | 4 +- 15 files changed, 606 insertions(+), 66 deletions(-) create mode 100644 zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java create mode 100644 zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/CompanyColumn.java create mode 100644 zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/annotation/DeptColumn.java 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 entityClass = (Class) 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 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> MAP_TYPE = new TypeReference<>() {}; @@ -72,18 +74,24 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); return; } + Long accessLogId = accessLogger.logEntrance(request); // 校验访问 IP 是否落在允许范围内 if (!isIpAllowed(request)) { log.warn("[API-PORTAL] 拦截来自 IP {} 访问 {} 的请求", request.getRemoteAddr(), pathWithinApplication); response.sendError(HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); + accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); return; } ApiGatewayProperties.Security security = properties.getSecurity(); ApiClientCredentialDO credential = null; if (!security.isEnabled()) { - filterChain.doFilter(request, response); + byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream()); + CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody); + ApiGatewayAccessLogger.propagateLogIdHeader(passthroughRequest, accessLogId); + filterChain.doFilter(passthroughRequest, response); return; } + boolean dispatchedToGateway = false; try { Long tenantId = resolveTenantId(request); // 从请求头解析 appId 并加载客户端凭证,包含匿名访问配置 @@ -118,6 +126,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { // 使用可重复读取的请求包装,供后续过滤器继续消费 CachedBodyHttpServletRequest securedRequest = new CachedBodyHttpServletRequest(request, decryptedBody); + ApiGatewayAccessLogger.propagateLogIdHeader(securedRequest, accessLogId); if (StringUtils.hasText(request.getCharacterEncoding())) { securedRequest.setCharacterEncoding(request.getCharacterEncoding()); } @@ -129,6 +138,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); try { filterChain.doFilter(securedRequest, responseWrapper); + dispatchedToGateway = true; encryptResponse(responseWrapper, credential, security); } finally { responseWrapper.copyBodyToResponse(); @@ -136,9 +146,15 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { } catch (SecurityValidationException ex) { log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage()); writeErrorResponse(response, security, credential, ex.status(), ex.getMessage()); + if (!dispatchedToGateway) { + accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage()); + } } catch (Exception ex) { log.error("[API-PORTAL] 处理安全校验时出现异常", ex); writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败"); + if (!dispatchedToGateway) { + accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败"); + } } } diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/logger/ApiErrorLogDO.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/logger/ApiErrorLogDO.java index 5be883e9..a0dd659d 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/logger/ApiErrorLogDO.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/logger/ApiErrorLogDO.java @@ -26,11 +26,76 @@ import java.time.LocalDateTime; @KeySequence(value = "infra_api_error_log_seq") public class ApiErrorLogDO extends BaseDO { + /** + * {@link #traceId} 的最大长度 + */ + public static final Integer TRACE_ID_MAX_LENGTH = 64; + + /** + * {@link #applicationName} 的最大长度 + */ + public static final Integer APPLICATION_NAME_MAX_LENGTH = 50; + + /** + * {@link #requestMethod} 的最大长度 + */ + public static final Integer REQUEST_METHOD_MAX_LENGTH = 16; + + /** + * {@link #requestUrl} 的最大长度 + */ + public static final Integer REQUEST_URL_MAX_LENGTH = 255; + /** * {@link #requestParams} 的最大长度 */ public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000; + /** + * {@link #userIp} 的最大长度 + */ + public static final Integer USER_IP_MAX_LENGTH = 50; + + /** + * {@link #userAgent} 的最大长度 + */ + public static final Integer USER_AGENT_MAX_LENGTH = 512; + + /** + * {@link #exceptionName} 的最大长度 + */ + public static final Integer EXCEPTION_NAME_MAX_LENGTH = 128; + + /** + * {@link #exceptionClassName} 的最大长度 + */ + public static final Integer EXCEPTION_CLASS_NAME_MAX_LENGTH = 512; + + /** + * {@link #exceptionFileName} 的最大长度 + */ + public static final Integer EXCEPTION_FILE_NAME_MAX_LENGTH = 512; + + /** + * {@link #exceptionMethodName} 的最大长度 + */ + public static final Integer EXCEPTION_METHOD_NAME_MAX_LENGTH = 512; + + /** + * {@link #exceptionMessage} 的最大长度 + */ + public static final Integer EXCEPTION_MESSAGE_MAX_LENGTH = 4000; + + /** + * {@link #exceptionRootCauseMessage} 的最大长度 + */ + public static final Integer EXCEPTION_ROOT_CAUSE_MESSAGE_MAX_LENGTH = 4000; + + /** + * {@link #exceptionStackTrace} 的最大长度 + */ + public static final Integer EXCEPTION_STACK_TRACE_MAX_LENGTH = 8000; + /** * 编号 */ diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImpl.java index 2c9d2567..7ca2cbff 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImpl.java @@ -18,7 +18,6 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; -import static com.zt.plat.module.infra.dal.dataobject.logger.ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH; import static com.zt.plat.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND; import static com.zt.plat.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED; @@ -39,7 +38,7 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService { public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) { ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class) .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus()); - apiErrorLog.setRequestParams(StrUtils.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH)); + truncateOverflowFields(apiErrorLog); if (TenantContextHolder.getTenantId() != null) { apiErrorLogMapper.insert(apiErrorLog); } else { @@ -48,6 +47,35 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService { } } + private void truncateOverflowFields(ApiErrorLogDO apiErrorLog) { + apiErrorLog.setTraceId(StrUtils.maxLength(apiErrorLog.getTraceId(), ApiErrorLogDO.TRACE_ID_MAX_LENGTH)); + apiErrorLog.setApplicationName(StrUtils.maxLength(apiErrorLog.getApplicationName(), + ApiErrorLogDO.APPLICATION_NAME_MAX_LENGTH)); + apiErrorLog.setRequestMethod(StrUtils.maxLength(apiErrorLog.getRequestMethod(), + ApiErrorLogDO.REQUEST_METHOD_MAX_LENGTH)); + apiErrorLog.setRequestUrl(StrUtils.maxLength(apiErrorLog.getRequestUrl(), + ApiErrorLogDO.REQUEST_URL_MAX_LENGTH)); + apiErrorLog.setRequestParams(StrUtils.maxLength(apiErrorLog.getRequestParams(), + ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH)); + apiErrorLog.setUserIp(StrUtils.maxLength(apiErrorLog.getUserIp(), ApiErrorLogDO.USER_IP_MAX_LENGTH)); + apiErrorLog.setUserAgent(StrUtils.maxLength(apiErrorLog.getUserAgent(), + ApiErrorLogDO.USER_AGENT_MAX_LENGTH)); + apiErrorLog.setExceptionName(StrUtils.maxLength(apiErrorLog.getExceptionName(), + ApiErrorLogDO.EXCEPTION_NAME_MAX_LENGTH)); + apiErrorLog.setExceptionClassName(StrUtils.maxLength(apiErrorLog.getExceptionClassName(), + ApiErrorLogDO.EXCEPTION_CLASS_NAME_MAX_LENGTH)); + apiErrorLog.setExceptionFileName(StrUtils.maxLength(apiErrorLog.getExceptionFileName(), + ApiErrorLogDO.EXCEPTION_FILE_NAME_MAX_LENGTH)); + apiErrorLog.setExceptionMethodName(StrUtils.maxLength(apiErrorLog.getExceptionMethodName(), + ApiErrorLogDO.EXCEPTION_METHOD_NAME_MAX_LENGTH)); + apiErrorLog.setExceptionMessage(StrUtils.maxLength(apiErrorLog.getExceptionMessage(), + ApiErrorLogDO.EXCEPTION_MESSAGE_MAX_LENGTH)); + apiErrorLog.setExceptionRootCauseMessage(StrUtils.maxLength(apiErrorLog.getExceptionRootCauseMessage(), + ApiErrorLogDO.EXCEPTION_ROOT_CAUSE_MESSAGE_MAX_LENGTH)); + apiErrorLog.setExceptionStackTrace(StrUtils.maxLength(apiErrorLog.getExceptionStackTrace(), + ApiErrorLogDO.EXCEPTION_STACK_TRACE_MAX_LENGTH)); + } + @Override public PageResult getApiErrorLogPage(ApiErrorLogPageReqVO pageReqVO) { return apiErrorLogMapper.selectPage(pageReqVO); diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImplTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImplTest.java index 94c82ed2..09d3e227 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImplTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/logger/ApiErrorLogServiceImplTest.java @@ -3,6 +3,7 @@ package com.zt.plat.module.infra.service.logger; import com.zt.plat.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import com.zt.plat.framework.common.enums.UserTypeEnum; import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.string.StrUtils; import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; import com.zt.plat.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO; import com.zt.plat.module.infra.dal.dataobject.logger.ApiErrorLogDO; @@ -13,6 +14,7 @@ import org.springframework.context.annotation.Import; import jakarta.annotation.Resource; import java.time.Duration; +import java.time.LocalDateTime; import java.util.List; import static cn.hutool.core.util.RandomUtil.randomEle; @@ -79,15 +81,63 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest { @Test public void testCreateApiErrorLog() { - // 准备参数 - ApiErrorLogCreateReqDTO createDTO = randomPojo(ApiErrorLogCreateReqDTO.class); + // 准备参数:手动设置多个超长字段,确保持久化前会被截断 + ApiErrorLogCreateReqDTO createDTO = new ApiErrorLogCreateReqDTO(); + createDTO.setTraceId(repeat('t', ApiErrorLogDO.TRACE_ID_MAX_LENGTH + 20)); + createDTO.setUserId(10086L); + createDTO.setUserType(UserTypeEnum.ADMIN.getValue()); + createDTO.setApplicationName(repeat('a', ApiErrorLogDO.APPLICATION_NAME_MAX_LENGTH + 5)); + createDTO.setRequestMethod(repeat('b', ApiErrorLogDO.REQUEST_METHOD_MAX_LENGTH + 10)); + createDTO.setRequestUrl(repeat('c', ApiErrorLogDO.REQUEST_URL_MAX_LENGTH + 20)); + createDTO.setRequestParams(repeat('d', ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH + 50)); + createDTO.setUserIp(repeat('1', ApiErrorLogDO.USER_IP_MAX_LENGTH + 10)); + createDTO.setUserAgent(repeat('e', ApiErrorLogDO.USER_AGENT_MAX_LENGTH + 100)); + createDTO.setExceptionTime(LocalDateTime.of(2025, 1, 1, 10, 20, 30)); + createDTO.setExceptionName(repeat('f', ApiErrorLogDO.EXCEPTION_NAME_MAX_LENGTH + 10)); + createDTO.setExceptionClassName(repeat('g', ApiErrorLogDO.EXCEPTION_CLASS_NAME_MAX_LENGTH + 10)); + createDTO.setExceptionFileName(repeat('h', ApiErrorLogDO.EXCEPTION_FILE_NAME_MAX_LENGTH + 10)); + createDTO.setExceptionMethodName(repeat('i', ApiErrorLogDO.EXCEPTION_METHOD_NAME_MAX_LENGTH + 10)); + createDTO.setExceptionLineNumber(256); + createDTO.setExceptionStackTrace(repeat('s', ApiErrorLogDO.EXCEPTION_STACK_TRACE_MAX_LENGTH + 100)); + createDTO.setExceptionRootCauseMessage(repeat('r', ApiErrorLogDO.EXCEPTION_ROOT_CAUSE_MESSAGE_MAX_LENGTH + 80)); + createDTO.setExceptionMessage(repeat('m', ApiErrorLogDO.EXCEPTION_MESSAGE_MAX_LENGTH + 60)); // 调用 apiErrorLogService.createApiErrorLog(createDTO); // 断言 ApiErrorLogDO apiErrorLogDO = apiErrorLogMapper.selectOne(null); - assertPojoEquals(createDTO, apiErrorLogDO); + assertEquals(createDTO.getUserId(), apiErrorLogDO.getUserId()); + assertEquals(createDTO.getUserType(), apiErrorLogDO.getUserType()); + assertEquals(createDTO.getExceptionTime(), apiErrorLogDO.getExceptionTime()); + assertEquals(createDTO.getExceptionLineNumber(), apiErrorLogDO.getExceptionLineNumber()); assertEquals(ApiErrorLogProcessStatusEnum.INIT.getStatus(), apiErrorLogDO.getProcessStatus()); + + assertTruncated(createDTO.getTraceId(), apiErrorLogDO.getTraceId(), ApiErrorLogDO.TRACE_ID_MAX_LENGTH); + assertTruncated(createDTO.getApplicationName(), apiErrorLogDO.getApplicationName(), + ApiErrorLogDO.APPLICATION_NAME_MAX_LENGTH); + assertTruncated(createDTO.getRequestMethod(), apiErrorLogDO.getRequestMethod(), + ApiErrorLogDO.REQUEST_METHOD_MAX_LENGTH); + assertTruncated(createDTO.getRequestUrl(), apiErrorLogDO.getRequestUrl(), + ApiErrorLogDO.REQUEST_URL_MAX_LENGTH); + assertTruncated(createDTO.getRequestParams(), apiErrorLogDO.getRequestParams(), + ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH); + assertTruncated(createDTO.getUserIp(), apiErrorLogDO.getUserIp(), ApiErrorLogDO.USER_IP_MAX_LENGTH); + assertTruncated(createDTO.getUserAgent(), apiErrorLogDO.getUserAgent(), + ApiErrorLogDO.USER_AGENT_MAX_LENGTH); + assertTruncated(createDTO.getExceptionName(), apiErrorLogDO.getExceptionName(), + ApiErrorLogDO.EXCEPTION_NAME_MAX_LENGTH); + assertTruncated(createDTO.getExceptionClassName(), apiErrorLogDO.getExceptionClassName(), + ApiErrorLogDO.EXCEPTION_CLASS_NAME_MAX_LENGTH); + assertTruncated(createDTO.getExceptionFileName(), apiErrorLogDO.getExceptionFileName(), + ApiErrorLogDO.EXCEPTION_FILE_NAME_MAX_LENGTH); + assertTruncated(createDTO.getExceptionMethodName(), apiErrorLogDO.getExceptionMethodName(), + ApiErrorLogDO.EXCEPTION_METHOD_NAME_MAX_LENGTH); + assertTruncated(createDTO.getExceptionStackTrace(), apiErrorLogDO.getExceptionStackTrace(), + ApiErrorLogDO.EXCEPTION_STACK_TRACE_MAX_LENGTH); + assertTruncated(createDTO.getExceptionRootCauseMessage(), apiErrorLogDO.getExceptionRootCauseMessage(), + ApiErrorLogDO.EXCEPTION_ROOT_CAUSE_MESSAGE_MAX_LENGTH); + assertTruncated(createDTO.getExceptionMessage(), apiErrorLogDO.getExceptionMessage(), + ApiErrorLogDO.EXCEPTION_MESSAGE_MAX_LENGTH); } @Test @@ -161,4 +211,12 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest { assertPojoEquals(log02, logs.get(0), "createTime", "updateTime"); } + private static void assertTruncated(String source, String actual, int maxLength) { + assertEquals(StrUtils.maxLength(source, maxLength), actual); + } + + private static String repeat(char ch, int length) { + return String.valueOf(ch).repeat(Math.max(length, 0)); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java index 98e9f1dd..09fe2d3e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFullSyncReqVO.java @@ -33,6 +33,9 @@ public class IWorkFullSyncReqVO { @Schema(description = "同步范围列表,默认同步全部。可选:subcompany、department、jobTitle、user") private List scopes; + @Schema(description = "指定同步记录的 iWork ID。传入后仅同步对应记录", example = "12345") + private String id; + @Schema(description = "是否包含已失效(canceled=1)的记录", example = "false") private Boolean includeCanceled = Boolean.FALSE; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java index 53c926e6..8954ea8f 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java @@ -14,7 +14,9 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED; @@ -86,6 +88,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO(); query.setCurpage(page); query.setPagesize(pageSize); + applyQueryConditions(query, reqVO); IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query); ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); @@ -103,6 +106,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO(); query.setCurpage(page); query.setPagesize(pageSize); + applyQueryConditions(query, reqVO); IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query); ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); @@ -120,6 +124,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO(); query.setCurpage(page); query.setPagesize(pageSize); + applyQueryConditions(query, reqVO); IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query); ensureIWorkSuccess("拉取岗位", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); @@ -137,6 +142,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkUserQueryReqVO query = new IWorkUserQueryReqVO(); query.setCurpage(page); query.setPagesize(pageSize); + applyQueryConditions(query, reqVO); IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query); ensureIWorkSuccess("拉取人员", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); @@ -181,6 +187,21 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { stat.incrementFailed(result.getFailed()); } + private void applyQueryConditions(IWorkOrgBaseQueryReqVO query, IWorkFullSyncReqVO reqVO) { + if (query == null || reqVO == null) { + return; + } + if (StrUtil.isBlank(reqVO.getId())) { + return; + } + Map params = query.getParams(); + if (params == null) { + params = new HashMap<>(); + query.setParams(params); + } + params.put("id", reqVO.getId()); + } + private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) { boolean includeCanceled = Boolean.TRUE.equals(reqVO.getIncludeCanceled()); boolean allowUpdate = Boolean.TRUE.equals(reqVO.getAllowUpdate()); diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml index f27d9baa..c027e52b 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml @@ -120,8 +120,8 @@ iwork: ttl-seconds: 3600 refresh-ahead-seconds: 60 client: - connect-timeout: 5s - response-timeout: 30s + connect-timeout: 60s + response-timeout: 60s org: token-seed: 456465 paths: From 55dd8df69c86ce56c77dcb1e84d31e2c222bb727 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 09:03:26 +0800 Subject: [PATCH 02/13] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=20iwork=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E9=94=99=E8=AF=AF=E6=9C=AA=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=85=B7=E4=BD=93=E7=9A=84=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWorkIntegrationErrorCodeConstants.java | 8 ++++---- .../impl/IWorkIntegrationServiceImpl.java | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkIntegrationErrorCodeConstants.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkIntegrationErrorCodeConstants.java index 9faf4e1c..faf0c9ab 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkIntegrationErrorCodeConstants.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkIntegrationErrorCodeConstants.java @@ -10,13 +10,13 @@ public interface IWorkIntegrationErrorCodeConstants { ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_001, "iWork 集成未配置网关地址"); ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_002, "iWork 集成缺少必填配置(appId/userId/workflowId)或配置无效"); - ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_003, "iWork 注册授权失败"); - ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_004, "iWork 令牌申请失败"); - ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_005, "iWork 接口请求失败"); + ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_003, "iWork 注册授权失败:{}"); + ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_004, "iWork 令牌申请失败:{}"); + ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_005, "iWork 接口请求失败:{}"); ErrorCode IWORK_USER_IDENTIFIER_MISSING = new ErrorCode(1_010_200_006, "缺少用户识别信息,无法调用 iWork 接口"); ErrorCode IWORK_OPERATOR_USER_MISSING = new ErrorCode(1_010_200_007, "缺少 iWork 操作人用户编号"); ErrorCode IWORK_WORKFLOW_ID_MISSING = new ErrorCode(1_010_200_008, "缺少 iWork 流程模板编号"); ErrorCode IWORK_ORG_IDENTIFIER_MISSING = new ErrorCode(1_010_200_009, "iWork 人力组织接口缺少认证标识"); - ErrorCode IWORK_ORG_REMOTE_FAILED = new ErrorCode(1_010_200_010, "iWork 人力组织接口请求失败{}"); + ErrorCode IWORK_ORG_REMOTE_FAILED = new ErrorCode(1_010_200_010, "iWork 人力组织接口请求失败:{}"); ErrorCode IWORK_SEAL_REQUIRED_FIELD_MISSING = new ErrorCode(1_010_200_011, "缺少用印必填字段:{}"); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java index 4e319c07..3c6885d3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java @@ -850,15 +850,26 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { } private ServiceException buildRemoteException(ErrorCode errorCode, int statusCode, String responseBody) { - StringBuilder message = new StringBuilder(errorCode.getMsg()); + String detail = buildRemoteErrorDetail(statusCode, responseBody); + if (!StringUtils.hasText(detail)) { + detail = "未知错误"; + } + return ServiceExceptionUtil.exception(errorCode, detail); + } + + private String buildRemoteErrorDetail(int statusCode, String responseBody) { + StringBuilder detail = new StringBuilder(); if (statusCode > 0) { - message.append("(HTTP ").append(statusCode).append(")"); + detail.append("HTTP ").append(statusCode); } String reason = extractReadableReason(responseBody); if (StringUtils.hasText(reason)) { - message.append(":").append(reason); + if (detail.length() > 0) { + detail.append(","); + } + detail.append(reason); } - return ServiceExceptionUtil.exception0(errorCode.getCode(), message.toString()); + return detail.toString(); } private String extractReadableReason(String responseBody) { From a689a3ffe4cb6ecae4cf933cab276a21c2ae7cf5 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 11:13:33 +0800 Subject: [PATCH 03/13] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9C=AA=E5=90=8C=E6=AD=A5=E4=BA=BA=E5=8A=9B?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=BC=96=E5=8F=B7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/ruoyi-vue-pro-dm8.sql | 38 ++++++++++--------- sql/dm/新增用户工号字段_20251202.sql | 8 ++++ .../auth/vo/AuthPermissionInfoRespVO.java | 3 ++ .../user/vo/profile/UserProfileRespVO.java | 3 ++ .../admin/user/vo/user/UserPageReqVO.java | 3 ++ .../admin/user/vo/user/UserRespVO.java | 4 ++ .../admin/user/vo/user/UserSaveReqVO.java | 5 +++ .../dal/dataobject/user/AdminUserDO.java | 4 ++ .../dal/mysql/user/AdminUserMapper.java | 5 +++ .../iwork/impl/IWorkSyncProcessorImpl.java | 1 + .../service/oauth2/EbanOAuth2ServiceImpl.java | 16 ++++---- .../system/service/user/AdminUserService.java | 8 ++++ .../service/user/AdminUserServiceImpl.java | 24 ++++++++++++ 13 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 sql/dm/新增用户工号字段_20251202.sql diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index 2fba3fe4..845c7c99 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -4453,6 +4453,7 @@ CREATE TABLE system_users ( username varchar(30) NOT NULL, password varchar(100) DEFAULT '' NULL, nickname varchar(30) NOT NULL, + workcode varchar(64) DEFAULT NULL NULL, remark varchar(500) DEFAULT NULL NULL, post_ids varchar(255) DEFAULT NULL NULL, email varchar(50) DEFAULT '' NULL, @@ -4474,6 +4475,7 @@ COMMENT ON COLUMN system_users.id IS '用户ID'; COMMENT ON COLUMN system_users.username IS '用户账号'; COMMENT ON COLUMN system_users.password IS '密码'; COMMENT ON COLUMN system_users.nickname IS '用户昵称'; +COMMENT ON COLUMN system_users.workcode IS '工号'; COMMENT ON COLUMN system_users.remark IS '备注'; COMMENT ON COLUMN system_users.post_ids IS '岗位编号数组'; COMMENT ON COLUMN system_users.email IS '用户邮箱'; @@ -4496,24 +4498,24 @@ COMMENT ON TABLE system_users IS '用户信息表'; -- ---------------------------- -- @formatter:off -- SET IDENTITY_INSERT system_users ON; -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 'http://test.zt.iocoder.cn/test/20250502/avatar_1746154660449.png', 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'zt', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', 'ZT', '不要吓我', '[1]', 'zt@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', 'ZT', NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', '0', 118); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', 'ZT', NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', '0', 119); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', 'ZT', NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', '0', 120); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', '0', 121); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', '0', 121); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, '[]', '', '15601691235', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', 'ZT1', NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', '0', 122); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-24 22:21:05', '1', '2022-03-19 21:50:58', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (118, 'goudan', '$2a$04$jth0yOj8cSJq84D6vrzusOHDwW/LpBfgBnQ6bfFlD8zNZfM632Ta2', '狗蛋', NULL, '[1]', '', '15601691239', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-17 09:10:27', '1', '2022-07-09 17:44:43', '1', '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (131, 'hh', '$2a$04$jyH9h6.gaw8mpOjPfHIpx.8as2Rzfcmdlj5rlJFwgCw4rsv/MTb2K', '呵呵', NULL, '[]', '777@qq.com', '15601882312', 1, NULL, 0, '', NULL, '1', '2024-04-27 08:45:56', '1', '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (139, 'wwbwwb', '$2a$04$aOHoFbQU6zfBk/1Z9raF/ugTdhjNdx7culC1HhO0zvoczAnahCiMq', '小秃头', NULL, NULL, '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', NULL, '2025-04-21 14:23:08', '0', 1); -INSERT INTO system_users (id, username, password, nickname, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', NULL, '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 'http://test.zt.iocoder.cn/test/20250502/avatar_1746154660449.png', 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'zt', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', 'ZT', NULL, '不要吓我', '[1]', 'zt@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, NULL, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, NULL, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', 'ZT', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', '0', 118); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', 'ZT', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', '0', 119); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', 'ZT', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', '0', 120); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', '0', 121); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', '0', 121); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, NULL, '[]', '', '15601691235', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', 'ZT1', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', '0', 122); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-24 22:21:05', '1', '2022-03-19 21:50:58', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', NULL, '11222', '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', NULL, '1111', '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (118, 'goudan', '$2a$04$jth0yOj8cSJq84D6vrzusOHDwW/LpBfgBnQ6bfFlD8zNZfM632Ta2', '狗蛋', NULL, NULL, '[1]', '', '15601691239', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-17 09:10:27', '1', '2022-07-09 17:44:43', '1', '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (131, 'hh', '$2a$04$jyH9h6.gaw8mpOjPfHIpx.8as2Rzfcmdlj5rlJFwgCw4rsv/MTb2K', '呵呵', NULL, NULL, '[]', '777@qq.com', '15601882312', 1, NULL, 0, '', NULL, '1', '2024-04-27 08:45:56', '1', '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (139, 'wwbwwb', '$2a$04$aOHoFbQU6zfBk/1Z9raF/ugTdhjNdx7culC1HhO0zvoczAnahCiMq', '小秃头', NULL, NULL, NULL, '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '0', 1); COMMIT; -- SET IDENTITY_INSERT system_users OFF; -- @formatter:on diff --git a/sql/dm/新增用户工号字段_20251202.sql b/sql/dm/新增用户工号字段_20251202.sql new file mode 100644 index 00000000..138e864a --- /dev/null +++ b/sql/dm/新增用户工号字段_20251202.sql @@ -0,0 +1,8 @@ +-- 达梦8数据库 DDL 脚本 +-- 为 system_users 表添加 workcode 字段(工号) + +-- 添加工号字段 +ALTER TABLE system_users ADD COLUMN workcode VARCHAR(64); + +-- 添加字段注释 +COMMENT ON COLUMN system_users.workcode IS '工号'; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java index d3dc158c..e8a76dbc 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java @@ -50,6 +50,9 @@ public class AuthPermissionInfoRespVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt") private String username; + @Schema(description = "工号", example = "A00123") + private String workcode; + @Schema(description = "用户邮箱", example = "zt@iocoder.cn") private String email; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java index bb49b040..4b0b9a6e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java @@ -22,6 +22,9 @@ public class UserProfileRespVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt") private String username; + @Schema(description = "工号", example = "A00123") + private String workcode; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT") private String nickname; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java index 25dba90b..ec64cc41 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserPageReqVO.java @@ -23,6 +23,9 @@ public class UserPageReqVO extends PageParam { @Schema(description = "用户账号,模糊匹配", example = "zt") private String username; + @Schema(description = "工号,模糊匹配", example = "A00123") + private String workcode; + @Schema(description = "手机号码,模糊匹配", example = "zt") private String mobile; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserRespVO.java index ca1d0b8b..c20e1ebd 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserRespVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserRespVO.java @@ -25,6 +25,10 @@ public class UserRespVO{ @ExcelProperty("用户名称") private String username; + @Schema(description = "工号", example = "A00123") + @ExcelProperty("工号") + private String workcode; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT") @ExcelProperty("用户昵称") private String nickname; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index ceba8266..d0bd575d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -33,6 +33,11 @@ public class UserSaveReqVO { @DiffLogField(name = "用户账号") private String username; + @Schema(description = "工号", example = "A00123") + @Length(max = 64, message = "工号长度不能超过64个字符") + @DiffLogField(name = "工号") + private String workcode; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT") @Size(max = 30, message = "用户昵称长度不能超过30个字符") @DiffLogField(name = "用户昵称") diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/user/AdminUserDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/user/AdminUserDO.java index bddcbd8d..72d173e2 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/user/AdminUserDO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/user/AdminUserDO.java @@ -38,6 +38,10 @@ public class AdminUserDO extends TenantBaseDO { */ @NotEmpty private String username; + /** + * 工号 + */ + private String workcode; /** * 加密后的密码 * diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java index 58682c73..270a3534 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/user/AdminUserMapper.java @@ -23,6 +23,10 @@ public interface AdminUserMapper extends BaseMapperX { return selectOne(AdminUserDO::getUsername, username); } + default AdminUserDO selectByWorkcode(String workcode) { + return selectOne(AdminUserDO::getWorkcode, workcode); + } + default AdminUserDO selectByEmail(String email) { return selectOne(AdminUserDO::getEmail, email); } @@ -36,6 +40,7 @@ public interface AdminUserMapper extends BaseMapperX { return selectJoinPage(reqVO, AdminUserDO.class, new MPJLambdaWrapperX() .leftJoin(UserDeptDO.class, UserDeptDO::getUserId, AdminUserDO::getId) .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername()) + .likeIfPresent(AdminUserDO::getWorkcode, reqVO.getWorkcode()) .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile()) .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus()) .betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime()) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java index c9922d9f..b79aa877 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java @@ -435,6 +435,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { CommonStatusEnum status) { UserSaveReqVO req = new UserSaveReqVO(); req.setUsername(username); + req.setWorkcode(trimToNull(source.getWorkcode())); req.setNickname(limitLength(StrUtil.blankToDefault(source.getLastname(), username), 30)); req.setRemark(buildUserRemark(source)); if (deptId != null) { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java index 0aa803e0..fe41228f 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java @@ -72,16 +72,16 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } - String username = StrUtil.trim(StrUtil.blankToDefault(userInfo.getLoginName(), userInfo.getUsername())); - if (StrUtil.isBlank(username)) { - log.error("E办OAuth2用户信息缺少 username 与 loginName,无法匹配账号: {}", JSONUtil.toJsonStr(userInfo)); + String workcode = StrUtil.trim(StrUtil.blankToDefault(userInfo.getLoginName(), userInfo.getUsername())); + if (StrUtil.isBlank(workcode)) { + log.error("E办OAuth2用户信息缺少工号(loginName),无法匹配账号: {}", JSONUtil.toJsonStr(userInfo)); throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } - AdminUserDO user = userService.getUserByUsername(username); + AdminUserDO user = userService.getUserByWorkcode(workcode); if (user == null) { - createLoginLog(null, username, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS); - log.warn("E办OAuth2用户displayName未在系统中找到对应账号: {}", username); + createLoginLog(null, workcode, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS); + log.warn("E办OAuth2用户工号未在系统中找到对应账号: {}", workcode); throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC); } @@ -92,7 +92,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { EbanTokenInfo tokenInfo = userInfo.getTokenInfo(); if (tokenInfo == null || StrUtil.isBlank(tokenInfo.getAccessToken())) { - log.error("E办OAuth2回调缺少有效的token信息,uid={}, username={}", userInfo.getUid(), username); + log.error("E办OAuth2回调缺少有效的token信息,uid={}, username={}", userInfo.getUid(), userInfo.getUsername()); throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } @@ -105,7 +105,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { tokenInfo.getExpiresIn(), userInfo ); - log.info("成功保存E办token,userId={}, uid={}, username={}", user.getId(), userInfo.getUid(), username); + log.info("成功保存E办token,userId={}, uid={}, workcode={}", user.getId(), userInfo.getUid(), workcode); return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, ebanAccessTokenDO); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java index fb82c63d..63f54ef3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java @@ -101,6 +101,14 @@ public interface AdminUserService { */ AdminUserDO getUserByUsername(String username); + /** + * 通过工号查询用户 + * + * @param workcode 工号 + * @return 用户对象信息 + */ + AdminUserDO getUserByWorkcode(String workcode); + /** * 通过手机号获取用户 * diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java index 4384f9ed..d1b00ab7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java @@ -125,6 +125,7 @@ public class AdminUserServiceImpl implements AdminUserService { if (user.getUserSource() == null) { user.setUserSource(UserSourceEnum.EXTERNAL.getSource()); } + user.setWorkcode(normalizeWorkcode(createReqVO.getWorkcode())); PasswordStrategyEnum passwordStrategy = determinePasswordStrategy(user.getUserSource()); user.setAvatar(normalizeAvatarValue(createReqVO.getAvatar())); user.setPassword(encodePassword(createReqVO.getPassword(), passwordStrategy)); @@ -190,6 +191,9 @@ public class AdminUserServiceImpl implements AdminUserService { if (StrUtil.isNotBlank(updateReqVO.getNickname())) { updateObj.setNickname(updateReqVO.getNickname()); } + if (updateReqVO.getWorkcode() != null) { + updateObj.setWorkcode(normalizeWorkcode(updateReqVO.getWorkcode())); + } if (StrUtil.isNotBlank(updateReqVO.getMobile())) { updateObj.setMobile(updateReqVO.getMobile()); } @@ -330,6 +334,19 @@ public class AdminUserServiceImpl implements AdminUserService { return user; } + @Override + public AdminUserDO getUserByWorkcode(String workcode) { + String normalized = normalizeWorkcode(workcode); + if (StrUtil.isBlank(normalized)) { + return null; + } + AdminUserDO user = userMapper.selectByWorkcode(normalized); + if (user != null) { + fillUserDeptInfo(Collections.singletonList(user)); + } + return user; + } + @Override public AdminUserDO getUserByMobile(String mobile) { AdminUserDO user = userMapper.selectByMobile(mobile); @@ -510,6 +527,13 @@ public class AdminUserServiceImpl implements AdminUserService { return StrUtil.isBlank(avatarValue) ? null : avatarValue.trim(); } + private String normalizeWorkcode(String workcode) { + if (StrUtil.isBlank(workcode)) { + return null; + } + return workcode.trim(); + } + private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email, Set deptIds, Set postIds, boolean skipAssociationValidation, boolean skipMobileValidation, boolean skipEmailValidation) { From ee9bc438b53660f9acf8775a96f2902b64b55779 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 14:10:47 +0800 Subject: [PATCH 04/13] =?UTF-8?q?1.=20=E5=89=94=E9=99=A4=E5=A4=96=E5=9F=9F?= =?UTF-8?q?=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- sql/dm/ruoyi-vue-pro-dm8.sql | 2 +- sql/mysql/ruoyi-vue-pro.sql | 2 +- .../framework/common/enums/DocumentEnum.java | 2 +- .../core/handler/GlobalExceptionHandler.java | 32 +-------------- .../AiKnowledgeDocumentCreateListReqVO.java | 2 +- .../document/AiKnowledgeDocumentRespVO.java | 2 +- .../AiKnowledgeDocumentCreateReqVO.java | 2 +- .../file/FileConfigServiceImplTest.java | 8 ++-- .../databridge/IotDataBridgeExecuteTest.java | 2 +- .../service/account/MpAccountServiceImpl.java | 2 +- .../server/controller/DefaultController.java | 40 ++----------------- zt-server/src/main/resources/application.yaml | 1 - 13 files changed, 18 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 871f59d0..2e3f922f 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,7 @@ | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 6.1.10 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?zt) | | [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 6.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?zt) | | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 8.0.1 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?zt) | -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) | +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](http://172.16.46.63:30888/bpm/) | | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 4.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?zt) | | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.0.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?zt) | | [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?zt) | diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index 845c7c99..aa082349 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -2800,7 +2800,7 @@ COMMENT ON TABLE system_oauth2_client IS 'OAuth2 客户端表'; -- ---------------------------- -- @formatter:off -- SET IDENTITY_INSERT system_oauth2_client ON; -INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (1, 'default', 'admin123', '后台管理', 'http://test.zt.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '["https://www.iocoder.cn","https://doc.iocoder.cn"]', '["password","authorization_code","implicit","refresh_token"]', '["user.read","user.write"]', '[]', '["user.read","user.write"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-05-02 20:42:22', '0'); +INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (1, 'default', 'admin123', '后台管理', 'http://test.zt.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '["https://www.iocoder.cn","http://172.16.46.63:30888"]', '["password","authorization_code","implicit","refresh_token"]', '["user.read","user.write"]', '[]', '["user.read","user.write"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-05-02 20:42:22', '0'); INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.zt.iocoder.cn/xx/20250502/ed07110a37464b5299f8bd7c67ad65c7_1746187077009.jpg', '啦啦啦啦', 0, 1800, 43200, '["https://www.iocoder.cn"]', '["password","authorization_code","implicit"]', '["user_info","projects"]', '["user_info"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-05-02 19:58:08', '0'); INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (41, 'zt-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.zt.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '["http://127.0.0.1:18080"]', '["authorization_code","refresh_token"]', '["user.read","user.write"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', '0'); INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (42, 'zt-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.zt.iocoder.cn/604bdc695e13b3b22745be704d1f2aa8ee05c5f26f9fead6d1ca49005afbc857.jpeg', NULL, 0, 1800, 43200, '["http://127.0.0.1:18080"]', '["password","refresh_token"]', '["user.read","user.write"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2025-05-04 16:00:46', '0'); diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 399a5409..c8cce0d3 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -2450,7 +2450,7 @@ CREATE TABLE `system_oauth2_client` ( -- Records of system_oauth2_client -- ---------------------------- BEGIN; -INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '后台管理', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-05-02 20:42:22', b'0'); +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '后台管理', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"http://172.16.46.63:30888\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-05-02 20:42:22', b'0'); INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/xx/20250502/ed07110a37464b5299f8bd7c67ad65c7_1746187077009.jpg', '啦啦啦啦', 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-05-02 19:58:08', b'0'); INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', b'0'); INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/604bdc695e13b3b22745be704d1f2aa8ee05c5f26f9fead6d1ca49005afbc857.jpeg', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2025-05-04 16:00:46', b'0'); diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/enums/DocumentEnum.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/enums/DocumentEnum.java index f7598356..126c2688 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/enums/DocumentEnum.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/enums/DocumentEnum.java @@ -13,7 +13,7 @@ import lombok.Getter; public enum DocumentEnum { REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"), - TENANT("https://doc.iocoder.cn", "SaaS 多租户文档"); + TENANT("http://172.16.46.63:30888", "SaaS 多租户文档"); private final String url; private final String memo; diff --git a/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/core/handler/GlobalExceptionHandler.java b/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/core/handler/GlobalExceptionHandler.java index 1d2b8907..edcbfaf9 100644 --- a/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/core/handler/GlobalExceptionHandler.java +++ b/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/core/handler/GlobalExceptionHandler.java @@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.servlet.JakartaServletUtil; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.zt.plat.framework.common.biz.infra.logger.ApiErrorLogCommonApi; import com.zt.plat.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import com.zt.plat.framework.common.exception.ServiceException; @@ -16,7 +17,6 @@ import com.zt.plat.framework.common.util.json.JsonUtils; import com.zt.plat.framework.common.util.monitor.TracerUtils; import com.zt.plat.framework.common.util.servlet.ServletUtils; import com.zt.plat.framework.web.core.util.WebFrameworkUtils; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; @@ -365,42 +365,12 @@ public class GlobalExceptionHandler { return CommonResult.error(NOT_IMPLEMENTED.getCode(), "[微信公众号 zt-module-mp - 表结构未导入][参考 http://172.16.46.63:30888/mp/build/ 开启]"); } - // 4. 商城系统 - if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) { - log.error("[商城系统 zt-module-mall - 已禁用][参考 http://172.16.46.63:30888/mall/build/ 开启]"); - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[商城系统 zt-module-mall - 已禁用][参考 http://172.16.46.63:30888/mall/build/ 开启]"); - } - // 5. ERP 系统 - if (message.contains("erp_")) { - log.error("[ERP 系统 zt-module-erp - 表结构未导入][参考 http://172.16.46.63:30888/erp/build/ 开启]"); - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[ERP 系统 zt-module-erp - 表结构未导入][参考 http://172.16.46.63:30888/erp/build/ 开启]"); - } - // 6. CRM 系统 - if (message.contains("crm_")) { - log.error("[CRM 系统 zt-module-crm - 表结构未导入][参考 http://172.16.46.63:30888/crm/build/ 开启]"); - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[CRM 系统 zt-module-crm - 表结构未导入][参考 http://172.16.46.63:30888/crm/build/ 开启]"); - } - // 7. 支付平台 - if (message.contains("pay_")) { - log.error("[支付模块 zt-module-pay - 表结构未导入][参考 http://172.16.46.63:30888/pay/build/ 开启]"); - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[支付模块 zt-module-pay - 表结构未导入][参考 http://172.16.46.63:30888/pay/build/ 开启]"); - } // 8. AI 大模型 if (message.contains("ai_")) { log.error("[AI 大模型 zt-module-ai - 表结构未导入][参考 http://172.16.46.63:30888/ai/build/ 开启]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), "[AI 大模型 zt-module-ai - 表结构未导入][参考 http://172.16.46.63:30888/ai/build/ 开启]"); } - // 9. IOT 物联网 - if (message.contains("iot_")) { - log.error("[IoT 物联网 zt-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[IoT 物联网 zt-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); - } return null; } diff --git a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java index 38368f0b..dee17fc4 100644 --- a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java +++ b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java @@ -33,7 +33,7 @@ public class AiKnowledgeDocumentCreateListReqVO { @NotBlank(message = "文档名称不能为空") private String name; - @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn") + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://172.16.46.63:30888") @URL(message = "文档 URL 格式不正确") private String url; diff --git a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java index 0a4249dc..de3bb07b 100644 --- a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java +++ b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java @@ -18,7 +18,7 @@ public class AiKnowledgeDocumentRespVO { @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") private String name; - @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn") + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://172.16.46.63:30888") private String url; @Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....") diff --git a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java index 4da54893..3a1e8fdb 100644 --- a/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java +++ b/zt-module-ai/zt-module-ai-server/src/main/java/com/zt/plat/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java @@ -19,7 +19,7 @@ public class AiKnowledgeDocumentCreateReqVO { @NotBlank(message = "文档名称不能为空") private String name; - @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn") + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://172.16.46.63:30888") @URL(message = "文档 URL 格式不正确") private String url; diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileConfigServiceImplTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileConfigServiceImplTest.java index 9c572d9d..7d271209 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileConfigServiceImplTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileConfigServiceImplTest.java @@ -15,13 +15,13 @@ import com.zt.plat.module.infra.framework.file.core.client.FileClientFactory; import com.zt.plat.module.infra.framework.file.core.client.local.LocalFileClient; import com.zt.plat.module.infra.framework.file.core.client.local.LocalFileClientConfig; import com.zt.plat.module.infra.framework.file.core.enums.FileStorageEnum; +import jakarta.annotation.Resource; +import jakarta.validation.Validator; import lombok.Data; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import jakarta.annotation.Resource; -import jakarta.validation.Validator; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Map; @@ -92,7 +92,7 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { o.setId(dbFileConfig.getId()); // 设置更新的 ID o.setStorage(FileStorageEnum.LOCAL.getStorage()); Map config = MapUtil.builder().put("basePath", "/yunai2") - .put("domain", "https://doc.iocoder.cn").build(); + .put("domain", "http://172.16.46.63:30888").build(); o.setConfig(config); }); @@ -102,7 +102,7 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { FileConfigDO fileConfig = fileConfigMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, fileConfig, "config"); assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath()); - assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); + assertEquals("http://172.16.46.63:30888", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain()); // 验证 cache assertNull(fileConfigService.getClientCache().getIfPresent(fileConfig.getId())); } diff --git a/zt-module-iot/zt-module-iot-biz/src/test/java/com/zt/plat/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java b/zt-module-iot/zt-module-iot-biz/src/test/java/com/zt/plat/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java index 9a420944..b940da30 100644 --- a/zt-module-iot/zt-module-iot-biz/src/test/java/com/zt/plat/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java +++ b/zt-module-iot/zt-module-iot-biz/src/test/java/com/zt/plat/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java @@ -143,7 +143,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest { public void testHttpDataBridge() throws Exception { // 创建配置 IotDataBridgeHttpConfig config = new IotDataBridgeHttpConfig(); - config.setUrl("https://doc.iocoder.cn/"); + config.setUrl("http://172.16.46.63:30888/"); config.setMethod(HttpMethod.GET.name()); // 执行测试 diff --git a/zt-module-mp/zt-module-mp-server/src/main/java/com/zt/plat/module/mp/service/account/MpAccountServiceImpl.java b/zt-module-mp/zt-module-mp-server/src/main/java/com/zt/plat/module/mp/service/account/MpAccountServiceImpl.java index 5736b7f3..0dc6b18d 100644 --- a/zt-module-mp/zt-module-mp-server/src/main/java/com/zt/plat/module/mp/service/account/MpAccountServiceImpl.java +++ b/zt-module-mp/zt-module-mp-server/src/main/java/com/zt/plat/module/mp/service/account/MpAccountServiceImpl.java @@ -76,7 +76,7 @@ public class MpAccountServiceImpl implements MpAccountService { if (!ex.getMessage().contains("doesn't exist")) { throw ex; } - log.error("[微信公众号 zt-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]"); + log.error("[微信公众号 zt-module-mp - 表结构未导入][参考 http://172.16.46.63:30888/mp/build/ 开启]"); } log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size()); diff --git a/zt-server/src/main/java/com/zt/plat/server/controller/DefaultController.java b/zt-server/src/main/java/com/zt/plat/server/controller/DefaultController.java index fd7f2f28..1306e0df 100644 --- a/zt-server/src/main/java/com/zt/plat/server/controller/DefaultController.java +++ b/zt-server/src/main/java/com/zt/plat/server/controller/DefaultController.java @@ -23,57 +23,25 @@ public class DefaultController { @RequestMapping("/admin-api/bpm/**") public CommonResult bpm404() { return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[工作流模块 zt-module-bpm - 已禁用][参考 https://doc.iocoder.cn/bpm/ 开启]"); + "[工作流模块 zt-module-bpm - 已禁用][参考 http://172.16.46.63:30888/bpm/ 开启]"); } @RequestMapping("/admin-api/mp/**") public CommonResult mp404() { return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[微信公众号 zt-module-mp - 已禁用][参考 https://doc.iocoder.cn/mp/build/ 开启]"); - } - - @RequestMapping(value = { "/admin-api/product/**", // 商品中心 - "/admin-api/trade/**", // 交易中心 - "/admin-api/promotion/**" }) // 营销中心 - public CommonResult mall404() { - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[商城系统 zt-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]"); - } - - @RequestMapping("/admin-api/erp/**") - public CommonResult erp404() { - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[ERP 模块 zt-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]"); - } - - @RequestMapping("/admin-api/crm/**") - public CommonResult crm404() { - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[CRM 模块 zt-module-crm - 已禁用][参考 https://doc.iocoder.cn/crm/build/ 开启]"); + "[微信公众号 zt-module-mp - 已禁用][参考 http://172.16.46.63:30888/mp/build/ 开启]"); } @RequestMapping(value = { "/admin-api/report/**"}) public CommonResult report404() { return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[报表模块 zt-module-report - 已禁用][参考 https://doc.iocoder.cn/report/ 开启]"); - } - - @RequestMapping(value = { "/admin-api/pay/**"}) - public CommonResult pay404() { - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[支付模块 zt-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]"); + "[报表模块 zt-module-report - 已禁用][参考 http://172.16.46.63:30888/report/ 开启]"); } @RequestMapping(value = { "/admin-api/ai/**"}) public CommonResult ai404() { return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[AI 大模型 zt-module-ai - 已禁用][参考 https://doc.iocoder.cn/ai/build/ 开启]"); - } - - @RequestMapping(value = { "/admin-api/iot/**"}) - public CommonResult iot404() { - return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[IoT 物联网 zt-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + "[AI 大模型 zt-module-ai - 已禁用][参考 http://172.16.46.63:30888/ai/build/ 开启]"); } /** diff --git a/zt-server/src/main/resources/application.yaml b/zt-server/src/main/resources/application.yaml index 2dc6dbbb..da5fa087 100644 --- a/zt-server/src/main/resources/application.yaml +++ b/zt-server/src/main/resources/application.yaml @@ -84,7 +84,6 @@ iwork: void-workflow: /api/workflow/paService/doCancelRequest token: ttl-seconds: 3600 - refresh-ahead-seconds: 60 client: connect-timeout: 5s response-timeout: 30s From b7ad25e91c17d17a39c790485f3ee2395a337998 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 15:08:47 +0800 Subject: [PATCH 05/13] =?UTF-8?q?1.=20=E6=B8=85=E7=90=86=20iwork=20?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=9A=84=E6=97=A7=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iwork/vo/IWorkDetailRecordVO.java | 23 ------ .../iwork/vo/IWorkDetailTableVO.java | 25 ------ .../iwork/vo/IWorkFormFieldVO.java | 20 ----- .../iwork/vo/IWorkOperationRespVO.java | 42 +++++++++- .../iwork/vo/IWorkOrgQueryReqVO.java | 73 ------------------ .../iwork/vo/IWorkTokenApplyReqVO.java | 12 --- .../iwork/config/IWorkProperties.java | 5 -- .../impl/IWorkIntegrationServiceImpl.java | 76 ++----------------- .../src/main/resources/application.yaml | 1 - 9 files changed, 45 insertions(+), 232 deletions(-) delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailRecordVO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailTableVO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFormFieldVO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOrgQueryReqVO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkTokenApplyReqVO.java diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailRecordVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailRecordVO.java deleted file mode 100644 index 7a2dce2d..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailRecordVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -import java.util.List; - -/** - * 传递给 iWork 的单条明细记录。 - */ -@Data -public class IWorkDetailRecordVO { - - @Schema(description = "记录序号,从 0 开始", example = "0") - private Integer recordOrder; - - @Schema(description = "明细字段列表") - @NotEmpty(message = "明细字段不能为空") - @Valid - private List fields; -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailTableVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailTableVO.java deleted file mode 100644 index 3d17be66..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkDetailTableVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -import java.util.List; - -/** - * iWork 流程请求中的明细表定义。 - */ -@Data -public class IWorkDetailTableVO { - - @Schema(description = "表名", example = "formtable_main_26_dt1") - @NotBlank(message = "明细表名不能为空") - private String tableDBName; - - @Schema(description = "明细记录集合") - @NotEmpty(message = "明细记录不能为空") - @Valid - private List records; -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFormFieldVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFormFieldVO.java deleted file mode 100644 index c40c2f85..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFormFieldVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -/** - * 提交给 iWork 的单个表单字段。 - */ -@Data -public class IWorkFormFieldVO { - - @Schema(description = "字段名", example = "sqr") - @NotBlank(message = "字段名不能为空") - private String fieldName; - - @Schema(description = "字段值", example = "张三") - @NotBlank(message = "字段值不能为空") - private String fieldValue; -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOperationRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOperationRespVO.java index 2b01e0a6..c0b5e043 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOperationRespVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOperationRespVO.java @@ -1,5 +1,6 @@ package com.zt.plat.module.system.controller.admin.integration.iwork.vo; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -11,12 +12,49 @@ import java.util.Map; @Data public class IWorkOperationRespVO { - @Schema(description = "iWork 返回的原始数据") - private Map payload; + @Schema(description = "iWork 返回的原始数据结构") + private Payload payload; @Schema(description = "是否判断为成功") private boolean success; @Schema(description = "返回提示信息") private String message; + + @Data + public static class Payload { + + @Schema(description = "iWork 返回的业务状态码,例如 SUCCESS") + private String code; + + @Schema(description = "iWork 返回的数据体") + private PayloadData data; + + @Schema(description = "错误信息对象,通常为空对象") + private Map errMsg; + + @Schema(description = "返回失败时的详细信息") + private ReqFailMsg reqFailMsg; + } + + @Data + public static class PayloadData { + + @Schema(description = "iWork 生成的请求编号 requestid") + @JsonProperty("requestid") + private Long requestId; + } + + @Data + public static class ReqFailMsg { + + @Schema(description = "失败时的关键参数集合") + private Map keyParameters; + + @Schema(description = "失败消息对象") + private Map msgInfo; + + @Schema(description = "其他附加参数,例如 doAutoApprove") + private Map otherParams; + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOrgQueryReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOrgQueryReqVO.java deleted file mode 100644 index bd59d628..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkOrgQueryReqVO.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -import java.util.Map; - -/** - * 查询 iWork 人力组织信息所需的参数。 - */ -@Data -public class IWorkOrgQueryReqVO { - - @Schema(description = "当前页码", example = "1") - private Integer curpage; - - @Schema(description = "每页条数", example = "10") - private Integer pagesize; - - // ================= 分部查询 ================= - - @Schema(description = "分部编码") - private String subcompanyCode; - - @Schema(description = "分部名称") - private String subcompanyName; - - // ================= 部门查询 ================= - - @Schema(description = "部门编码") - private String departmentCode; - - @Schema(description = "部门名称") - private String departmentName; - - @Schema(description = "所属分部ID") - private String subcompanyId; - - // ================= 岗位查询 ================= - - @Schema(description = "岗位编码") - private String jobTitleCode; - - @Schema(description = "岗位名称") - private String jobTitleName; - - // ================= 人员查询 ================= - - @Schema(description = "人员工号") - private String workCode; - - @Schema(description = "人员姓名") - private String lastName; - - @Schema(description = "所属部门ID") - private String departmentId; - - @Schema(description = "所属岗位ID") - private String jobTitleId; - - @Schema(description = "人员状态 (0:试用, 1:正式, 2:临时, 3:试用延期, 4:解聘, 5:离职, 6:退休, 7:无效)") - private String status; - - @Schema(description = "手机号") - private String mobile; - - @Schema(description = "邮箱") - private String email; - - @Schema(description = "查询参数(扩展用),将被序列化为 params 传给 iWork") - private Map params; -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkTokenApplyReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkTokenApplyReqVO.java deleted file mode 100644 index b3422122..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkTokenApplyReqVO.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 获取 iWork 会话令牌的请求载荷。 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class IWorkTokenApplyReqVO extends IWorkBaseReqVO { -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/integration/iwork/config/IWorkProperties.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/integration/iwork/config/IWorkProperties.java index cadd8b88..9443464e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/integration/iwork/config/IWorkProperties.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/integration/iwork/config/IWorkProperties.java @@ -97,11 +97,6 @@ public class IWorkProperties { */ @Min(value = 1, message = "iWork Token 有效期必须大于 0") private long ttlSeconds; - /** - * Token 过期前提前刷新的秒数。 - */ - @Min(value = 0, message = "iWork Token 提前刷新秒数不能为负数") - private long refreshAheadSeconds; } @Data diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java index 3c6885d3..60167296 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java @@ -20,7 +20,6 @@ import com.zt.plat.module.system.framework.integration.iwork.config.IWorkPropert import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.ToString; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okio.Buffer; @@ -59,10 +58,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { private final FileApi fileApi; private final BusinessFileApi businessFileApi; - private final Cache sessionCache = Caffeine.newBuilder() - .maximumSize(256) - .build(); - private final Cache registrationCache = Caffeine.newBuilder() .maximumSize(32) .build(); @@ -104,7 +99,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { boolean force = forceRegistration || forceToken; ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, forceRegistration); - IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, force); + IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, force); IWorkAuthTokenRespVO respVO = new IWorkAuthTokenRespVO(); respVO.setAppId(appId); @@ -124,7 +119,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId()); ensureIdentifier(reqVO.getIdentifierKey(), reqVO.getIdentifierValue()); - IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); + IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); Map payload = buildUserPayload(reqVO); String responseBody = executeJsonRequest(properties.getPaths().getUserInfo(), reqVO.getQueryParams(), appId, session, payload); @@ -137,7 +132,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { String appId = resolveAppId(); ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId()); - IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); + IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); Map payload = buildCreatePayload(reqVO); String responseBody = executeFormRequest(properties.getPaths().getCreateWorkflow(), null, appId, session, payload); @@ -153,7 +148,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { if (!StringUtils.hasText(reqVO.getRequestId())) { throw ServiceExceptionUtil.exception(IWORK_USER_IDENTIFIER_MISSING); } - IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); + IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken())); Map payload = buildVoidPayload(reqVO); String responseBody = executeJsonRequest(properties.getPaths().getVoidWorkflow(), null, appId, session, payload); @@ -308,26 +303,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { } } - private IWorkSession ensureSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefresh) { - SessionKey key = new SessionKey(appId, operatorUserId); - Instant now = Instant.now(); - if (!forceRefresh) { - IWorkSession cached = sessionCache.getIfPresent(key); - if (cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) { - return cached; - } - } - synchronized (key.intern()) { - IWorkSession cached = sessionCache.getIfPresent(key); - if (!forceRefresh && cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) { - return cached; - } - IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, forceRefresh); - sessionCache.put(key, session); - return session; - } - } - private IWorkSession createSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefreshRegistration) { RegistrationState registration = obtainRegistration(appId, clientKeyPair, forceRefreshRegistration); String encryptedSecret = encryptWithPublicKey(registration.secret(), registration.spk()); @@ -651,7 +626,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { return respVO; } JsonNode node = parseJson(responseBody, IWORK_REMOTE_REQUEST_FAILED); - respVO.setPayload(objectMapper.convertValue(node, MAP_TYPE)); + respVO.setPayload(objectMapper.convertValue(node, IWorkOperationRespVO.Payload.class)); respVO.setSuccess(isSuccess(node)); respVO.setMessage(resolveMessage(node)); return respVO; @@ -992,46 +967,5 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { this.expiresAt = expiresAt; this.spk = spk; } - - private boolean isValid(Instant now, long refreshAheadSeconds) { - Instant refreshThreshold = expiresAt.minusSeconds(Math.max(0L, refreshAheadSeconds)); - return refreshThreshold.isAfter(now) && StringUtils.hasText(token) && StringUtils.hasText(encryptedUserId); - } - } - - @ToString - private static final class SessionKey { - private final String appId; - private final String operatorUserId; - - private SessionKey(String appId, String operatorUserId) { - this.appId = appId; - this.operatorUserId = operatorUserId; - } - - private String cacheKey() { - return appId + "::" + operatorUserId; - } - - private String intern() { - return cacheKey().intern(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof SessionKey that)) { - return false; - } - return Objects.equals(appId, that.appId) - && Objects.equals(operatorUserId, that.operatorUserId); - } - - @Override - public int hashCode() { - return Objects.hash(appId, operatorUserId); - } } } diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml index c027e52b..3369dc0d 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml @@ -118,7 +118,6 @@ iwork: void-workflow: /api/workflow/paService/doCancelRequest token: ttl-seconds: 3600 - refresh-ahead-seconds: 60 client: connect-timeout: 60s response-timeout: 60s From 2e0b0a5e83a7ac68e8345a339d25a2c178fbfa51 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 16:19:40 +0800 Subject: [PATCH 06/13] =?UTF-8?q?1.=20=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=AD?= =?UTF-8?q?=20databus=20=E6=A8=A1=E5=9D=97=E7=BA=BF=E7=A8=8B=E6=B1=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/config/GatewayWebClientConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java index 24811a53..96ba5a07 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java @@ -27,7 +27,7 @@ public class GatewayWebClientConfiguration { @Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize, @Value("${databus.gateway.web-client.max-idle-time:45000}") long maxIdleTimeMillis, @Value("${databus.gateway.web-client.evict-in-background-interval:20000}") long evictInBackgroundMillis, - @Value("${databus.gateway.web-client.connection-pool-enabled:true}") boolean connectionPoolEnabled) { + @Value("${databus.gateway.web-client.connection-pool-enabled:false}") boolean connectionPoolEnabled) { this.maxInMemorySize = maxInMemorySize; this.maxIdleTimeMillis = maxIdleTimeMillis; this.evictInBackgroundMillis = evictInBackgroundMillis; From e11065a5968adf5c4025a4dd88c2f610ba77f267 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 17:45:58 +0800 Subject: [PATCH 07/13] =?UTF-8?q?1.=20=E5=90=AF=E5=8A=A8=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=EF=BC=8C=E5=AE=9A=E6=97=B6=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=20databus=20api=202.=20=E4=BF=AE=E5=A4=8D=20databus=20?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=203.=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=20iwork=20=E5=9B=9E=E8=B0=83=E4=B8=9A=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../desensitize/core/DesensitizeTest.java | 2 +- .../security/GatewaySecurityFilter.java | 8 +- .../gateway/step/impl/HttpStepHandler.java | 9 +- .../gateway/ApiGatewayControllerTest.java | 16 +- .../sample/DatabusApiInvocationExample.java | 17 +- .../security/GatewaySecurityFilterTest.java | 49 ++- ...tepHandlerConnectionResetScenarioTest.java | 360 ------------------ .../gateway/ApiDefinitionServiceImplTest.java | 4 + .../api/businessfile/BusinessFileApi.java | 11 +- .../api/businessfile/BusinessFileApiImpl.java | 6 + .../businessfile/BusinessFileController.java | 9 + .../businessfile/BusinessFileMapper.java | 4 + .../businessfile/BusinessFileService.java | 8 + .../businessfile/BusinessFileServiceImpl.java | 13 + .../iwork/vo/IWorkFileCallbackReqVO.java | 6 +- .../impl/IWorkIntegrationServiceImpl.java | 33 +- .../template/TemplateServerApplication.java | 2 + .../databus/TemplateDatabusRequestLogDO.java | 126 ++++++ .../TemplateDatabusRequestLogMapper.java | 12 + .../job/databus/TemplateDatabusScheduler.java | 21 + .../databus/TemplateDatabusInvokeService.java | 304 +++++++++++++++ .../sql/template_databus_request_log.sql | 49 +++ 22 files changed, 662 insertions(+), 407 deletions(-) delete mode 100644 zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerConnectionResetScenarioTest.java create mode 100644 zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/dataobject/databus/TemplateDatabusRequestLogDO.java create mode 100644 zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/mysql/databus/TemplateDatabusRequestLogMapper.java create mode 100644 zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/job/databus/TemplateDatabusScheduler.java create mode 100644 zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/service/databus/TemplateDatabusInvokeService.java create mode 100644 zt-module-template/zt-module-template-server/src/main/resources/sql/template_databus_request_log.sql diff --git a/zt-framework/zt-spring-boot-starter-web/src/test/java/com/zt/plat/framework/desensitize/core/DesensitizeTest.java b/zt-framework/zt-spring-boot-starter-web/src/test/java/com/zt/plat/framework/desensitize/core/DesensitizeTest.java index 3b6dc26a..0176695d 100644 --- a/zt-framework/zt-spring-boot-starter-web/src/test/java/com/zt/plat/framework/desensitize/core/DesensitizeTest.java +++ b/zt-framework/zt-spring-boot-starter-web/src/test/java/com/zt/plat/framework/desensitize/core/DesensitizeTest.java @@ -48,7 +48,7 @@ public class DesensitizeTest { DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class); // 断言 assertNotNull(d); - assertEquals("芋***", d.getNickname()); + assertEquals("Z***", d.getNickname()); assertEquals("998800********31", d.getBankCard()); assertEquals("粤A6***6", d.getCarLicense()); assertEquals("0108*****22", d.getFixedPhone()); 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 b2bff142..c7b76152 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 @@ -273,10 +273,10 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { String signatureType = resolveSignatureType(credential, security); try { -// boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType); -// if (!valid) { -// throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "签名校验失败"); -// } + boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType); + if (!valid) { + throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "签名校验失败"); + } } catch (IllegalArgumentException ex) { throw new SecurityValidationException(HttpStatus.INTERNAL_SERVER_ERROR, "签名算法配置异常"); } diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java index fc271c96..58a7c7fd 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java @@ -393,8 +393,13 @@ public class HttpStepHandler implements ApiStepHandler { } private boolean supportsRequestBody(HttpMethod method) { - // 所有请求都要传递请求体 - return true; + if (method == null) { + return true; + } + return !(HttpMethod.GET.equals(method) + || HttpMethod.HEAD.equals(method) + || HttpMethod.OPTIONS.equals(method) + || HttpMethod.TRACE.equals(method)); } private Mono applyResilientRetry(Mono responseMono, ApiStepDefinition stepDefinition) { diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayControllerTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayControllerTest.java index 6a3768f4..e18f72b1 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayControllerTest.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayControllerTest.java @@ -2,8 +2,11 @@ package com.zt.plat.module.databus.controller.admin.gateway; import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO; import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService; +import com.zt.plat.module.databus.framework.integration.gateway.core.IntegrationFlowManager; import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse; 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.service.gateway.ApiAnonymousUserService; import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService; import com.zt.plat.module.databus.service.gateway.ApiDefinitionService; import org.junit.jupiter.api.Test; @@ -48,8 +51,17 @@ class ApiGatewayControllerTest { @MockBean private StringRedisTemplate stringRedisTemplate; - @MockBean - private ApiClientCredentialService apiClientCredentialService; + @MockBean + private IntegrationFlowManager integrationFlowManager; + + @MockBean + private ApiClientCredentialService apiClientCredentialService; + + @MockBean + private ApiAnonymousUserService apiAnonymousUserService; + + @MockBean + private ApiGatewayAccessLogger apiGatewayAccessLogger; @Test void invokeShouldReturnGatewayEnvelope() throws Exception { diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java index 6189626d..d964863a 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java @@ -28,16 +28,10 @@ import java.util.UUID; public final class DatabusApiInvocationExample { public static final String TIMESTAMP = Long.toString(System.currentTimeMillis()); -// private static final String APP_ID = "ztmy"; -// private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ="; -// private static final String APP_ID = "test"; -// private static final String APP_SECRET = "RSYtKXrXPLMy3oeh0cOro6QCioRUgqfnKCkDkNq78sI="; - private static final String APP_ID = "testAnnoy"; - private static final String APP_SECRET = "jyGCymUjCFL2i3a4Tm3qBIkUrUl4ZgKPYvOU/47ZWcM="; + private static final String APP_ID = "ztmy"; + private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ="; private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES; -// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/lgstOpenApi/v1"; - private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/test/1"; -// private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/lgstOpenApi/v1"; + private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1"; private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); @@ -88,8 +82,11 @@ public final class DatabusApiInvocationExample { Map queryParams = new LinkedHashMap<>(); long extraTimestamp = 1761556157185L; +// String bodyJson = String.format(""" +// {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"} +// """, extraTimestamp); String bodyJson = String.format(""" - {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"} + {} """, extraTimestamp); Map bodyParams = parseBodyJson(bodyJson); diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilterTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilterTest.java index 62e34af3..9add6954 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilterTest.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilterTest.java @@ -9,6 +9,7 @@ import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; 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.service.gateway.ApiAnonymousUserService; import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService; import jakarta.servlet.http.HttpServletRequest; @@ -25,7 +26,9 @@ import org.springframework.security.core.context.SecurityContextHolder; import java.time.Duration; import java.util.Collections; +import java.util.Map; import java.util.Optional; +import java.util.TreeMap; import java.util.UUID; import static com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties.*; @@ -47,11 +50,12 @@ class GatewaySecurityFilterTest { ApiGatewayProperties properties = createProperties(); properties.getSecurity().setEnabled(false); StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class); - ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); - ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); - when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty()); + ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); + ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty()); - GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper()); + ApiGatewayAccessLogger accessLogger = mock(ApiGatewayAccessLogger.class); + when(accessLogger.logEntrance(any())).thenReturn(1L); + GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper(), accessLogger); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1"); request.setRemoteAddr("127.0.0.1"); @@ -74,7 +78,9 @@ class GatewaySecurityFilterTest { ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty()); - GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper()); + ApiGatewayAccessLogger accessLogger = mock(ApiGatewayAccessLogger.class); + when(accessLogger.logEntrance(any())).thenReturn(1L); + GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper(), accessLogger); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1"); request.setRemoteAddr("10.0.0.1"); @@ -97,6 +103,8 @@ class GatewaySecurityFilterTest { ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); + ApiGatewayAccessLogger accessLogger = mock(ApiGatewayAccessLogger.class); + when(accessLogger.logEntrance(any())).thenReturn(1L); when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty()); ApiClientCredentialDO credential = new ApiClientCredentialDO(); credential.setAppId("demo-app"); @@ -108,13 +116,13 @@ class GatewaySecurityFilterTest { properties.getSecurity().setRequireBodyEncryption(false); properties.getSecurity().setEncryptResponse(false); - GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper()); + GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper(), accessLogger); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1"); request.setRemoteAddr("127.0.0.1"); long timestamp = System.currentTimeMillis(); String nonce = UUID.randomUUID().toString().replaceAll("-", ""); - String signature = signatureForApp("demo-app"); + String signature = signatureForApp("demo-app", timestamp); request.addHeader(APP_ID_HEADER, "demo-app"); request.addHeader(TIMESTAMP_HEADER, String.valueOf(timestamp)); request.addHeader(NONCE_HEADER, nonce); @@ -142,13 +150,15 @@ class GatewaySecurityFilterTest { ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); + ApiGatewayAccessLogger accessLogger = mock(ApiGatewayAccessLogger.class); + when(accessLogger.logEntrance(any())).thenReturn(1L); ApiClientCredentialDO credential = new ApiClientCredentialDO(); credential.setAppId("demo-app"); credential.setEncryptionKey("demo-secret-key"); credential.setEncryptionType(CryptoSignatureUtils.ENCRYPT_TYPE_AES); when(credentialService.findActiveCredential("demo-app")).thenReturn(Optional.of(credential)); - GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper()); + GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper(), accessLogger); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1"); request.setRemoteAddr("127.0.0.1"); @@ -183,6 +193,8 @@ class GatewaySecurityFilterTest { ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class); ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class); + ApiGatewayAccessLogger accessLogger = mock(ApiGatewayAccessLogger.class); + when(accessLogger.logEntrance(any())).thenReturn(1L); ApiClientCredentialDO credential = new ApiClientCredentialDO(); credential.setAppId("demo-app"); credential.setSignatureType(null); @@ -200,7 +212,7 @@ class GatewaySecurityFilterTest { when(anonymousUserService.find(99L)).thenReturn(Optional.of(details)); when(anonymousUserService.issueAccessToken(details)).thenReturn(Optional.of("mock-token")); - GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper()); + GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper(), accessLogger); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1"); request.setRemoteAddr("127.0.0.1"); @@ -209,7 +221,7 @@ class GatewaySecurityFilterTest { request.addHeader(APP_ID_HEADER, "demo-app"); request.addHeader(TIMESTAMP_HEADER, String.valueOf(timestamp)); request.addHeader(NONCE_HEADER, nonce); - request.addHeader(SIGNATURE_HEADER, signatureForApp("demo-app")); + request.addHeader(SIGNATURE_HEADER, signatureForApp("demo-app", timestamp)); MockHttpServletResponse response = new MockHttpServletResponse(); MockFilterChain chain = new MockFilterChain(); @@ -231,7 +243,20 @@ class GatewaySecurityFilterTest { return properties; } - private String signatureForApp(String appId) { - return SecureUtil.md5("appId=" + appId); + private String signatureForApp(String appId, long timestamp) { + Map payload = new TreeMap<>(); + payload.put(APP_ID_HEADER, appId); + payload.put(TIMESTAMP_HEADER, String.valueOf(timestamp)); + StringBuilder sb = new StringBuilder(); + payload.forEach((key, value) -> { + if (value == null) { + return; + } + if (sb.length() > 0) { + sb.append('&'); + } + sb.append(key).append('=').append(value); + }); + return SecureUtil.md5(sb.toString()); } } diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerConnectionResetScenarioTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerConnectionResetScenarioTest.java deleted file mode 100644 index 854c7122..00000000 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerConnectionResetScenarioTest.java +++ /dev/null @@ -1,360 +0,0 @@ -package com.zt.plat.module.databus.framework.integration.gateway.step.impl; - -import com.zt.plat.framework.common.exception.ServiceException; -import org.junit.jupiter.api.Test; -import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.PrematureCloseException; -import reactor.netty.resources.ConnectionProvider; -import reactor.util.retry.Retry; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Demonstrates the stale-connection scenario using the legacy vs the deferred retry pipeline. - */ -class HttpStepHandlerConnectionResetScenarioTest { - - private static final Duration RETRY_DELAY = Duration.ofMillis(200); - private static final int RETRY_ATTEMPTS = 3; - private static final Duration BLOCK_TIMEOUT = Duration.ofSeconds(5); - private static final Duration RESET_WAIT = Duration.ofMillis(300); - - @Test - void legacyPipelineLosesSuccessfulRetry() throws Exception { - try (ResetOnceHttpServer server = new ResetOnceHttpServer()) { - WebClient webClient = createWebClient(); - URI uri = server.uri("/demo"); - - warmUp(server, webClient, uri); - server.awaitWarmupConnectionReset(RESET_WAIT); - - legacyInvoke(webClient, uri, Map.of("mode", "legacy")); - - server.awaitFreshResponses(1, Duration.ofSeconds(2)); - assertThat(server.getFreshResponseCount()).isEqualTo(1); - assertThat(server.getServedBodies()).contains("reset", "fresh"); - } - } - - @Test - void deferredPipelinePropagatesSuccessfulRetry() throws Exception { - try (ResetOnceHttpServer server = new ResetOnceHttpServer()) { - WebClient webClient = createWebClient(); - URI uri = server.uri("/demo"); - - warmUp(server, webClient, uri); - server.awaitWarmupConnectionReset(RESET_WAIT); - - Object result = deferredInvoke(webClient, uri, Map.of("mode", "defer")); - assertThat(result).isInstanceOf(Map.class); - Map resultMap = (Map) result; - assertThat(resultMap.get("stage")).isEqualTo("fresh"); - - server.awaitFreshResponses(1, Duration.ofSeconds(2)); - assertThat(server.getFreshResponseCount()).isEqualTo(1); - assertThat(server.getServedBodies()).contains("reset", "fresh"); - } - } - - private WebClient createWebClient() { - ConnectionProvider provider = ConnectionProvider.builder("http-step-handler-demo") - .maxConnections(1) - .pendingAcquireMaxCount(-1) - .maxIdleTime(Duration.ofSeconds(5)) - .build(); - HttpClient httpClient = HttpClient.create(provider).compress(true); - return WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .build(); - } - - private void warmUp(ResetOnceHttpServer server, WebClient webClient, URI uri) { - webClient.post() - .uri(uri) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .bodyValue(Map.of("warm", true)) - .retrieve() - .bodyToMono(Object.class) - .block(BLOCK_TIMEOUT); - server.awaitWarmupResponse(Duration.ofSeconds(2)); - } - - private Object legacyInvoke(WebClient webClient, URI uri, Object body) { - WebClient.RequestHeadersSpec spec = webClient.post() - .uri(uri) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .bodyValue(body); - Mono responseMono = spec.retrieve() - .bodyToMono(Object.class) - // 模拟业务中首次订阅后缓存失败结果的场景 - .cache(); - return responseMono.retryWhen(Retry.fixedDelay(RETRY_ATTEMPTS, RETRY_DELAY) - .filter(this::isRetryableException) - .onRetryExhaustedThrow((specification, signal) -> signal.failure())) - .block(BLOCK_TIMEOUT); - } - - private Object deferredInvoke(WebClient webClient, URI uri, Object body) { - Mono responseMono = Mono.defer(() -> webClient.post() - .uri(uri) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .bodyValue(body) - .retrieve() - .bodyToMono(Object.class) - // 通过 defer,每次重试都会重新创建带缓存的响应 Mono - .cache()); - return responseMono.retryWhen(Retry.fixedDelay(RETRY_ATTEMPTS, RETRY_DELAY) - .filter(this::isRetryableException)) - .block(BLOCK_TIMEOUT); - } - - private boolean isRetryableException(Throwable throwable) { - if (throwable == null) { - return false; - } - Throwable cursor = throwable; - while (cursor != null) { - if (cursor instanceof ServiceException) { - return false; - } - if (cursor instanceof PrematureCloseException) { - return true; - } - if (cursor instanceof IOException) { - return true; - } - cursor = cursor.getCause(); - } - return false; - } - - private static final class ResetOnceHttpServer implements AutoCloseable { - - private static final Duration RESET_DELAY = Duration.ofMillis(250); - - private final ServerSocket serverSocket; - private final ExecutorService acceptExecutor; - private final ScheduledExecutorService scheduler; - private final AtomicInteger connectionCount = new AtomicInteger(); - private final AtomicInteger freshResponses = new AtomicInteger(); - private final CountDownLatch warmupResponseSent = new CountDownLatch(1); - private final CountDownLatch warmupReset = new CountDownLatch(1); - private final List servedBodies = new CopyOnWriteArrayList<>(); - private volatile boolean running = true; - private volatile Socket warmupSocket; - - ResetOnceHttpServer() throws IOException { - this.serverSocket = new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1")); - this.serverSocket.setReuseAddress(true); - this.acceptExecutor = Executors.newSingleThreadExecutor(r -> { - Thread t = new Thread(r, "reset-once-http-accept"); - t.setDaemon(true); - return t; - }); - this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { - Thread t = new Thread(r, "reset-once-http-scheduler"); - t.setDaemon(true); - return t; - }); - acceptExecutor.submit(this::acceptLoop); - } - - URI uri(String path) { - Objects.requireNonNull(path, "path"); - if (!path.startsWith("/")) { - path = "/" + path; - } - return URI.create("http://127.0.0.1:" + serverSocket.getLocalPort() + path); - } - - List getServedBodies() { - return new ArrayList<>(servedBodies); - } - - int getFreshResponseCount() { - return freshResponses.get(); - } - - void awaitWarmupResponse(Duration timeout) { - awaitLatch(warmupResponseSent, timeout); - } - - void awaitWarmupConnectionReset(Duration timeout) { - awaitLatch(warmupReset, timeout); - } - - void awaitFreshResponses(int expected, Duration timeout) { - long deadline = System.nanoTime() + timeout.toNanos(); - while (freshResponses.get() < expected && System.nanoTime() < deadline) { - try { - Thread.sleep(10); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - return; - } - } - } - - private void awaitLatch(CountDownLatch latch, Duration timeout) { - try { - if (!latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) { - throw new IllegalStateException("Timed out waiting for latch"); - } - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new IllegalStateException(ex); - } - } - - private void acceptLoop() { - try { - while (running) { - Socket socket = serverSocket.accept(); - int index = connectionCount.incrementAndGet(); - handle(socket, index); - } - } catch (SocketException ex) { - if (running) { - throw new IllegalStateException("Unexpected server socket error", ex); - } - } catch (IOException ex) { - if (running) { - throw new IllegalStateException("I/O error in server", ex); - } - } - } - - private void handle(Socket socket, int index) { - try { - socket.setTcpNoDelay(true); - RequestMetadata metadata = readRequest(socket); - if (index == 1) { - warmupSocket = socket; - String body = "{\"stage\":\"warmup\",\"path\":\"" + metadata.path + "\"}"; - writeResponse(socket, body, true); - servedBodies.add("warmup"); - warmupResponseSent.countDown(); - scheduler.schedule(() -> forceReset(socket), RESET_DELAY.toMillis(), TimeUnit.MILLISECONDS); - } else if (index == 2) { - // 模拟客户端复用到仍在连接池中的旧连接,但服务端已在请求到达后立即复位。 - servedBodies.add("reset"); - scheduler.schedule(() -> closeWithReset(socket), 10, TimeUnit.MILLISECONDS); - } else { - String body = "{\"stage\":\"fresh\",\"attempt\":" + index + "}"; - writeResponse(socket, body, false); - servedBodies.add("fresh"); - freshResponses.incrementAndGet(); - socket.close(); - } - } catch (IOException ex) { - // ignore for the purpose of the test - } - } - - private void forceReset(Socket socket) { - try { - if (!socket.isClosed()) { - servedBodies.add("reset"); - closeWithReset(socket); - } - } finally { - warmupReset.countDown(); - } - } - - private void closeWithReset(Socket socket) { - try { - if (!socket.isClosed()) { - socket.setSoLinger(true, 0); - socket.close(); - } - } catch (IOException ignored) { - // ignore - } - } - - private RequestMetadata readRequest(Socket socket) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII)); - String requestLine = reader.readLine(); - if (requestLine == null) { - return new RequestMetadata("unknown", 0); - } - String path = requestLine.split(" ", 3)[1]; - int contentLength = 0; - String line; - while ((line = reader.readLine()) != null && !line.isEmpty()) { - if (line.toLowerCase(Locale.ROOT).startsWith("content-length:")) { - contentLength = Integer.parseInt(line.substring(line.indexOf(':') + 1).trim()); - } - } - if (contentLength > 0) { - char[] buffer = new char[contentLength]; - int read = 0; - while (read < contentLength) { - int r = reader.read(buffer, read, contentLength - read); - if (r < 0) { - break; - } - read += r; - } - } - return new RequestMetadata(path, contentLength); - } - - private void writeResponse(Socket socket, String body, boolean keepAlive) throws IOException { - byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); - StringBuilder builder = new StringBuilder() - .append("HTTP/1.1 200 OK\r\n") - .append("Content-Type: application/json\r\n") - .append("Content-Length: ").append(bodyBytes.length).append("\r\n"); - if (keepAlive) { - builder.append("Connection: keep-alive\r\n"); - } else { - builder.append("Connection: close\r\n"); - } - builder.append("\r\n"); - OutputStream outputStream = socket.getOutputStream(); - outputStream.write(builder.toString().getBytes(StandardCharsets.US_ASCII)); - outputStream.write(bodyBytes); - outputStream.flush(); - } - - @Override - public void close() throws Exception { - running = false; - try { - serverSocket.close(); - } catch (IOException ignored) { - } - if (warmupSocket != null && !warmupSocket.isClosed()) { - try { - warmupSocket.close(); - } catch (IOException ignored) { - } - } - scheduler.shutdownNow(); - acceptExecutor.shutdownNow(); - } - - private record RequestMetadata(String path, int contentLength) { - } - } -} diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java index 004baf8f..fc521e7c 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java @@ -16,6 +16,7 @@ import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper; import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper; import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper; import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum; +import com.zt.plat.module.databus.service.gateway.ApiVersionService; import com.zt.plat.module.databus.service.gateway.impl.ApiDefinitionServiceImpl; import jakarta.annotation.Resource; import org.junit.jupiter.api.AfterEach; @@ -63,6 +64,9 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest { @MockBean private StringRedisTemplate stringRedisTemplate; + @MockBean + private ApiVersionService apiVersionService; + @TestConfiguration static class JacksonTestConfiguration { diff --git a/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApi.java b/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApi.java index ded1090b..67919fe0 100644 --- a/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApi.java +++ b/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApi.java @@ -6,7 +6,7 @@ import com.zt.plat.module.infra.api.businessfile.dto.BusinessFilePageReqDTO; import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileRespDTO; import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileSaveReqDTO; import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileWithUrlRespDTO; -import com.zt.plat.module.infra.enums.ApiConstants; +import com.zt.plat.framework.common.enums.RpcConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,11 +19,11 @@ import java.util.List; /** * @author chenbowen */ -@FeignClient(name = ApiConstants.NAME) +@FeignClient(name = RpcConstants.INFRA_NAME) @Tag(name = "RPC 服务 - 业务附件关联") public interface BusinessFileApi { - String PREFIX = ApiConstants.PREFIX + "/business-file"; + String PREFIX = RpcConstants.INFRA_PREFIX + "/business-file"; @PostMapping(PREFIX + "/create") @Operation(summary = "创建业务附件关联") @@ -52,6 +52,11 @@ public interface BusinessFileApi { @Parameter(name = "id", description = "编号", required = true) CommonResult getBusinessFile(@RequestParam("id") Long id); + @GetMapping(PREFIX + "/get-by-code") + @Operation(summary = "根据业务编码获得业务附件关联") + @Parameter(name = "businessCode", description = "业务编码", required = true) + CommonResult getBusinessFileByBusinessCode(@RequestParam("businessCode") String businessCode); + @PostMapping(PREFIX + "/page") @Operation(summary = "获得业务附件关联分页") CommonResult> getBusinessFilePage(@RequestBody BusinessFilePageReqDTO pageReqDTO); diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApiImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApiImpl.java index 5731f200..3ebca19d 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApiImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/api/businessfile/BusinessFileApiImpl.java @@ -67,6 +67,12 @@ public class BusinessFileApiImpl implements BusinessFileApi { return success(BeanUtils.toBean(businessFile, BusinessFileRespDTO.class)); } + @Override + public CommonResult getBusinessFileByBusinessCode(String businessCode) { + BusinessFileDO businessFile = businessFileService.getBusinessFileByBusinessCode(businessCode); + return success(BeanUtils.toBean(businessFile, BusinessFileRespDTO.class)); + } + @Override public CommonResult> getBusinessFilePage(BusinessFilePageReqDTO pageReqDTO) { PageResult pageResult = businessFileService.getBusinessFilePage(BeanUtils.toBean(pageReqDTO, BusinessFilePageReqVO.class)); diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/BusinessFileController.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/BusinessFileController.java index 8c4f8eaa..2d143fb8 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/BusinessFileController.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/BusinessFileController.java @@ -90,6 +90,15 @@ public class BusinessFileController { return success(BeanUtils.toBean(businessFile, BusinessFileRespVO.class)); } + @GetMapping("/get-by-code") + @Operation(summary = "根据业务编码获得业务附件关联") + @Parameter(name = "businessCode", description = "业务编码", required = true) + @PreAuthorize("@ss.hasPermission('infra:business-file:query')") + public CommonResult getBusinessFileByBusinessCode(@RequestParam("businessCode") String businessCode) { + BusinessFileDO businessFile = businessFileService.getBusinessFileByBusinessCode(businessCode); + return success(BeanUtils.toBean(businessFile, BusinessFileRespVO.class)); + } + @GetMapping("/page") @Operation(summary = "获得业务附件关联分页") @PreAuthorize("@ss.hasPermission('infra:business-file:query')") diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/mysql/businessfile/BusinessFileMapper.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/mysql/businessfile/BusinessFileMapper.java index 35b67b26..5e886088 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/mysql/businessfile/BusinessFileMapper.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/mysql/businessfile/BusinessFileMapper.java @@ -29,4 +29,8 @@ public interface BusinessFileMapper extends BaseMapperX { .orderByDesc(BusinessFileDO::getId)); } + default BusinessFileDO selectByBusinessCode(String businessCode) { + return selectFirstOne(BusinessFileDO::getBusinessCode, businessCode); + } + } \ No newline at end of file diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileService.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileService.java index c9b79f35..a7298eef 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileService.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileService.java @@ -53,6 +53,14 @@ public interface BusinessFileService { */ BusinessFileDO getBusinessFile(Long id); + /** + * 根据业务编码获得业务附件关联 + * + * @param businessCode 业务编码 + * @return 业务附件关联 + */ + BusinessFileDO getBusinessFileByBusinessCode(String businessCode); + /** * 获得业务附件关联分页 * diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java index eb482468..4a30d7fa 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java @@ -18,6 +18,7 @@ import com.zt.plat.module.system.api.user.AdminUserApi; import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import java.util.*; @@ -99,6 +100,18 @@ public class BusinessFileServiceImpl implements BusinessFileService { return businessFileMapper.selectById(id); } + @Override + public BusinessFileDO getBusinessFileByBusinessCode(String businessCode) { + if (!StringUtils.hasText(businessCode)) { + throw exception(BUSINESS_FILE_NOT_EXISTS); + } + BusinessFileDO businessFile = businessFileMapper.selectByBusinessCode(businessCode.trim()); + if (businessFile == null) { + throw exception(BUSINESS_FILE_NOT_EXISTS); + } + return businessFile; + } + @Override public PageResult getBusinessFilePage(BusinessFilePageReqVO pageReqVO) { return businessFileMapper.selectPage(pageReqVO); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFileCallbackReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFileCallbackReqVO.java index 9a7dc009..58519d84 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFileCallbackReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkFileCallbackReqVO.java @@ -12,9 +12,9 @@ public class IWorkFileCallbackReqVO { @NotBlank(message = "文件 URL 不能为空") private String fileUrl; - @Schema(description = "业务 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") - @NotBlank(message = "业务 ID 不能为空") - private String businessId; + @Schema(description = "业务编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "DJ-2025-0001") + @NotBlank(message = "业务编码不能为空") + private String businessCode; @Schema(description = "文件名称,可选", example = "合同附件.pdf") private String fileName; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java index 60167296..d1d7108a 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java @@ -11,6 +11,7 @@ import com.zt.plat.framework.common.exception.ServiceException; import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.module.infra.api.businessfile.BusinessFileApi; +import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileRespDTO; import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileSaveReqDTO; import com.zt.plat.module.infra.api.file.FileApi; import com.zt.plat.module.infra.api.file.dto.FileCreateReqDTO; @@ -161,20 +162,16 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "回调请求不能为空"); } String fileUrl = Optional.ofNullable(reqVO.getFileUrl()).map(String::trim).orElse(""); - String businessIdStr = Optional.ofNullable(reqVO.getBusinessId()).map(String::trim).orElse(""); + String businessCode = Optional.ofNullable(reqVO.getBusinessCode()).map(String::trim).orElse(""); if (!StringUtils.hasText(fileUrl)) { throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "文件 URL 不能为空"); } - if (!StringUtils.hasText(businessIdStr)) { - throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务 ID 不能为空"); + if (!StringUtils.hasText(businessCode)) { + throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务编码不能为空"); } - Long businessId; - try { - businessId = Long.valueOf(businessIdStr); - } catch (NumberFormatException ex) { - throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务 ID 必须是数字: " + businessIdStr); - } + BusinessFileRespDTO referenceBusinessFile = loadBusinessFileByBusinessCode(businessCode); + Long businessId = referenceBusinessFile.getBusinessId(); // 通过文件 API 创建文件 FileCreateReqDTO fileCreateReqDTO = new FileCreateReqDTO(); @@ -191,7 +188,8 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { Long fileId = fileResult.getData().getId(); BusinessFileSaveReqDTO businessReq = BusinessFileSaveReqDTO.builder() - .businessId(businessId) + .businessId(businessId) + .businessCode(referenceBusinessFile.getBusinessCode()) .fileId(fileId) .fileName(fileResult.getData().getName()) .source("iwork") @@ -204,6 +202,21 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { return bizResult.getData(); } + private BusinessFileRespDTO loadBusinessFileByBusinessCode(String businessCode) { + CommonResult businessResult = businessFileApi.getBusinessFileByBusinessCode(businessCode); + if (businessResult == null || !businessResult.isSuccess() || businessResult.getData() == null) { + throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), + "根据业务编码获取业务附件关联失败: " + Optional.ofNullable(businessResult) + .map(CommonResult::getMsg) + .orElse("未知错误")); + } + BusinessFileRespDTO businessFile = businessResult.getData(); + if (businessFile.getBusinessId() == null) { + throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务编码未绑定业务 ID: " + businessCode); + } + return businessFile; + } + private byte[] downloadFileBytes(String fileUrl) { OkHttpClient client = okHttpClient(); Request request = new Request.Builder().url(fileUrl).get().build(); diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java index 435beee6..cbbe927a 100644 --- a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java @@ -2,6 +2,7 @@ package com.zt.plat.module.template; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 项目的启动类 @@ -9,6 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * @author 周迪 */ @SpringBootApplication +@EnableScheduling public class TemplateServerApplication { public static void main(String[] args) { diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/dataobject/databus/TemplateDatabusRequestLogDO.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/dataobject/databus/TemplateDatabusRequestLogDO.java new file mode 100644 index 00000000..14427fe7 --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/dataobject/databus/TemplateDatabusRequestLogDO.java @@ -0,0 +1,126 @@ +package com.zt.plat.module.template.dal.dataobject.databus; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Databus 请求与响应日志表。 + */ +@TableName("template_databus_request_log") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TemplateDatabusRequestLogDO extends BaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 请求唯一标识。 + */ + @TableField("REQUEST_ID") + private String requestId; + + /** + * 实际请求 URL。 + */ + @TableField("TARGET_URL") + private String targetUrl; + + /** + * HTTP 方法。 + */ + @TableField("HTTP_METHOD") + private String httpMethod; + + /** + * Query 参数 JSON。 + */ + @TableField("QUERY_PARAMS") + private String queryParams; + + /** + * 请求头 JSON。 + */ + @TableField("REQUEST_HEADERS") + private String requestHeaders; + + /** + * 原始请求体。 + */ + @TableField("REQUEST_BODY") + private String requestBody; + + /** + * 加密后的请求体。 + */ + @TableField("ENCRYPTED_REQUEST_BODY") + private String encryptedRequestBody; + + /** + * 签名。 + */ + @TableField("SIGNATURE") + private String signature; + + /** + * 随机串。 + */ + @TableField("NONCE") + private String nonce; + + /** + * 时间戳。 + */ + @TableField("ZT_TIMESTAMP") + private String ztTimestamp; + + /** + * HTTP 返回状态码。 + */ + @TableField("RESPONSE_STATUS") + private Integer responseStatus; + + /** + * 加密响应体。 + */ + @TableField("ENCRYPTED_RESPONSE_BODY") + private String encryptedResponseBody; + + /** + * 解密后的响应体。 + */ + @TableField("RESPONSE_BODY") + private String responseBody; + + /** + * 是否调用成功。 + */ + @TableField("SUCCESS") + private Boolean success; + + /** + * 错误信息。 + */ + @TableField("ERROR_MESSAGE") + private String errorMessage; + + /** + * 调用耗时(毫秒)。 + */ + @TableField("DURATION_MS") + private Long durationMs; + +} diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/mysql/databus/TemplateDatabusRequestLogMapper.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/mysql/databus/TemplateDatabusRequestLogMapper.java new file mode 100644 index 00000000..d4182a24 --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/dal/mysql/databus/TemplateDatabusRequestLogMapper.java @@ -0,0 +1,12 @@ +package com.zt.plat.module.template.dal.mysql.databus; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.module.template.dal.dataobject.databus.TemplateDatabusRequestLogDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * Databus 请求日志 Mapper。 + */ +@Mapper +public interface TemplateDatabusRequestLogMapper extends BaseMapperX { +} diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/job/databus/TemplateDatabusScheduler.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/job/databus/TemplateDatabusScheduler.java new file mode 100644 index 00000000..fb530505 --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/job/databus/TemplateDatabusScheduler.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.template.job.databus; + +import com.zt.plat.module.template.service.databus.TemplateDatabusInvokeService; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 基于 Spring 的 Databus 调度任务。 + */ +@Component +@RequiredArgsConstructor +public class TemplateDatabusScheduler { + + private final TemplateDatabusInvokeService invokeService; + + @Scheduled(cron = "0 0/10 * * * ?") + public void execute() { + invokeService.invokeAndRecord(); + } +} diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/service/databus/TemplateDatabusInvokeService.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/service/databus/TemplateDatabusInvokeService.java new file mode 100644 index 00000000..d0be0ddd --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/service/databus/TemplateDatabusInvokeService.java @@ -0,0 +1,304 @@ +package com.zt.plat.module.template.service.databus; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import com.zt.plat.module.template.dal.dataobject.databus.TemplateDatabusRequestLogDO; +import com.zt.plat.module.template.dal.mysql.databus.TemplateDatabusRequestLogMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * 调用 Databus 接口并记录日志。 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class TemplateDatabusInvokeService { + + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final String HEADER_ZT_APP_ID = "ZT-App-Id"; + private static final String HEADER_ZT_TIMESTAMP = "ZT-Timestamp"; + private static final String HEADER_ZT_NONCE = "ZT-Nonce"; + private static final String HEADER_ZT_SIGNATURE = "ZT-Signature"; + private static final String HEADER_ZT_AUTH_TOKEN = "ZT-Auth-Token"; + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + private static final String TARGET_URL = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1"; + private static final String APP_ID = "ztmy"; + private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ="; + private static final String AUTH_TOKEN = "a5d7cf609c0b47038ea405c660726ee9"; + private static final String DEFAULT_HTTP_METHOD = "POST"; + private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES; + private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5); + private static final Duration READ_TIMEOUT = Duration.ofSeconds(10); + private static final Map BASE_QUERY_PARAMS = Map.of( + "businessCode", "11", + "fileId", "11" + ); + private static final Map EXTRA_HEADERS = Map.of(); + private static final String DEFAULT_REQUEST_BODY = """ + { + } + """; + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(CONNECT_TIMEOUT) + .build(); + + private final TemplateDatabusRequestLogMapper requestLogMapper; + private final ObjectMapper objectMapper; + + @TenantIgnore + public void invokeAndRecord() { + TemplateDatabusRequestLogDO logDO = TemplateDatabusRequestLogDO.builder() + .requestId(generateRequestId()) + .httpMethod(DEFAULT_HTTP_METHOD) + .targetUrl(TARGET_URL) + .success(Boolean.FALSE) + .build(); + Instant start = Instant.now(); + try { + Map queryParams = buildQueryParams(); + Map bodyParams = buildBodyParams(logDO.getRequestId()); + String requestBody = toJson(bodyParams); + String serializedQuery = toJson(queryParams); + logDO.setRequestBody(requestBody); + logDO.setQueryParams(serializedQuery); + + String timestamp = Long.toString(System.currentTimeMillis()); + logDO.setZtTimestamp(timestamp); + String nonce = generateNonce(); + logDO.setNonce(nonce); + String signature = generateSignature(queryParams, bodyParams, timestamp); + logDO.setSignature(signature); + + String encryptedBody = encryptPayload(requestBody); + logDO.setEncryptedRequestBody(encryptedBody); + + URI uri = buildUri(TARGET_URL, queryParams); + logDO.setTargetUrl(uri.toString()); + Map headers = buildHeaders(nonce, timestamp, signature); + logDO.setRequestHeaders(toJson(headers)); + + HttpRequest request = buildHttpRequest(uri, headers, encryptedBody); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + logDO.setResponseStatus(response.statusCode()); + logDO.setEncryptedResponseBody(response.body()); + logDO.setResponseBody(tryDecrypt(response.body())); + boolean success = response.statusCode() >= 200 && response.statusCode() < 300; + logDO.setSuccess(success); + if (!success) { + log.warn("Databus API 返回非 2xx 状态码: {}", response.statusCode()); + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + logDO.setErrorMessage(truncate("Interrupted: " + ex.getMessage(), 1000)); + log.warn("Databus 调度被中断: {}", ex.getMessage()); + } catch (Exception ex) { + logDO.setErrorMessage(truncate(ex.getMessage(), 1000)); + log.error("Databus 调度执行异常", ex); + } finally { + logDO.setDurationMs(Duration.between(start, Instant.now()).toMillis()); + requestLogMapper.insert(logDO); + } + } + + private Map buildQueryParams() { + Map params = new LinkedHashMap<>(); + BASE_QUERY_PARAMS.forEach(params::put); + return params; + } + + private Map buildBodyParams(String requestId) { + Map body = new LinkedHashMap<>(parseTemplateBody()); + body.put("__requestId__", requestId); + body.put("datetime", DATETIME_FORMATTER.format(LocalDateTime.now())); + body.putIfAbsent("system", "TEMPLATE"); + return body; + } + + private Map parseTemplateBody() { + try { + return objectMapper.readValue(DEFAULT_REQUEST_BODY, MAP_TYPE); + } catch (JsonProcessingException ex) { + throw new IllegalStateException("内置 Databus 请求体 JSON 解析失败", ex); + } + } + + private Map buildHeaders(String nonce, String timestamp, String signature) { + Map headers = new LinkedHashMap<>(); + headers.put(HEADER_ZT_APP_ID, APP_ID); + headers.put(HEADER_ZT_TIMESTAMP, timestamp); + headers.put(HEADER_ZT_NONCE, nonce); + headers.put(HEADER_ZT_SIGNATURE, signature); + if (StringUtils.hasText(AUTH_TOKEN)) { + headers.put(HEADER_ZT_AUTH_TOKEN, AUTH_TOKEN); + } + headers.put(HEADER_CONTENT_TYPE, "application/json"); + EXTRA_HEADERS.forEach((key, value) -> { + if (StringUtils.hasText(key) && value != null) { + headers.put(key, value); + } + }); + return headers; + } + + private HttpRequest buildHttpRequest(URI uri, Map headers, String encryptedBody) { + HttpRequest.Builder builder = HttpRequest.newBuilder(uri) + .timeout(READ_TIMEOUT); + headers.forEach(builder::header); + HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.ofString(encryptedBody, StandardCharsets.UTF_8); + switch (DEFAULT_HTTP_METHOD) { + case "GET": + builder.GET(); + break; + case "PUT": + builder.PUT(publisher); + break; + case "PATCH": + builder.method("PATCH", publisher); + break; + case "DELETE": + builder.method("DELETE", publisher); + break; + default: + builder.POST(publisher); + } + return builder.build(); + } + + private String encryptPayload(String plaintext) { + try { + return CryptoSignatureUtils.encrypt(plaintext, APP_SECRET, ENCRYPTION_TYPE); + } catch (Exception ex) { + throw new IllegalStateException("请求体加密失败", ex); + } + } + + private String tryDecrypt(String cipherText) { + if (!StringUtils.hasText(cipherText)) { + return cipherText; + } + try { + return CryptoSignatureUtils.decrypt(cipherText, APP_SECRET, ENCRYPTION_TYPE); + } catch (Exception ex) { + return " " + ex.getMessage(); + } + } + + private URI buildUri(String baseUrl, Map queryParams) { + if (queryParams.isEmpty()) { + return URI.create(baseUrl); + } + StringBuilder builder = new StringBuilder(baseUrl); + builder.append(baseUrl.contains("?") ? '&' : '?'); + boolean first = true; + for (Map.Entry entry : queryParams.entrySet()) { + if (!first) { + builder.append('&'); + } + first = false; + builder.append(encode(entry.getKey())) + .append('=') + .append(encode(Objects.toString(entry.getValue(), ""))); + } + return URI.create(builder.toString()); + } + + private String generateSignature(Map queryParams, Map bodyParams, String timestamp) { + TreeMap sorted = new TreeMap<>(); + queryParams.forEach((key, value) -> sorted.put(key, normalizeValue(value))); + bodyParams.forEach((key, value) -> sorted.put(key, normalizeValue(value))); + sorted.put(HEADER_ZT_APP_ID, APP_ID); + sorted.put(HEADER_ZT_TIMESTAMP, timestamp); + StringBuilder canonical = new StringBuilder(); + sorted.forEach((key, value) -> { + if (value == null) { + return; + } + if (canonical.length() > 0) { + canonical.append('&'); + } + canonical.append(key).append('=').append(value); + }); + return md5Hex(canonical.toString()); + } + + private Object normalizeValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Map || value instanceof Iterable) { + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException ex) { + return value.toString(); + } + } + return value; + } + + private String md5Hex(String source) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] bytes = digest.digest(source.getBytes(StandardCharsets.UTF_8)); + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte aByte : bytes) { + String part = Integer.toHexString(aByte & 0xFF); + if (part.length() == 1) { + builder.append('0'); + } + builder.append(part); + } + return builder.toString(); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("MD5 算法不可用", ex); + } + } + + private String encode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } + + private String generateRequestId() { + return UUID.randomUUID().toString().replace("-", ""); + } + + private String generateNonce() { + return UUID.randomUUID().toString().replace("-", ""); + } + + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException ex) { + return String.valueOf(value); + } + } + + private String truncate(String value, int maxLength) { + if (!StringUtils.hasText(value) || value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength); + } +} diff --git a/zt-module-template/zt-module-template-server/src/main/resources/sql/template_databus_request_log.sql b/zt-module-template/zt-module-template-server/src/main/resources/sql/template_databus_request_log.sql new file mode 100644 index 00000000..5112f7b2 --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/resources/sql/template_databus_request_log.sql @@ -0,0 +1,49 @@ +CREATE TABLE template_databus_request_log ( + ID BIGINT NOT NULL, + REQUEST_ID VARCHAR(64) NOT NULL, + TARGET_URL VARCHAR(512) NOT NULL, + HTTP_METHOD VARCHAR(16) NOT NULL, + QUERY_PARAMS TEXT, + REQUEST_HEADERS TEXT, + REQUEST_BODY TEXT, + ENCRYPTED_REQUEST_BODY TEXT, + SIGNATURE VARCHAR(128), + NONCE VARCHAR(64), + ZT_TIMESTAMP VARCHAR(32), + RESPONSE_STATUS INT, + ENCRYPTED_RESPONSE_BODY TEXT, + RESPONSE_BODY TEXT, + SUCCESS BIT DEFAULT '0' NOT NULL, + ERROR_MESSAGE TEXT, + DURATION_MS BIGINT, + CREATE_TIME DATETIME(6) DEFAULT CURRENT_TIMESTAMP NOT NULL, + UPDATE_TIME DATETIME(6) DEFAULT CURRENT_TIMESTAMP NOT NULL, + CREATOR VARCHAR(64), + UPDATER VARCHAR(64), + DELETED BIT DEFAULT '0' NOT NULL, + PRIMARY KEY (ID) +); + +COMMENT ON TABLE template_databus_request_log IS 'Databus 请求调度日志表'; +COMMENT ON COLUMN template_databus_request_log.ID IS '主键'; +COMMENT ON COLUMN template_databus_request_log.REQUEST_ID IS '请求唯一标识'; +COMMENT ON COLUMN template_databus_request_log.TARGET_URL IS '目标地址(含 Query)'; +COMMENT ON COLUMN template_databus_request_log.HTTP_METHOD IS 'HTTP 方法'; +COMMENT ON COLUMN template_databus_request_log.QUERY_PARAMS IS 'Query 参数 JSON'; +COMMENT ON COLUMN template_databus_request_log.REQUEST_HEADERS IS '请求头 JSON'; +COMMENT ON COLUMN template_databus_request_log.REQUEST_BODY IS '原始请求体'; +COMMENT ON COLUMN template_databus_request_log.ENCRYPTED_REQUEST_BODY IS '加密请求体'; +COMMENT ON COLUMN template_databus_request_log.SIGNATURE IS '签名'; +COMMENT ON COLUMN template_databus_request_log.NONCE IS '随机串'; +COMMENT ON COLUMN template_databus_request_log.ZT_TIMESTAMP IS '时间戳'; +COMMENT ON COLUMN template_databus_request_log.RESPONSE_STATUS IS '响应状态码'; +COMMENT ON COLUMN template_databus_request_log.ENCRYPTED_RESPONSE_BODY IS '加密响应体'; +COMMENT ON COLUMN template_databus_request_log.RESPONSE_BODY IS '解密后的响应体'; +COMMENT ON COLUMN template_databus_request_log.SUCCESS IS '是否成功'; +COMMENT ON COLUMN template_databus_request_log.ERROR_MESSAGE IS '错误信息'; +COMMENT ON COLUMN template_databus_request_log.DURATION_MS IS '耗时(毫秒)'; +COMMENT ON COLUMN template_databus_request_log.CREATE_TIME IS '创建时间'; +COMMENT ON COLUMN template_databus_request_log.UPDATE_TIME IS '更新时间'; +COMMENT ON COLUMN template_databus_request_log.CREATOR IS '创建人'; +COMMENT ON COLUMN template_databus_request_log.UPDATER IS '更新人'; +COMMENT ON COLUMN template_databus_request_log.DELETED IS '逻辑删除标记'; From 0846e4a56a24a88ac8fe34c70c923f3e8914c279 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 2 Dec 2025 17:48:59 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20template=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployment.yaml | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/deployment.yaml b/deployment.yaml index 523a1c8d..8f3caa41 100644 --- a/deployment.yaml +++ b/deployment.yaml @@ -422,3 +422,77 @@ spec: port: 48100 targetPort: 48100 nodePort: 30090 + + +--- +# zt-module-template +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-d6a0e78ebd674c279614498e4c57b133 + name: zt-module-template + labels: + app: zt-module-template + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-template + template: + metadata: + labels: + app: zt-module-template + spec: + containers: + - name: zt-module-template + image: 172.16.46.66:10043/zt/zt-module-template:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48100 + initialDelaySeconds: 50 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48100 + initialDelaySeconds: 50 + periodSeconds: 10 + failureThreshold: 5 + resources: + requests: + cpu: "500m" + memory: "1024Mi" + limits: + cpu: "700m" + memory: "1024Mi" + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-d6a0e78ebd674c279614498e4c57b133 + name: zt-module-template +spec: + type: NodePort + selector: + app: zt-module-template + ports: + - protocol: TCP + port: 48100 + targetPort: 48100 + nodePort: 30889 From 45140c7f5a9d97aaab512c6bafe216d871671fca Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 3 Dec 2025 09:51:43 +0800 Subject: [PATCH 09/13] =?UTF-8?q?1.=20=E8=B0=83=E6=95=B4=20databus=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=86=99=E5=85=A5=E6=97=A5=E5=BF=97=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E6=9C=BA=EF=BC=8C=E8=A7=A3=E5=86=B3=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E4=B8=8D=E5=88=B0=E7=A7=9F=E6=88=B7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/GatewaySecurityFilter.java | 85 +++++++++---------- 1 file changed, 39 insertions(+), 46 deletions(-) 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 c7b76152..0c9ad557 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 @@ -59,7 +59,8 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { private final ObjectMapper objectMapper; private final ApiGatewayAccessLogger accessLogger; private final AntPathMatcher pathMatcher = new AntPathMatcher(); - private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) @@ -68,32 +69,38 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { // 仅处理配置的 API 门户路径,不符合的请求直接放行 boolean matchesPortalPath = properties.getAllBasePaths() .stream() - .map(this::normalizeBasePath) .anyMatch(basePath -> pathMatcher.match(basePath + "/**", pathWithinApplication)); if (!matchesPortalPath) { filterChain.doFilter(request, response); return; } - Long accessLogId = accessLogger.logEntrance(request); - // 校验访问 IP 是否落在允许范围内 - if (!isIpAllowed(request)) { - log.warn("[API-PORTAL] 拦截来自 IP {} 访问 {} 的请求", request.getRemoteAddr(), pathWithinApplication); - response.sendError(HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); - accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); - return; - } + Long accessLogId = null; ApiGatewayProperties.Security security = properties.getSecurity(); ApiClientCredentialDO credential = null; - if (!security.isEnabled()) { - byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream()); - CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody); - ApiGatewayAccessLogger.propagateLogIdHeader(passthroughRequest, accessLogId); - filterChain.doFilter(passthroughRequest, response); - return; - } + Long tenantId = null; boolean dispatchedToGateway = false; try { - Long tenantId = resolveTenantId(request); + tenantId = resolveTenantId(request); + if (tenantId != null) { + // 绑定租户上下文,保证访问日志与后续数据库操作可写入 tenantId + TenantContextHolder.setTenantId(tenantId); + } + if (!isIpAllowed(request)) { + log.warn("[API-PORTAL] 拦截来自 IP {} 访问 {} 的请求", request.getRemoteAddr(), pathWithinApplication); + accessLogId = accessLogger.logEntrance(request); + response.sendError(HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); + accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); + return; + } + // IP 校验通过后再补录入口日志,避免无租户信息写库 + accessLogId = accessLogger.logEntrance(request); + if (!security.isEnabled()) { + byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream()); + CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody); + ApiGatewayAccessLogger.propagateLogIdHeader(passthroughRequest, accessLogId); + filterChain.doFilter(passthroughRequest, response); + return; + } // 从请求头解析 appId 并加载客户端凭证,包含匿名访问配置 String appId = requireHeader(request, APP_ID_HEADER, "缺少应用标识"); credential = credentialService.findActiveCredential(appId) @@ -144,17 +151,27 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { responseWrapper.copyBodyToResponse(); } } catch (SecurityValidationException ex) { + if (accessLogId == null) { + accessLogId = accessLogger.logEntrance(request); + } log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage()); writeErrorResponse(response, security, credential, ex.status(), ex.getMessage()); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage()); } } catch (Exception ex) { + if (accessLogId == null) { + accessLogId = accessLogger.logEntrance(request); + } log.error("[API-PORTAL] 处理安全校验时出现异常", ex); writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败"); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败"); } + } finally { + if (tenantId != null) { + TenantContextHolder.clear(); + } } } @@ -177,15 +194,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { return requestUri; } - private String normalizeBasePath(String basePath) { - String candidate = StringUtils.hasText(basePath) ? basePath : ApiGatewayProperties.DEFAULT_BASE_PATH; - candidate = candidate.startsWith("/") ? candidate : "/" + candidate; - if (candidate.endsWith("/")) { - candidate = candidate.substring(0, candidate.length() - 1); - } - return candidate; - } - private Long resolveTenantId(HttpServletRequest request) { if (!properties.isEnableTenantHeader()) { return null; @@ -306,7 +314,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { }); } catch (IllegalArgumentException ex) { log.debug("[API-PORTAL] 解析查询串 {} 失败", queryString, ex); - target.put("query", queryString); } } @@ -321,7 +328,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { if (bodyText.startsWith("{")) { try { Map bodyMap = objectMapper.readValue(bodyText, MAP_TYPE); - bodyMap.forEach((key, value) -> target.put(key, normalizeValue(value))); + target.putAll(bodyMap); return; } catch (JsonProcessingException ex) { log.debug("[API-PORTAL] 解析请求体 JSON 失败", ex); @@ -330,20 +337,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { target.put("body", bodyText); } - private Object normalizeValue(Object value) { - if (value == null) { - return null; - } - if (value instanceof Map || value instanceof List) { - try { - return objectMapper.writeValueAsString(value); - } catch (JsonProcessingException ex) { - return value.toString(); - } - } - return value; - } - private String resolveEncryptionType(ApiClientCredentialDO credential, ApiGatewayProperties.Security security) { if (credential != null && StringUtils.hasText(credential.getEncryptionType())) { return credential.getEncryptionType(); @@ -481,12 +474,12 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { response.resetBuffer(); response.setStatus(status.value()); String resolvedMessage = StringUtils.hasText(message) ? message : status.getReasonPhrase(); - String traceId = TracerUtils.getTraceId(); - ApiGatewayResponse envelope = ApiGatewayResponse.builder() + String traceId = TracerUtils.getTraceId(); + ApiGatewayResponse envelope = ApiGatewayResponse.builder() .code(status.value()) .message(resolvedMessage) .response(null) - .traceId(traceId) + .traceId(traceId) .build(); if (shouldEncryptErrorResponse(security, credential)) { String encryptionKey = credential.getEncryptionKey(); From 842155bfbd16915692cba4519b6255d2647c6789 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 3 Dec 2025 12:09:14 +0800 Subject: [PATCH 10/13] =?UTF-8?q?1.=20iwork=20=E5=9B=9E=E8=B0=83=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E4=B8=8D=E5=B8=A6=E7=A7=9F=E6=88=B7=E4=B8=8E=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E9=99=90=E5=88=B6=EF=BC=8C=E6=94=AF=E6=8C=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E4=B8=9A=E5=8A=A1=E7=BC=96=E5=8F=B7=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E9=99=84=E4=BB=B6=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../businessfile/dto/BusinessFileRespDTO.java | 5 +++++ .../businessfile/vo/BusinessFileRespVO.java | 4 ++++ .../businessfile/BusinessFileDO.java | 5 +++++ .../businessfile/BusinessFileServiceImpl.java | 2 ++ .../iwork/IWorkIntegrationController.java | 4 +++- .../impl/IWorkIntegrationServiceImpl.java | 20 +++++++++++++++---- 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/dto/BusinessFileRespDTO.java b/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/dto/BusinessFileRespDTO.java index 82605536..d6f33774 100644 --- a/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/dto/BusinessFileRespDTO.java +++ b/zt-module-infra/zt-module-infra-api/src/main/java/com/zt/plat/module/infra/api/businessfile/dto/BusinessFileRespDTO.java @@ -24,6 +24,11 @@ public class BusinessFileRespDTO implements Serializable { */ private Long id; + /** + * 租户编号 + */ + private Long tenantId; + /** * 业务Id */ diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/vo/BusinessFileRespVO.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/vo/BusinessFileRespVO.java index c9990c42..a99ec881 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/vo/BusinessFileRespVO.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/businessfile/vo/BusinessFileRespVO.java @@ -16,6 +16,10 @@ public class BusinessFileRespVO { @ExcelProperty("主键ID") private Long id; + @Schema(description = "租户编号", example = "1024") + @ExcelProperty("租户编号") + private Long tenantId; + @Schema(description = "业务Id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24322") @ExcelProperty("业务Id") private Long businessId; diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/businessfile/BusinessFileDO.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/businessfile/BusinessFileDO.java index 84df46e9..4c067913 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/businessfile/BusinessFileDO.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/dal/dataobject/businessfile/BusinessFileDO.java @@ -24,6 +24,11 @@ public class BusinessFileDO extends BaseDO { @TableId(type = IdType.ASSIGN_ID) private Long id; /** + * 租户编号 + */ + @TableField("TENANT_ID") + private Long tenantId; + /** * 业务Id */ @TableField("BSN_ID") diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java index 4a30d7fa..92722f96 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java @@ -5,6 +5,7 @@ import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.framework.common.util.user.UserNameEnrichUtils; import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import com.zt.plat.module.infra.controller.admin.businessfile.vo.BusinessFilePageReqVO; import com.zt.plat.module.infra.controller.admin.businessfile.vo.BusinessFileRespVO; import com.zt.plat.module.infra.controller.admin.businessfile.vo.BusinessFileSaveReqVO; @@ -101,6 +102,7 @@ public class BusinessFileServiceImpl implements BusinessFileService { } @Override + @TenantIgnore public BusinessFileDO getBusinessFileByBusinessCode(String businessCode) { if (!StringUtils.hasText(businessCode)) { throw exception(BUSINESS_FILE_NOT_EXISTS); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java index fb69b185..e69863fe 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java @@ -1,12 +1,14 @@ package com.zt.plat.module.system.controller.admin.integration.iwork; import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService; import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.security.PermitAll; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; @@ -14,7 +16,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import jakarta.annotation.security.PermitAll; import static com.zt.plat.framework.common.pojo.CommonResult.success; @@ -57,6 +58,7 @@ public class IWorkIntegrationController { } @PermitAll + @TenantIgnore @PostMapping("/callback/file") @Operation(summary = "iWork 文件回调:根据文件 URL 保存为附件并创建业务附件关联") public CommonResult callbackFile(@Valid @RequestBody IWorkFileCallbackReqVO reqVO) { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java index d1d7108a..c3a7a5cd 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java @@ -19,6 +19,7 @@ import com.zt.plat.module.infra.api.file.dto.FileRespDTO; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService; +import com.zt.plat.framework.tenant.core.util.TenantUtils; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,6 +38,7 @@ import java.security.*; import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.*; @@ -171,11 +173,21 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { } BusinessFileRespDTO referenceBusinessFile = loadBusinessFileByBusinessCode(businessCode); + Long tenantId = referenceBusinessFile.getTenantId(); + if (tenantId == null) { + throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务附件缺少租户信息,无法创建回调附件: " + businessCode); + } + + AtomicReference attachmentIdRef = new AtomicReference<>(); + TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile))); + return attachmentIdRef.get(); + } + + private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile) { Long businessId = referenceBusinessFile.getBusinessId(); - // 通过文件 API 创建文件 FileCreateReqDTO fileCreateReqDTO = new FileCreateReqDTO(); - fileCreateReqDTO.setName(resolveFileName(reqVO.getFileName(), fileUrl)); + fileCreateReqDTO.setName(resolveFileName(overrideFileName, fileUrl)); fileCreateReqDTO.setDirectory(null); fileCreateReqDTO.setType(null); fileCreateReqDTO.setContent(downloadFileBytes(fileUrl)); @@ -188,8 +200,8 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { Long fileId = fileResult.getData().getId(); BusinessFileSaveReqDTO businessReq = BusinessFileSaveReqDTO.builder() - .businessId(businessId) - .businessCode(referenceBusinessFile.getBusinessCode()) + .businessId(businessId) + .businessCode(referenceBusinessFile.getBusinessCode()) .fileId(fileId) .fileName(fileResult.getData().getName()) .source("iwork") From 69bcd6697b1b7e6800a1223cd694cdfe81721961 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 3 Dec 2025 17:59:56 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=9A=84=20xmx=20=E8=87=B3=201024mb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zt-gateway/Dockerfile | 2 +- zt-module-ai/zt-module-ai-server/Dockerfile | 2 +- zt-module-bpm/zt-module-bpm-server/Dockerfile | 2 +- zt-module-infra/zt-module-infra-server/Dockerfile | 2 +- zt-module-mp/zt-module-mp-server/Dockerfile | 2 +- zt-module-report/zt-module-report-server/Dockerfile | 2 +- zt-module-system/zt-module-system-server/Dockerfile | 2 +- zt-module-template/zt-module-template-server/Dockerfile | 2 +- zt-server/Dockerfile | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/zt-gateway/Dockerfile b/zt-gateway/Dockerfile index 9c33b9bf..47876e4d 100644 --- a/zt-gateway/Dockerfile +++ b/zt-gateway/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-gateway.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48080 diff --git a/zt-module-ai/zt-module-ai-server/Dockerfile b/zt-module-ai/zt-module-ai-server/Dockerfile index 286ab90d..246697fb 100644 --- a/zt-module-ai/zt-module-ai-server/Dockerfile +++ b/zt-module-ai/zt-module-ai-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-ai-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48090 diff --git a/zt-module-bpm/zt-module-bpm-server/Dockerfile b/zt-module-bpm/zt-module-bpm-server/Dockerfile index 230aa5c7..868eec5c 100644 --- a/zt-module-bpm/zt-module-bpm-server/Dockerfile +++ b/zt-module-bpm/zt-module-bpm-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-bpm-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48083 diff --git a/zt-module-infra/zt-module-infra-server/Dockerfile b/zt-module-infra/zt-module-infra-server/Dockerfile index 6f55bb34..49d73456 100644 --- a/zt-module-infra/zt-module-infra-server/Dockerfile +++ b/zt-module-infra/zt-module-infra-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-infra-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48082 diff --git a/zt-module-mp/zt-module-mp-server/Dockerfile b/zt-module-mp/zt-module-mp-server/Dockerfile index 18036673..ed023332 100644 --- a/zt-module-mp/zt-module-mp-server/Dockerfile +++ b/zt-module-mp/zt-module-mp-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-mp-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48086 diff --git a/zt-module-report/zt-module-report-server/Dockerfile b/zt-module-report/zt-module-report-server/Dockerfile index 53fd8763..0a9a86cf 100644 --- a/zt-module-report/zt-module-report-server/Dockerfile +++ b/zt-module-report/zt-module-report-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-report-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48084 diff --git a/zt-module-system/zt-module-system-server/Dockerfile b/zt-module-system/zt-module-system-server/Dockerfile index bce59094..6e8a12d8 100644 --- a/zt-module-system/zt-module-system-server/Dockerfile +++ b/zt-module-system/zt-module-system-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-system-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48081 diff --git a/zt-module-template/zt-module-template-server/Dockerfile b/zt-module-template/zt-module-template-server/Dockerfile index 4e36f207..e32668f2 100644 --- a/zt-module-template/zt-module-template-server/Dockerfile +++ b/zt-module-template/zt-module-template-server/Dockerfile @@ -10,7 +10,7 @@ COPY ./target/zt-module-template-server.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m" ## 暴露后端项目的 48080 端口 EXPOSE 48100 diff --git a/zt-server/Dockerfile b/zt-server/Dockerfile index e9725022..fd0d36b0 100644 --- a/zt-server/Dockerfile +++ b/zt-server/Dockerfile @@ -11,7 +11,7 @@ COPY ./target/zt-server.jar app.jar ## 设置 TZ 时区 ENV TZ=Asia/Shanghai ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 -ENV JAVA_OPTS="-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom" +ENV JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom" ## 应用参数 ENV ARGS="" From d4ade11b440f2f1939d1fc2f465888e7ed8ef931 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 3 Dec 2025 18:49:36 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20template=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployment.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment.yaml b/deployment.yaml index 8f3caa41..63c885e7 100644 --- a/deployment.yaml +++ b/deployment.yaml @@ -457,14 +457,14 @@ spec: readinessProbe: httpGet: path: /actuator/health - port: 48100 + port: 49100 initialDelaySeconds: 50 periodSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health - port: 48100 + port: 49100 initialDelaySeconds: 50 periodSeconds: 10 failureThreshold: 5 @@ -493,6 +493,6 @@ spec: app: zt-module-template ports: - protocol: TCP - port: 48100 - targetPort: 48100 + port: 49100 + targetPort: 49100 nodePort: 30889 From a7a2de77d8e35cd427c01c5b54c48bbf7f2f062b Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 4 Dec 2025 09:40:33 +0800 Subject: [PATCH 13/13] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E7=A9=BA=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E4=B8=8B=E7=9A=84=20sql=20=E8=AF=AD=E6=B3=95=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/businessfile/BusinessFileServiceImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java index 92722f96..2c9c50b5 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/businessfile/BusinessFileServiceImpl.java @@ -76,12 +76,16 @@ public class BusinessFileServiceImpl implements BusinessFileService { } @Override - public void deleteBusinessFileListByIds(List ids) { + public void deleteBusinessFileListByIds(List ids) { + // 如果 ids 为空,直接 return + if (ids == null || ids.isEmpty()){ + return; + } // 校验存在 validateBusinessFileExists(ids); // 删除 businessFileMapper.deleteByIds(ids); - } + } private void validateBusinessFileExists(List ids) { List list = businessFileMapper.selectByIds(ids); @@ -130,7 +134,7 @@ public class BusinessFileServiceImpl implements BusinessFileService { // 批量设置文件信息 setFileInfo(result.getList()); - + // 批量设置创建人名称 UserNameEnrichUtils.setCreatorNames(result.getList(), BusinessFileRespVO::getCreator,