From 5eff0d71df4db77264bb1c4ceb050af4bd798893 Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Fri, 1 May 2026 23:59:12 +0800 Subject: [PATCH] feat: add new models and update application structure for job scheduling --- .../pig4cloud/pigx/PigxBootApplication.java | 6 +- .../src/main/resources/application-dev.yml | 4 +- pigx-boot/src/main/resources/application.yml | 21 +- .../src/main/resources/logback-spring.xml | 15 +- .../DynamicRouteAutoConfiguration.java | 4 +- .../GlobalExceptionConfiguration.java | 52 +-- .../gateway/rule/VersionGrayLoadBalancer.java | 6 +- .../support/DynamicRouteHealthIndicator.java | 4 +- .../support/RedisRouteDefinitionWriter.java | 7 +- .../gray/rule/GrayRoundRobinLoadBalancer.java | 4 +- .../IdempotentAutoConfiguration.java | 37 +- .../idempotent/aspect/IdempotentAspect.java | 4 +- .../init/ApplicationLoggerInitializer.java | 4 +- .../log/util/JacksonSensitiveFieldUtil.java | 28 +- .../main/resources/META-INF/spring.factories | 2 +- .../sensitive/core/SensitiveSerialize.java | 5 +- .../util/SensitiveWordsProperties.java | 2 +- .../sentinel/feign/PigxSentinelFeign.java | 3 +- .../handle/GlobalBizExceptionHandler.java | 379 +++++++++++++----- .../sequence/SequenceAutoConfiguration.java | 6 +- .../common/sse/holder/SseEmitterHolder.java | 6 +- .../common/xss/PigxXssAutoConfiguration.java | 61 +-- .../pigx/common/xss/core/JacksonXssClean.java | 12 +- .../src/main/resources/logback-spring.xml | 2 +- .../nacos/bootstrap/PigxNacosApplication.java | 4 +- .../src/main/resources/application.properties | 15 +- .../src/main/resources/logback-spring.xml | 2 +- .../pigx/daemon/elastic/job/DemoJob.java | 8 +- .../src/main/resources/logback-spring.xml | 2 +- .../quartz/config/PigxInitQuartzJob.java | 36 +- .../quartz/config/PigxQuartzConfig.java | 69 +++- .../config/PigxQuartzCustomizerConfig.java | 4 +- .../quartz/config/PigxQuartzFactory.java | 80 +++- .../config/PigxQuartzInvokeFactory.java | 22 +- .../config/QuartzProtectionProperties.java | 37 ++ .../quartz/constants/PigxQuartzEnum.java | 24 +- .../pigx/daemon/quartz/entity/SysJobLog.java | 22 +- .../pigx/daemon/quartz/event/SysJobEvent.java | 11 +- .../daemon/quartz/event/SysJobListener.java | 15 +- .../quartz/event/SysJobLogListener.java | 4 +- .../support/QuartzExecutionMetadata.java | 32 ++ .../support/QuartzProtectionSupport.java | 176 ++++++++ .../daemon/quartz/util/TaskInvokUtil.java | 246 +++++++++--- .../pigx/bi/PigxJimuApplication.java | 6 +- ...AiAssistantCompatibilityConfiguration.java | 62 +++ .../src/main/resources/logback-spring.xml | 2 +- .../src/main/resources/logback-spring.xml | 2 +- .../mp/config/WxMpInRedisConfigStorage.java | 24 +- .../mp/controller/WxAccountController.java | 14 +- .../mp/controller/WxPortalController.java | 34 -- .../src/main/resources/logback-spring.xml | 2 +- .../resources/mapper/WxAccountFansMapper.xml | 2 +- .../main/resources/mapper/WxAccountMapper.xml | 2 +- .../main/resources/mapper/WxMenuMapper.xml | 2 +- 54 files changed, 1215 insertions(+), 420 deletions(-) create mode 100644 pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/config/QuartzProtectionProperties.java create mode 100644 pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/support/QuartzExecutionMetadata.java create mode 100644 pigx-visual/pigx-daemon-quartz/src/main/java/com/pig4cloud/pigx/daemon/quartz/support/QuartzProtectionSupport.java create mode 100644 pigx-visual/pigx-jimu-platform/src/main/java/com/pig4cloud/pigx/bi/config/JimuAiAssistantCompatibilityConfiguration.java diff --git a/pigx-boot/src/main/java/com/pig4cloud/pigx/PigxBootApplication.java b/pigx-boot/src/main/java/com/pig4cloud/pigx/PigxBootApplication.java index e1656d6ff..28d098fdc 100755 --- a/pigx-boot/src/main/java/com/pig4cloud/pigx/PigxBootApplication.java +++ b/pigx-boot/src/main/java/com/pig4cloud/pigx/PigxBootApplication.java @@ -29,8 +29,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PigxBootApplication { - public static void main(String[] args) { - SpringApplication.run(PigxBootApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(PigxBootApplication.class, args); + } } diff --git a/pigx-boot/src/main/resources/application-dev.yml b/pigx-boot/src/main/resources/application-dev.yml index a6b3bc197..dfab16e68 100644 --- a/pigx-boot/src/main/resources/application-dev.yml +++ b/pigx-boot/src/main/resources/application-dev.yml @@ -12,11 +12,10 @@ spring: password: root url: jdbc:mysql://pigx-mysql:3306/pigxx_boot?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true - # 文件上传配置 file: + type: local local: - enable: true base-path: /Users/lengleng/Downloads/img # 验证码配置 @@ -36,4 +35,3 @@ jasypt: # swagger token url 配置 swagger: token-url: ${swagger.gateway}/admin/oauth2/token - diff --git a/pigx-boot/src/main/resources/application.yml b/pigx-boot/src/main/resources/application.yml index 0522aadf6..10f59c04f 100644 --- a/pigx-boot/src/main/resources/application.yml +++ b/pigx-boot/src/main/resources/application.yml @@ -2,8 +2,6 @@ server: port: 9999 servlet: context-path: /admin - undertow: - max-http-post-size: 100MB spring: application: @@ -14,8 +12,8 @@ spring: # 上传文件大小限制 servlet: multipart: - max-file-size: ${server.undertow.max-http-post-size} - max-request-size: ${server.undertow.max-http-post-size} + max-file-size: 100MB + max-request-size: 100MB cloud: discovery: enabled: false @@ -34,6 +32,8 @@ spring: request-context-attribute: request main: allow-bean-definition-overriding: true + jackson: + use-jackson2-defaults: true profiles: active: dev @@ -57,6 +57,7 @@ security: - /code/** #--------------如下配置尽量不要变动------------- +management.endpoints.access.default: none # 默认所有端点都不允许访问 # mybatis-plus 配置 mybatis-plus: mapper-locations: classpath*:/mapper/*Mapper.xml @@ -73,8 +74,10 @@ mybatis-plus: call-setters-on-nulls: true shrink-whitespaces-in-sql: true # 查询以下前缀的表,不输出日志 -pigx: - mybatis: - skip-table: - - QRTZ_ #quartz 定时任务的表 - - ACT_ #工作流相关的表 +pigx.mybatis.skip-table: + - QRTZ_ #quartz 定时任务的表 + - ACT_ #工作流相关的表 + +# flowable 配置 +flowable: + variable-json-mapper: jackson2 diff --git a/pigx-boot/src/main/resources/logback-spring.xml b/pigx-boot/src/main/resources/logback-spring.xml index a35b8803f..6f116bff5 100644 --- a/pigx-boot/src/main/resources/logback-spring.xml +++ b/pigx-boot/src/main/resources/logback-spring.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + diff --git a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/DynamicRouteAutoConfiguration.java b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/DynamicRouteAutoConfiguration.java index d226191ba..0051a99c1 100644 --- a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/DynamicRouteAutoConfiguration.java +++ b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/DynamicRouteAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -69,7 +69,7 @@ public class DynamicRouteAutoConfiguration { * @return */ @Bean - public DynamicRouteHealthIndicator healthIndicator(RedisTemplate redisTemplate) { + public DynamicRouteHealthIndicator dynamicRouteHealthIndicator(RedisTemplate redisTemplate) { redisTemplate.setKeySerializer(new StringRedisSerializer()); if (!redisTemplate.hasKey(CacheConstants.ROUTE_KEY)) { diff --git a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/GlobalExceptionConfiguration.java b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/GlobalExceptionConfiguration.java index 93b6726ee..c42e876b2 100644 --- a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/GlobalExceptionConfiguration.java +++ b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/configuration/GlobalExceptionConfiguration.java @@ -1,12 +1,12 @@ package com.pig4cloud.pigx.common.gateway.configuration; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pigx.common.core.util.R; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.boot.webflux.error.ErrorWebExceptionHandler; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBufferFactory; @@ -29,33 +29,33 @@ import reactor.core.publisher.Mono; @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler { - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - ServerHttpResponse response = exchange.getResponse(); + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + ServerHttpResponse response = exchange.getResponse(); - if (response.isCommitted()) { - return Mono.error(ex); - } + if (response.isCommitted()) { + return Mono.error(ex); + } - // header set - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); - if (ex instanceof ResponseStatusException) { - response.setStatusCode(((ResponseStatusException) ex).getStatusCode()); - } + // header set + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + if (ex instanceof ResponseStatusException) { + response.setStatusCode(((ResponseStatusException) ex).getStatusCode()); + } - return response.writeWith(Mono.fromSupplier(() -> { - DataBufferFactory bufferFactory = response.bufferFactory(); - try { - log.error("Error request :{} Error Spring Cloud Gateway : {}", exchange.getRequest().getPath(), - ex.getMessage()); - return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage()))); - } catch (JsonProcessingException e) { - log.warn("Error writing response", ex); - return bufferFactory.wrap(new byte[0]); - } - })); - } + return response.writeWith(Mono.fromSupplier(() -> { + DataBufferFactory bufferFactory = response.bufferFactory(); + try { + log.error("Error request :{} Error Spring Cloud Gateway : {}", exchange.getRequest().getPath(), + ex.getMessage()); + return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage()))); + } catch (JacksonException e) { + log.warn("Error writing response", ex); + return bufferFactory.wrap(new byte[0]); + } + })); + } } diff --git a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/rule/VersionGrayLoadBalancer.java b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/rule/VersionGrayLoadBalancer.java index 2d6367845..932bdc466 100644 --- a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/rule/VersionGrayLoadBalancer.java +++ b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/rule/VersionGrayLoadBalancer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -65,7 +65,7 @@ public class VersionGrayLoadBalancer implements GrayLoadBalancer { // 过滤出不含VERSION实例 List versionInstanceList = instances.stream() .filter(instance -> !instance.getMetadata().containsKey(CommonConstants.VERSION)) - .toList(); + .toList(); if (CollUtil.isEmpty(versionInstanceList)) { // 根据权重获取实例 return randomByWeight(instances); @@ -77,7 +77,7 @@ public class VersionGrayLoadBalancer implements GrayLoadBalancer { List availableList = instances.stream() .filter(instance -> reqVersion .equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstants.VERSION))) - .toList(); + .toList(); if (CollUtil.isEmpty(availableList)) { // 根据权重获取实例 return randomByWeight(instances); diff --git a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/DynamicRouteHealthIndicator.java b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/DynamicRouteHealthIndicator.java index 0b0e4125b..601a06fb8 100644 --- a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/DynamicRouteHealthIndicator.java +++ b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/DynamicRouteHealthIndicator.java @@ -3,8 +3,8 @@ package com.pig4cloud.pigx.common.gateway.support; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.health.contributor.AbstractHealthIndicator; +import org.springframework.boot.health.contributor.Health; import org.springframework.data.redis.core.RedisTemplate; /** diff --git a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/RedisRouteDefinitionWriter.java b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/RedisRouteDefinitionWriter.java index 0a1ea978b..814451820 100644 --- a/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/RedisRouteDefinitionWriter.java +++ b/pigx-common/pigx-common-gateway/src/main/java/com/pig4cloud/pigx/common/gateway/support/RedisRouteDefinitionWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -27,7 +27,6 @@ import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -43,6 +42,7 @@ import java.util.List; @Slf4j @Component @RequiredArgsConstructor +@SuppressWarnings("removal") public class RedisRouteDefinitionWriter implements RouteDefinitionRepository { private final RedisTemplate redisTemplate; @@ -83,7 +83,8 @@ public class RedisRouteDefinitionWriter implements RouteDefinitionRepository { return Flux.fromIterable(routeList); } - redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); + redisTemplate.setHashValueSerializer( + new org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); HashOperations stringObjectObjectHashOperations = redisTemplate.opsForHash(); List values = stringObjectObjectHashOperations.values(CacheConstants.ROUTE_KEY); log.info("redis 中路由定义条数: {}, {}", values.size(), values); diff --git a/pigx-common/pigx-common-gray/src/main/java/com/pig4cloud/pigx/common/gray/rule/GrayRoundRobinLoadBalancer.java b/pigx-common/pigx-common-gray/src/main/java/com/pig4cloud/pigx/common/gray/rule/GrayRoundRobinLoadBalancer.java index db8c80240..96225e514 100755 --- a/pigx-common/pigx-common-gray/src/main/java/com/pig4cloud/pigx/common/gray/rule/GrayRoundRobinLoadBalancer.java +++ b/pigx-common/pigx-common-gray/src/main/java/com/pig4cloud/pigx/common/gray/rule/GrayRoundRobinLoadBalancer.java @@ -77,7 +77,7 @@ public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer { // 过滤出不含VERSION实例 List versionInstanceList = instances.stream() .filter(instance -> !instance.getMetadata().containsKey(CommonConstants.VERSION)) - .toList(); + .toList(); if (CollUtil.isEmpty(versionInstanceList)) { // 根据权重获取实例 return new DefaultResponse(randomByWeight(instances)); @@ -92,7 +92,7 @@ public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer { Map metadata = nacosInstance.getMetadata(); String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION); return reqVersion.equalsIgnoreCase(targetVersion); - }).toList(); + }).toList(); // 存在 随机返回 if (CollUtil.isNotEmpty(serviceInstanceList)) { diff --git a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java index b8ab16d3e..7ab8e7a4d 100644 --- a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java +++ b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/IdempotentAutoConfiguration.java @@ -4,11 +4,17 @@ import com.pig4cloud.pigx.common.idempotent.aspect.IdempotentAspect; import com.pig4cloud.pigx.common.idempotent.expression.ExpressionResolver; import com.pig4cloud.pigx.common.idempotent.expression.KeyResolver; import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.SingleServerConfig; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; /** * @author lengleng @@ -17,7 +23,8 @@ import org.springframework.context.annotation.Configuration; * 幂等插件初始化 */ @Configuration(proxyBeanMethods = false) -@AutoConfigureAfter(RedisAutoConfiguration.class) +@AutoConfigureAfter(DataRedisAutoConfiguration.class) +@EnableConfigurationProperties(DataRedisProperties.class) public class IdempotentAutoConfiguration { @@ -40,7 +47,31 @@ public class IdempotentAutoConfiguration { * @return {@link IdempotentAspect } */ @Bean - public IdempotentAspect idempotentAspect(Redisson redisson, KeyResolver keyResolver) { + @ConditionalOnMissingBean(RedissonClient.class) + public RedissonClient redissonClient(DataRedisProperties redisProperties) { + Config config = new Config(); + SingleServerConfig singleServerConfig = config.useSingleServer() + .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort()) + .setDatabase(redisProperties.getDatabase()); + + if (StringUtils.hasText(redisProperties.getUsername())) { + singleServerConfig.setUsername(redisProperties.getUsername()); + } + if (StringUtils.hasText(redisProperties.getPassword())) { + singleServerConfig.setPassword(redisProperties.getPassword()); + } + if (redisProperties.getConnectTimeout() != null) { + singleServerConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis()); + } + if (redisProperties.getTimeout() != null) { + singleServerConfig.setTimeout((int) redisProperties.getTimeout().toMillis()); + } + + return Redisson.create(config); + } + + @Bean + public IdempotentAspect idempotentAspect(RedissonClient redisson, KeyResolver keyResolver) { return new IdempotentAspect(redisson, keyResolver); } diff --git a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/aspect/IdempotentAspect.java b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/aspect/IdempotentAspect.java index 037553c83..dc02646de 100644 --- a/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/aspect/IdempotentAspect.java +++ b/pigx-common/pigx-common-idempotent/src/main/java/com/pig4cloud/pigx/common/idempotent/aspect/IdempotentAspect.java @@ -11,8 +11,8 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; -import org.redisson.Redisson; import org.redisson.api.RMapCache; +import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; @@ -60,7 +60,7 @@ public class IdempotentAspect { /** 是否自动删除 Key 标识 */ private static final String DELKEY = "delKey"; - private final Redisson redisson; + private final RedissonClient redisson; private final KeyResolver keyResolver; diff --git a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/init/ApplicationLoggerInitializer.java b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/init/ApplicationLoggerInitializer.java index 3c7c59572..d037a6324 100644 --- a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/init/ApplicationLoggerInitializer.java +++ b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/init/ApplicationLoggerInitializer.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.common.log.init; +import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; @@ -22,8 +22,6 @@ public class ApplicationLoggerInitializer implements EnvironmentPostProcessor, O System.setProperty("logging.file.name", String.format("%s/%s/debug.log", logBase, appName)); // 避免 sentinel 1.8.4+ 心跳日志过大 System.setProperty("csp.sentinel.log.level", "OFF"); - // 避免 sentinel 健康检查 server - System.setProperty("management.health.sentinel.enabled", "false"); // 避免各种依赖的地方组件造成 BeanPostProcessorChecker 警告 System.setProperty("logging.level.org.springframework.context.support.PostProcessorRegistrationDelegate", "ERROR"); diff --git a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/JacksonSensitiveFieldUtil.java b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/JacksonSensitiveFieldUtil.java index 5d7e253c9..ad7acb598 100644 --- a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/JacksonSensitiveFieldUtil.java +++ b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/JacksonSensitiveFieldUtil.java @@ -1,7 +1,9 @@ package com.pig4cloud.pigx.common.log.util; import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; @@ -10,6 +12,12 @@ import lombok.SneakyThrows; /** * Jackson 脱敏工具类 + *

+ * 状态字段 {@code objectMapper} 通过 read-copy-write 模式更新,{@link #configureSensitiveFields} + * 与 {@link #registerCustomModule} 都需要在并发调用下保证修改不丢失,因此设为 {@code synchronized}。 + * 读取路径({@link #readValue} 等)依赖 {@code volatile} 保证可见性,无需加锁。 + *

+ * 注:{@link ObjectMapper#copy()} 是 Jackson 2 才支持的能力,这里有意保留 Jackson 2 依赖。 * * @author lengleng * @date 2024/07/18 @@ -17,20 +25,22 @@ import lombok.SneakyThrows; public class JacksonSensitiveFieldUtil { @Getter - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static volatile ObjectMapper objectMapper = JsonMapper.builder() + .addMixIn(Object.class, PropertyFilterMixIn.class) + .build(); - static { - objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class); - } - - public static void configureSensitiveFields(String[] ignorableFieldNames) { + public static synchronized void configureSensitiveFields(String[] ignorableFieldNames) { FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames)); - objectMapper.setFilterProvider(filters); + ObjectMapper mapper = objectMapper.copy(); + mapper.setFilterProvider(filters); + objectMapper = mapper; } - public static void registerCustomModule(com.fasterxml.jackson.databind.Module module) { - objectMapper.registerModule(module); + public static synchronized void registerCustomModule(Module module) { + ObjectMapper mapper = objectMapper.copy(); + mapper.registerModule(module); + objectMapper = mapper; } /** diff --git a/pigx-common/pigx-common-log/src/main/resources/META-INF/spring.factories b/pigx-common/pigx-common-log/src/main/resources/META-INF/spring.factories index 83eb42f33..f1710ea77 100644 --- a/pigx-common/pigx-common-log/src/main/resources/META-INF/spring.factories +++ b/pigx-common/pigx-common-log/src/main/resources/META-INF/spring.factories @@ -1,2 +1,2 @@ -org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.boot.EnvironmentPostProcessor=\ com.pig4cloud.pigx.common.log.init.ApplicationLoggerInitializer diff --git a/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/core/SensitiveSerialize.java b/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/core/SensitiveSerialize.java index c9e936e33..c64c49d1e 100644 --- a/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/core/SensitiveSerialize.java +++ b/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/core/SensitiveSerialize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -77,8 +77,7 @@ public class SensitiveSerialize extends JsonSerializer implements Contex sensitive = beanProperty.getContextAnnotation(Sensitive.class); } if (sensitive != null) { - this.sensitive = sensitive; - return this; + return new SensitiveSerialize(sensitive); } } return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); diff --git a/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/util/SensitiveWordsProperties.java b/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/util/SensitiveWordsProperties.java index d1f35bd4e..1e96ca541 100644 --- a/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/util/SensitiveWordsProperties.java +++ b/pigx-common/pigx-common-sensitive/src/main/java/com/pig4cloud/pigx/common/sensitive/util/SensitiveWordsProperties.java @@ -19,7 +19,7 @@ public class SensitiveWordsProperties { /** * The sensitive words to detect. */ - private String mask = "no_mask"; + private String mask = "*"; /** * Whether the sensitive words detection is enabled. diff --git a/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/feign/PigxSentinelFeign.java b/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/feign/PigxSentinelFeign.java index dfcfa8eb0..557d54e29 100644 --- a/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/feign/PigxSentinelFeign.java +++ b/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/feign/PigxSentinelFeign.java @@ -19,6 +19,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; + /** * 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} * @@ -90,7 +91,7 @@ public final class PigxSentinelFeign { Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType); if (fallbackInstance == null) { throw new IllegalStateException(String - .format("No %s instance of type %s found for feign client %s", type, fallbackType, name)); + .format("No %s instance of type %s found for feign client %s", type, fallbackType, name)); } if (!targetType.isAssignableFrom(fallbackType)) { diff --git a/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/handle/GlobalBizExceptionHandler.java b/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/handle/GlobalBizExceptionHandler.java index 6631a3e10..ceabf12a4 100644 --- a/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/handle/GlobalBizExceptionHandler.java +++ b/pigx-common/pigx-common-sentinel/src/main/java/com/pig4cloud/pigx/common/sentinel/handle/GlobalBizExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,130 +17,323 @@ package com.pig4cloud.pigx.common.sentinel.handle; +import cn.hutool.core.util.StrUtil; import com.alibaba.csp.sentinel.Tracer; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pigx.common.core.util.R; import feign.FeignException; -import lombok.SneakyThrows; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; -import org.springframework.web.bind.annotation.*; +import org.springframework.validation.method.ParameterValidationResult; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.async.AsyncRequestNotUsableException; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.resource.NoResourceFoundException; +import java.nio.ByteBuffer; import java.sql.SQLException; import java.util.List; +import java.util.stream.Collectors; /** + * 全局异常处理器 + *

+ * 适配 Spring Boot 4.0+/Spring Framework 7.0+,统一拦截 Web、参数校验、Feign、IO 等异常。 + * * @author lengleng - * @date 2020-06-29 + * @date 2026-05-18 */ @Slf4j @RestControllerAdvice +@Order(Ordered.HIGHEST_PRECEDENCE) public class GlobalBizExceptionHandler { - private final ObjectMapper objectMapper = new ObjectMapper(); + /** + * JSON 序列化工具,用于解析 Feign 透传的远端 R 响应体。 + */ + private final ObjectMapper objectMapper = new ObjectMapper(); - /** - * 全局异常. - * - * @param e the e - * @return R - */ - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public R handleGlobalException(Exception e) { + /** + * 全局兜底异常 + * @param e 未被更具体处理器捕获的异常 + * @return 统一失败响应,消息来自异常本地化描述 + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleGlobalException(Exception e) { + log.error("全局异常信息 ex={}", e.getMessage(), e); + // 业务异常交由 sentinel 记录 + Tracer.trace(e); + return R.failed(e.getLocalizedMessage()); + } - log.error("全局异常信息 ex={}", e.getMessage(), e); + /** + * 客户端断开导致的异步响应不可用异常,无需返回任何内容 + *

+ * Spring Boot 3.2+ 在 SSE / 长连接场景客户端主动断开时抛出,需静默处理避免污染日志 + * @param e 异步请求不可用异常 + */ + @ExceptionHandler(AsyncRequestNotUsableException.class) + @ResponseStatus(HttpStatus.OK) + public void handleAsyncRequestNotUsableException(AsyncRequestNotUsableException e) { + log.debug("异步请求已不可用 ex={}", e.getMessage()); + } - // 业务异常交由 sentinel 记录 - Tracer.trace(e); - return R.failed(e.getLocalizedMessage()); - } + /** + * 数据库异常 + * @param exception 数据库调用异常 + * @return 统一失败响应,不向客户端暴露底层 SQL 细节 + */ + @ExceptionHandler(SQLException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleSQLException(SQLException exception) { + log.error("数据库调用异常 ex={}", exception.getMessage(), exception); + return R.failed("数据库调用异常,请联系管理员处理"); + } - /** - * SQLException Exception - * - * @param exception 数据库调用异常 - * @return R - */ - @ExceptionHandler({SQLException.class}) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public R handleSQLException(SQLException exception) { - log.error("数据库调用异常 ex={}", exception.getMessage(), exception); - return R.failed("数据库调用异常,请联系管理员处理"); - } + /** + * Feign 远程调用异常,透传远端 R 响应体 + * @param e Feign 调用异常 + * @return 远端 R 响应体或本地统一失败响应 + */ + @ExceptionHandler(FeignException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public R handleFeignException(FeignException e) { + log.error("Feign 调用异常 ex={}", e.getMessage(), e); + Tracer.trace(e); - @SneakyThrows - @ExceptionHandler(FeignException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public R handleGlobalException(FeignException e) { - log.error("全局异常信息 ex={}", e.getMessage(), e); + if (e.responseBody().isPresent()) { + try { + ByteBuffer responseBody = e.responseBody().get(); + byte[] body = new byte[responseBody.remaining()]; + responseBody.get(body); + return objectMapper.readValue(body, R.class); + } + catch (Exception ex) { + log.warn("Feign 响应体反序列化失败 ex={}", ex.getMessage()); + } + } + return R.failed(e.getLocalizedMessage()); + } - // 业务异常交由 sentinel 记录 - Tracer.trace(e); + /** + * 拒绝授权异常 + * @param e Spring Security 访问拒绝异常 + * @return 权限不足的统一失败响应 + */ + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public R handleAccessDeniedException(AccessDeniedException e) { + log.error("拒绝授权异常信息 ex={}", e.getMessage()); + return R.failed("权限不足,不允许访问"); + } - if (e.responseBody().isPresent()) { - return objectMapper.readValue(e.responseBody().get().array(), R.class); - } + /** + * 表单参数绑定校验异常(@ModelAttribute / 表单提交) + * @param exception 表单参数绑定异常 + * @return 字段校验失败的统一失败响应 + */ + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleBindException(BindException exception) { + log.warn("参数绑定异常 ex={}", exception.getMessage()); + return R.failed(buildFieldErrorMessage(exception.getBindingResult().getFieldErrors())); + } - return R.failed(e.getLocalizedMessage()); - } + /** + * @RequestBody + @Valid 校验异常 + * @param exception 请求体参数校验异常 + * @return 字段校验失败的统一失败响应 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) { + log.warn("请求体参数校验异常 ex={}", exception.getMessage()); + return R.failed(buildFieldErrorMessage(exception.getBindingResult().getFieldErrors())); + } - /** - * AccessDeniedException - * - * @param e the e - * @return R - */ - @ExceptionHandler(AccessDeniedException.class) - @ResponseStatus(HttpStatus.FORBIDDEN) - public R handleAccessDeniedException(AccessDeniedException e) { - log.error("拒绝授权异常信息 ex={}", e.getMessage()); - return R.failed("权限不足,不允许访问"); - } + /** + * Spring 7.0+ 方法级参数校验异常(@Validated 标注在 Controller 上的 @RequestParam / @PathVariable 校验) + * @param exception 方法参数校验异常 + * @return 方法参数校验失败的统一失败响应 + */ + @ExceptionHandler(HandlerMethodValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleHandlerMethodValidationException(HandlerMethodValidationException exception) { + log.warn("方法参数校验异常 ex={}", exception.getMessage()); + String parameterMessage = exception.getParameterValidationResults() + .stream() + .map(ParameterValidationResult::getResolvableErrors) + .flatMap(List::stream) + .map(error -> error.getDefaultMessage() == null ? StrUtil.EMPTY : error.getDefaultMessage()) + .collect(Collectors.joining(StrUtil.COMMA)); + String crossParameterMessage = buildResolvableErrorMessage(exception.getCrossParameterValidationResults()); + String message = joinErrorMessages(parameterMessage, crossParameterMessage); + return R.failed(message); + } - /** - * validation Exception - * - * @param exception - * @return R - */ - @ExceptionHandler({BindException.class}) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public R handleBodyValidException(BindException exception) { - List fieldErrors = exception.getBindingResult().getFieldErrors(); - // 插入log 的逻辑 - return R.failed(String.format("%s %s", fieldErrors.get(0).getField(), fieldErrors.get(0).getDefaultMessage())); - } + /** + * @Validated 在 Service 层方法或单参数校验失败 + * @param exception 约束校验异常 + * @return 约束校验失败的统一失败响应 + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleConstraintViolationException(ConstraintViolationException exception) { + log.warn("约束校验异常 ex={}", exception.getMessage()); + String message = exception.getConstraintViolations() + .stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining(StrUtil.COMMA)); + return R.failed(message); + } - /** - * 避免 404 重定向到 /error 导致NPE ,ignore-url 需要配置对应端点 - * - * @return R - */ - @DeleteMapping("/error") - @ResponseStatus(HttpStatus.NOT_FOUND) - public R noHandlerFoundException() { - return R.failed(HttpStatus.NOT_FOUND.getReasonPhrase()); - } + /** + * 请求体解析异常(JSON 格式错误 / 类型不匹配) + * @param exception 请求体解析异常 + * @return 请求体格式错误的统一失败响应 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleHttpMessageNotReadableException(HttpMessageNotReadableException exception) { + log.warn("请求体解析异常 ex={}", exception.getMessage()); + return R.failed("请求体格式错误,无法解析"); + } - /** - * 保持和低版本请求路径不存在的行为一致 - *

- * [Spring Boot - * 3.2.0] 404 Not Found behavior #38733 - * - * @param exception - * @return R - */ - @ExceptionHandler({NoResourceFoundException.class}) - @ResponseStatus(HttpStatus.NOT_FOUND) - public R noResourceFoundException(NoResourceFoundException exception) { - log.debug("请求路径 404 {}", exception.getMessage()); - return R.failed(exception.getMessage()); - } + /** + * 缺少必填请求参数 + * @param exception 缺少请求参数异常 + * @return 标明缺失参数名的统一失败响应 + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleMissingServletRequestParameterException(MissingServletRequestParameterException exception) { + log.warn("缺少必填参数 ex={}", exception.getMessage()); + return R.failed(StrUtil.format("缺少必填参数: {}", exception.getParameterName())); + } + + /** + * 请求参数类型转换异常 + * @param exception 方法参数类型不匹配异常 + * @return 标明参数名的统一失败响应 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) { + log.warn("参数类型不匹配 ex={}", exception.getMessage()); + return R.failed(StrUtil.format("参数 {} 类型不正确", exception.getName())); + } + + /** + * 请求方法不支持 + * @param exception 请求方法不支持异常 + * @return 请求方法不支持的统一失败响应 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) { + log.warn("请求方法不支持 ex={}", exception.getMessage()); + return R.failed(exception.getMessage()); + } + + /** + * Content-Type 不支持 + * @param exception 媒体类型不支持异常 + * @return 媒体类型不支持的统一失败响应 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + public R handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException exception) { + log.warn("媒体类型不支持 ex={}", exception.getMessage()); + return R.failed(exception.getMessage()); + } + + /** + * 文件上传超过大小限制 + * @param exception 文件大小超过限制异常 + * @return 文件大小超过限制的统一失败响应 + */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + @ResponseStatus(HttpStatus.CONTENT_TOO_LARGE) + public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException exception) { + log.warn("上传文件超过大小限制 ex={}", exception.getMessage()); + return R.failed("上传文件大小超过限制"); + } + + /** + * 保持和低版本请求路径不存在的行为一致 + *

+ * [Spring Boot + * 3.2.0] 404 Not Found behavior #38733 + * @param exception 静态资源或请求路径不存在异常 + * @return 404 的统一失败响应 + */ + @ExceptionHandler(NoResourceFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public R handleNoResourceFoundException(NoResourceFoundException exception) { + log.debug("请求路径 404 {}", exception.getMessage()); + return R.failed(exception.getMessage()); + } + + /** + * 构造字段校验错误信息 + * @param fieldErrors 字段校验错误列表,空集合返回空字符串 + * @return 拼接后的字段校验错误信息 + */ + private String buildFieldErrorMessage(List fieldErrors) { + if (fieldErrors == null || fieldErrors.isEmpty()) { + return StrUtil.EMPTY; + } + return fieldErrors.stream() + .map(error -> StrUtil.format("{} {}", error.getField(), error.getDefaultMessage())) + .collect(Collectors.joining(StrUtil.COMMA)); + } + + /** + * 构造 Spring 参数校验错误信息 + * @param errors 参数校验错误,空集合返回空字符串 + * @return 拼接后的错误信息 + */ + private String buildResolvableErrorMessage(List errors) { + if (errors == null || errors.isEmpty()) { + return StrUtil.EMPTY; + } + return errors.stream() + .map(error -> error.getDefaultMessage() == null ? StrUtil.EMPTY : error.getDefaultMessage()) + .collect(Collectors.joining(StrUtil.COMMA)); + } + + /** + * 拼接非空错误信息片段 + * @param first 第一段错误信息,可为空 + * @param second 第二段错误信息,可为空 + * @return 拼接后的错误信息,全部为空时返回空字符串 + */ + private String joinErrorMessages(String first, String second) { + if (StrUtil.isBlank(first)) { + return StrUtil.blankToDefault(second, StrUtil.EMPTY); + } + if (StrUtil.isBlank(second)) { + return first; + } + return StrUtil.join(StrUtil.COMMA, first, second); + } } diff --git a/pigx-common/pigx-common-sequence/src/main/java/com/pig4cloud/pigx/common/sequence/SequenceAutoConfiguration.java b/pigx-common/pigx-common-sequence/src/main/java/com/pig4cloud/pigx/common/sequence/SequenceAutoConfiguration.java index 058429865..fea0c3bb5 100644 --- a/pigx-common/pigx-common-sequence/src/main/java/com/pig4cloud/pigx/common/sequence/SequenceAutoConfiguration.java +++ b/pigx-common/pigx-common-sequence/src/main/java/com/pig4cloud/pigx/common/sequence/SequenceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,8 +26,8 @@ import org.anyline.metadata.Index; import org.anyline.metadata.Table; import org.anyline.proxy.ServiceProxy; import org.anyline.service.AnylineService; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @@ -49,7 +49,7 @@ public class SequenceAutoConfiguration { /** * 启动后自动创建表 */ - @EventListener({WebServerInitializedEvent.class}) + @EventListener({ApplicationReadyEvent.class}) public void onApplicationReady() throws Exception { // 判断是否需要创建表 AnylineService service = ServiceProxy.service(); diff --git a/pigx-common/pigx-common-sse/src/main/java/com/pig4cloud/pigx/common/sse/holder/SseEmitterHolder.java b/pigx-common/pigx-common-sse/src/main/java/com/pig4cloud/pigx/common/sse/holder/SseEmitterHolder.java index 77faed9d2..a9246497b 100644 --- a/pigx-common/pigx-common-sse/src/main/java/com/pig4cloud/pigx/common/sse/holder/SseEmitterHolder.java +++ b/pigx-common/pigx-common-sse/src/main/java/com/pig4cloud/pigx/common/sse/holder/SseEmitterHolder.java @@ -31,10 +31,10 @@ public final class SseEmitterHolder { /** * 删除一个 sseEmitter - * @param sseEmitter SseEmitter + * @param sseEmitter SseEmitter */ - public static void removeSseEmitter(SseEmitter sseEmitter) { - USER_SSE_EMITTER_MAP.values().remove(sseEmitter); + public static void removeSseEmitter(SseEmitter sseEmitter) { + USER_SSE_EMITTER_MAP.values().remove(sseEmitter); } /** diff --git a/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/PigxXssAutoConfiguration.java b/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/PigxXssAutoConfiguration.java index 1f8f0f2ba..8752b53a5 100644 --- a/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/PigxXssAutoConfiguration.java +++ b/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/PigxXssAutoConfiguration.java @@ -21,7 +21,6 @@ import com.pig4cloud.pigx.common.xss.core.*; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +30,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; + /** * jackson xss 配置 * @@ -40,40 +40,41 @@ import java.util.List; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(PigxXssProperties.class) @ConditionalOnProperty(prefix = PigxXssProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) +@SuppressWarnings("removal") public class PigxXssAutoConfiguration implements WebMvcConfigurer { - private final PigxXssProperties xssProperties; + private final PigxXssProperties xssProperties; - @Bean - @ConditionalOnMissingBean - public XssCleaner xssCleaner(PigxXssProperties properties) { - return new DefaultXssCleaner(properties); - } + @Bean + @ConditionalOnMissingBean + public XssCleaner xssCleaner(PigxXssProperties properties) { + return new DefaultXssCleaner(properties); + } - @Bean - @ConditionalOnMissingBean - public FormXssClean formXssClean(PigxXssProperties properties, XssCleaner xssCleaner) { - return new FormXssClean(properties, xssCleaner); - } + @Bean + @ConditionalOnMissingBean + public FormXssClean formXssClean(PigxXssProperties properties, XssCleaner xssCleaner) { + return new FormXssClean(properties, xssCleaner); + } - @Bean - public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(PigxXssProperties properties, - XssCleaner xssCleaner) { - JacksonXssClean xssClean = new JacksonXssClean(properties, xssCleaner); - return builder -> builder.deserializerByType(String.class, xssClean); - } + @Bean + public org.springframework.boot.jackson2.autoconfigure.Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer( + PigxXssProperties properties, XssCleaner xssCleaner) { + JacksonXssClean xssClean = new JacksonXssClean(properties, xssCleaner); + return builder -> builder.deserializerByType(String.class, xssClean); + } - @Override - public void addInterceptors(InterceptorRegistry registry) { - List patterns = xssProperties.getPathPatterns(); - if (patterns.isEmpty()) { - patterns.add("/**"); - } - XssCleanInterceptor interceptor = new XssCleanInterceptor(xssProperties); - registry.addInterceptor(interceptor) - .addPathPatterns(patterns) - .excludePathPatterns(xssProperties.getPathExcludePatterns()) - .order(Ordered.LOWEST_PRECEDENCE); - } + @Override + public void addInterceptors(InterceptorRegistry registry) { + List patterns = xssProperties.getPathPatterns(); + if (patterns.isEmpty()) { + patterns.add("/**"); + } + XssCleanInterceptor interceptor = new XssCleanInterceptor(xssProperties); + registry.addInterceptor(interceptor) + .addPathPatterns(patterns) + .excludePathPatterns(xssProperties.getPathExcludePatterns()) + .order(Ordered.LOWEST_PRECEDENCE); + } } diff --git a/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/core/JacksonXssClean.java b/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/core/JacksonXssClean.java index c1aa746de..786a4b77c 100644 --- a/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/core/JacksonXssClean.java +++ b/pigx-common/pigx-common-xss/src/main/java/com/pig4cloud/pigx/common/xss/core/JacksonXssClean.java @@ -18,10 +18,9 @@ package com.pig4cloud.pigx.common.xss.core; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.pig4cloud.pigx.common.xss.config.PigxXssProperties; import com.pig4cloud.pigx.common.xss.utils.XssUtil; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -32,13 +31,18 @@ import java.io.IOException; * @author L.cm */ @Slf4j -@RequiredArgsConstructor -public class JacksonXssClean extends JsonDeserializer { +public class JacksonXssClean extends StdDeserializer { private final PigxXssProperties properties; private final XssCleaner xssCleaner; + public JacksonXssClean(PigxXssProperties properties, XssCleaner xssCleaner) { + super(String.class); + this.properties = properties; + this.xssCleaner = xssCleaner; + } + @Override public String deserialize(JsonParser p, DeserializationContext ctx) throws IOException { // XSS filter diff --git a/pigx-gateway/src/main/resources/logback-spring.xml b/pigx-gateway/src/main/resources/logback-spring.xml index 9707ae89c..4e0059ebc 100644 --- a/pigx-gateway/src/main/resources/logback-spring.xml +++ b/pigx-gateway/src/main/resources/logback-spring.xml @@ -1,6 +1,6 @@