Merge remote-tracking branch 'yudao/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
This commit is contained in:
puhui999
2023-09-30 11:35:51 +08:00
722 changed files with 5788 additions and 48800 deletions

View File

@ -137,6 +137,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -23,6 +23,10 @@ public class LocalDateTimeUtils {
return LocalDateTime.now().plus(duration);
}
public static LocalDateTime minusTime(Duration duration) {
return LocalDateTime.now().minus(duration);
}
public static boolean beforeNow(LocalDateTime date) {
return date.isBefore(LocalDateTime.now());
}

View File

@ -10,7 +10,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@ -23,7 +22,6 @@ import java.util.List;
*
* @author 芋道源码
*/
@UtilityClass
@Slf4j
public class JsonUtils {

View File

@ -64,12 +64,12 @@ public class PayTransferRespDTO {
}
/**
* 创建【FAILURE】状态的转账返回
* 创建【CLOSED】状态的转账返回
*/
public static PayTransferRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
String outTransferNo, Object rawData) {
public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.FAILURE.getStatus();
respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus();
respDTO.channelErrorCode = channelErrorCode;
respDTO.channelErrorMsg = channelErrorMsg;
// 相对通用的字段

View File

@ -58,7 +58,6 @@ public class PayTransferUnifiedReqDTO {
/**
* 支付渠道的额外参数
*
*/
private Map<String, String> channelExtras;

View File

@ -50,7 +50,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
*/
public final void init() {
doInit();
log.info("[init][客户端({}) 初始化完成]", getId());
log.debug("[init][客户端({}) 初始化完成]", getId());
}
/**

View File

@ -51,7 +51,7 @@ public class AlipayTransferClient extends AbstractAlipayPayClient {
model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER。
model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
PayTransferTypeEnum transferType = PayTransferTypeEnum.valueOf(reqDTO.getType());
PayTransferTypeEnum transferType = PayTransferTypeEnum.ofType(reqDTO.getType());
switch(transferType){
case WX_BALANCE :
case WALLET_BALANCE : {
@ -84,7 +84,7 @@ public class AlipayTransferClient extends AbstractAlipayPayClient {
if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.failureOf(response.getSubCode(), response.getSubMsg(),
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.enums.transfer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* 渠道的转账状态枚举
*
@ -12,10 +14,28 @@ import lombok.Getter;
@AllArgsConstructor
public enum PayTransferStatusRespEnum {
WAITING(0, "等待转账"),
SUCCESS(10, "转账成功"),
FAILURE(20, "转账失败");
WAITING(0, "转账"),
/**
* TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
*/
IN_PROGRESS(10, "转账进行中"),
SUCCESS(20, "转账成功"),
/**
* 转账关闭 (失败,或者其它情况)
*/
CLOSED(30, "转账关闭");
private final Integer status;
private final String name;
public static boolean isSuccess(Integer status) {
return Objects.equals(status, SUCCESS.getStatus());
}
public static boolean isClosed(Integer status) {
return Objects.equals(status, CLOSED.getStatus());
}
}

View File

@ -20,6 +20,9 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
BANK_CARD(3, "银行卡"),
WALLET_BALANCE(4, "钱包余额");
public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
private final Integer type;
private final String name;
@ -30,7 +33,7 @@ public enum PayTransferTypeEnum implements IntArrayValuable {
return ARRAYS;
}
public static PayTransferTypeEnum valueOf(Integer type) {
public static PayTransferTypeEnum ofType(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}

View File

@ -40,7 +40,7 @@ public abstract class AbstractSmsClient implements SmsClient {
*/
public final void init() {
doInit();
log.info("[init][配置({}) 初始化完成]", properties);
log.debug("[init][配置({}) 初始化完成]", properties);
}
/**

View File

@ -11,40 +11,40 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
*/
public interface SmsFrameworkErrorCodeConstants {
ErrorCode SMS_UNKNOWN = new ErrorCode(2001000000, "未知错误,需要解析");
ErrorCode SMS_UNKNOWN = new ErrorCode(2_001_000_000, "未知错误,需要解析");
// ========== 权限 / 限流等相关 2001000100 ==========
// ========== 权限 / 限流等相关 2-001-000-100 ==========
ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2001000100, "没有发送短信的权限");
ErrorCode SMS_IP_DENY = new ErrorCode(2001000100, "IP 不允许发送短信");
ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2_001_000_100, "没有发送短信的权限");
ErrorCode SMS_IP_DENY = new ErrorCode(2_001_000_100, "IP 不允许发送短信");
// 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟5 条 / 小时,累计 10 条 / 天。
ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2001000102, "指定手机的发送限流");
ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2_001_000_102, "指定手机的发送限流");
// 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2001000103, "每天的发送限流");
ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2_001_000_103, "每天的发送限流");
ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2_001_000_104, "短信内容有敏感词");
// 腾讯云为避免骚扰用户营销短信只允许在8点到22点发送。
ErrorCode SMS_SEND_MARKET_LIMIT_CONTROL = new ErrorCode(2001000105, "营销短信发送时间限制");
ErrorCode SMS_SEND_MARKET_LIMIT_CONTROL = new ErrorCode(2_001_000_105, "营销短信发送时间限制");
// ========== 模板相关 2001000200 ==========
ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
// ========== 模板相关 2-001-000-200 ==========
ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2_001_000_200, "短信模板不合法"); // 包括短信模板不存在
ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2_001_000_201, "模板参数不正确");
// ========== 签名相关 2001000300 ==========
ErrorCode SMS_SIGN_INVALID = new ErrorCode(2001000300, "短信签名不可用");
// ========== 签名相关 2-001-000-300 ==========
ErrorCode SMS_SIGN_INVALID = new ErrorCode(2_001_000_300, "短信签名不可用");
// ========== 账户相关 2001000400 ==========
ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2001000400, "账户余额不足");
ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2001000401, "apiKey 不存在");
// ========== 账户相关 2-001-000-400 ==========
ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2_001_000_400, "账户余额不足");
ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2_001_000_401, "apiKey 不存在");
// ========== 其它相关 2001000900 开头 ==========
ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
ErrorCode SMS_APP_ID_INVALID = new ErrorCode(2001000903, "SdkAppId不合法");
// ========== 其它相关 2-001-000-900 开头 ==========
ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2_001_000_900, "请求参数缺失");
ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2_001_000_901, "手机格式不正确");
ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2_001_000_902, "手机号在黑名单中");
ErrorCode SMS_APP_ID_INVALID = new ErrorCode(2_001_000_903, "SdkAppId不合法");
ErrorCode EXCEPTION = new ErrorCode(2001000999, "调用异常");
ErrorCode EXCEPTION = new ErrorCode(2_001_000_999, "调用异常");
}

View File

@ -1,36 +1,36 @@
package cn.iocoder.yudao.framework.social.config;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import com.xingyuv.http.HttpUtil;
import com.xingyuv.http.support.hutool.HutoolImpl;
import com.xingyuv.jushauth.cache.AuthStateCache;
import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 社交自动装配类
*
* @author timfruit
* @date 2021-10-30
*/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(JustAuthProperties.class)
public class YudaoSocialAutoConfiguration {
@Bean
@Primary
@ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
// 需要修改 HttpUtil 使用的实现,避免类报错
HttpUtil.setHttp(new HutoolImpl());
// 创建 YudaoAuthRequestFactory
return new YudaoAuthRequestFactory(properties, authStateCache);
}
}
package cn.iocoder.yudao.framework.social.config;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import com.xingyuv.http.HttpUtil;
import com.xingyuv.http.support.hutool.HutoolImpl;
import com.xingyuv.jushauth.cache.AuthStateCache;
import com.xingyuv.justauth.autoconfigure.JustAuthProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 社交自动装配类
*
* @author timfruit
* @date 2021-10-30
*/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(JustAuthProperties.class)
public class YudaoSocialAutoConfiguration {
@Bean
@Primary
@ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
// 需要修改 HttpUtil 使用的实现,避免类报错
HttpUtil.setHttp(new HutoolImpl());
// 创建 YudaoAuthRequestFactory
return new YudaoAuthRequestFactory(properties, authStateCache);
}
}

View File

@ -1,14 +1,11 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
@ -20,8 +17,6 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -100,25 +95,8 @@ public class YudaoTenantAutoConfiguration {
// ========== Job ==========
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof JobHandler)) {
return bean;
}
// 有 TenantJob 注解的情况下,才会进行处理
if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
return bean;
}
// 使用 TenantJobHandlerDecorator 装饰
return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
}
};
public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
return new TenantJobAspect(tenantFrameworkService);
}
// ========== Redis ==========

View File

@ -8,7 +8,7 @@ import java.lang.annotation.Target;
/**
* 多租户 Job 注解
*/
@Target({ElementType.TYPE})
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TenantJob {
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 多租户 JobHandler AOP
* 任务执行时,会按照租户逐个执行 Job 的逻辑
*
* 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
*
* @author 芋道源码
*/
@Aspect
@RequiredArgsConstructor
@Slf4j
public class TenantJobAspect {
private final TenantFrameworkService tenantFrameworkService;
@Around("@annotation(tenantJob)")
public String around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) {
// 获得租户列表
List<Long> tenantIds = tenantFrameworkService.getTenantIds();
if (CollUtil.isEmpty(tenantIds)) {
return null;
}
// 逐个租户,执行 Job
Map<Long, String> results = new ConcurrentHashMap<>();
tenantIds.parallelStream().forEach(tenantId -> {
// TODO 芋艿:先通过 parallel 实现并行1多个租户是一条执行日志2异常的情况
TenantUtils.execute(tenantId, () -> {
try {
joinPoint.proceed();
} catch (Throwable e) {
results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
}
});
});
return JsonUtils.toJsonString(results);
}
}

View File

@ -1,58 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 多租户 JobHandler 装饰器
* 任务执行时,会按照租户逐个执行 Job 的逻辑
*
* 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
*
* @author 芋道源码
*/
@AllArgsConstructor
public class TenantJobHandlerDecorator implements JobHandler {
private final TenantFrameworkService tenantFrameworkService;
/**
* 被装饰的 Job
*/
private final JobHandler jobHandler;
@Override
public final String execute(String param) throws Exception {
// 获得租户列表
List<Long> tenantIds = tenantFrameworkService.getTenantIds();
if (CollUtil.isEmpty(tenantIds)) {
return null;
}
// 逐个租户,执行 Job
Map<Long, String> results = new ConcurrentHashMap<>();
tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行1多个租户是一条执行日志2异常的情况
try {
// 设置租户
TenantContextHolder.setTenantId(tenantId);
// 执行 Job
String result = jobHandler.execute(param);
// 添加结果
results.put(tenantId, result);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
TenantContextHolder.clear();
}
});
return JsonUtils.toJsonString(results);
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.tenant.core.redis;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
@ -15,7 +16,7 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
* @author airhead
*/
@Slf4j
public class TenantRedisCacheManager extends RedisCacheManager {
public class TenantRedisCacheManager extends TimeoutRedisCacheManager {
public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
RedisCacheConfiguration defaultCacheConfiguration) {

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 验证 job 租户逻辑
* {@link TenantJobHandlerDecorator}
*
* @author gaibu
*/
public class TenantJobTest extends BaseMockitoUnitTest {
@Mock
TenantFrameworkService tenantFrameworkService;
@Test
public void test() throws Exception {
// 准备测试租户 id
List<Long> tenantIds = Lists.newArrayList(1L, 2L, 3L);
// mock 数据
Mockito.doReturn(tenantIds).when(tenantFrameworkService).getTenantIds();
// 准备测试任务
TestJob testJob = new TestJob();
// 创建任务装饰器
TenantJobHandlerDecorator tenantJobHandlerDecorator = new TenantJobHandlerDecorator(tenantFrameworkService, testJob);
// 执行任务
tenantJobHandlerDecorator.execute(null);
// 断言返回值
assertEquals(testJob.getTenantIds(), tenantIds);
}
}

View File

@ -9,12 +9,12 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
@TenantJob // 标记多租户
public class TestJob implements JobHandler {
private final List<Long> tenantIds = new CopyOnWriteArrayList<>();
@Override
@TenantJob // 标记多租户
public String execute(String param) throws Exception {
tenantIds.add(TenantContextHolder.getTenantId());
return "success";
@ -24,4 +24,5 @@ public class TestJob implements JobHandler {
CollUtil.sort(tenantIds, Long::compareTo);
return tenantIds;
}
}

View File

@ -30,7 +30,7 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem
*/
public final void init() {
doInit();
log.info("[init][配置({}) 初始化完成]", config);
log.debug("[init][配置({}) 初始化完成]", config);
}
/**

View File

@ -0,0 +1,309 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import java.util.*;
/**
* 流程模型转操作工具类
*/
public class BpmnModelUtils {
/**
* 根据节点,获取入口连线
*
* @param source 起始节点
* @return 入口连线列表
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getIncomingFlows();
}
return new ArrayList<>();
}
/**
* 根据节点,获取出口连线
*
* @param source 起始节点
* @return 出口连线列表
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getOutgoingFlows();
}
return new ArrayList<>();
}
/**
* 获取流程元素信息
*
* @param model bpmnModel 对象
* @param flowElementId 元素 ID
* @return 元素信息
*/
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model
* @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* 把 bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
// ========== 遍历相关的方法 ==========
/**
* 找到 source 节点之前的所有用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @return 用户任务节点 数组
*/
public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 类型为用户节点,则新增父级节点
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 类型为子流程,则添加子流程开始节点出口处相连的节点
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 获取子流程用户任务节点
List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
}
}
// 继续迭代
userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 用户任务节点
*/
public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
*
* @param source 起始节点
* @param target 目标节点
* @param visitedElements 已经经过的连线的 ID用于判断线路是否重复
* @return 结果
*/
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
// 不能是开始事件和子流程
if (source instanceof StartEvent && isInEventSubprocess(source)) {
return false;
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (CollUtil.isEmpty(sequenceFlows)) {
return true;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
// 这条线路存在目标节点,这条线路完成,进入下个线路
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (target.getId().equals(sourceFlowElement.getId())) {
continue;
}
// 如果目标节点为并行网关,则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return false;
}
}
return true;
}
/**
* 判断当前节点是否属于不同的子流程
*
* @param flowElement 被判断的节点
* @return true 表示属于子流程
*/
private static boolean isInEventSubprocess(FlowElement flowElement) {
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
/**
* 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
*
* @param source 起始节点
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 子级任务节点列表
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
return userTaskList;
}
}

View File

@ -1,14 +1,7 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.identity.Authentication;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Flowable 相关的工具方法
*
@ -26,49 +19,6 @@ public class FlowableUtils {
Authentication.setAuthenticatedUserId(null);
}
// ========== BPMN 相关的工具方法 ==========
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model
* @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* 把 bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
// ========== Execution 相关的工具方法 ==========
public static String formatCollectionVariable(String activityId) {

View File

@ -1,21 +1,29 @@
package cn.iocoder.yudao.framework.quartz.config;
import cn.iocoder.yudao.framework.quartz.core.scheduler.SchedulerManager;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.Optional;
/**
* 定时任务 Configuration
*/
@AutoConfiguration
@EnableScheduling // 开启 Spring 自带的定时任务
@Slf4j
public class YudaoQuartzAutoConfiguration {
@Bean
public SchedulerManager schedulerManager(Scheduler scheduler) {
return new SchedulerManager(scheduler);
public SchedulerManager schedulerManager(Optional<Scheduler> scheduler) {
if (!scheduler.isPresent()) {
log.info("[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
return new SchedulerManager(null);
}
return new SchedulerManager(scheduler.get());
}
}

View File

@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
import org.quartz.*;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
/**
* {@link org.quartz.Scheduler} 的管理器,负责创建任务
*
@ -37,6 +40,7 @@ public class SchedulerManager {
public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression,
Integer retryCount, Integer retryInterval)
throws SchedulerException {
validateScheduler();
// 创建 JobDetail 对象
JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class)
.usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId)
@ -61,6 +65,7 @@ public class SchedulerManager {
public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression,
Integer retryCount, Integer retryInterval)
throws SchedulerException {
validateScheduler();
// 创建新 Trigger 对象
Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);
// 修改调度
@ -74,6 +79,7 @@ public class SchedulerManager {
* @throws SchedulerException 删除异常
*/
public void deleteJob(String jobHandlerName) throws SchedulerException {
validateScheduler();
scheduler.deleteJob(new JobKey(jobHandlerName));
}
@ -84,6 +90,7 @@ public class SchedulerManager {
* @throws SchedulerException 暂停异常
*/
public void pauseJob(String jobHandlerName) throws SchedulerException {
validateScheduler();
scheduler.pauseJob(new JobKey(jobHandlerName));
}
@ -94,6 +101,7 @@ public class SchedulerManager {
* @throws SchedulerException 启动异常
*/
public void resumeJob(String jobHandlerName) throws SchedulerException {
validateScheduler();
scheduler.resumeJob(new JobKey(jobHandlerName));
scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
}
@ -108,11 +116,12 @@ public class SchedulerManager {
*/
public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)
throws SchedulerException {
validateScheduler();
// 触发任务
JobDataMap data = new JobDataMap(); // 无需重试,所以不设置 retryCount 和 retryInterval
data.put(JobDataKeyEnum.JOB_ID.name(), jobId);
data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);
data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam);
// 触发任务
scheduler.triggerJob(new JobKey(jobHandlerName), data);
}
@ -127,4 +136,11 @@ public class SchedulerManager {
.build();
}
private void validateScheduler() {
if (scheduler == null) {
throw exception0(NOT_IMPLEMENTED.getCode(),
"[定时任务 - 已禁用][参考 https://doc.iocoder.cn/job/ 开启]");
}
}
}

View File

@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.Db;

View File

@ -26,11 +26,13 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@ -3,9 +3,11 @@ package cn.iocoder.yudao.framework.lock4j.config;
import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(before = LockAutoConfiguration.class)
@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j")
public class YudaoLock4jConfiguration {
@Bean

View File

@ -32,12 +32,6 @@
<artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>

View File

@ -45,7 +45,7 @@ public class TimeoutRedisCacheManager extends RedisCacheManager {
Duration duration = parseDuration(names[1]);
cacheConfig = cacheConfig.entryTtl(duration);
}
return super.createRedisCache(names[0], cacheConfig);
return super.createRedisCache(name, cacheConfig);
}
/**

View File

@ -55,13 +55,6 @@
<version>${revision}</version>
</dependency>
<!-- 服务保障相关 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
<scope>provided</scope> <!-- 设置为 provided主要是 GlobalExceptionHandler 使用 -->
</dependency>
<!-- xss -->
<dependency>
<groupId>org.jsoup</groupId>

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.jackson.config;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeSerializer;
@ -11,51 +12,41 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
@AutoConfiguration
@Slf4j
public class YudaoJacksonAutoConfiguration {
@Bean
public BeanPostProcessor objectMapperBeanPostProcessor() {
return new BeanPostProcessor() {
@SuppressWarnings("InstantiationOfUtilityClass")
public JsonUtils jsonUtils(List<ObjectMapper> objectMappers) {
// 1.1 创建 SimpleModule 对象
SimpleModule simpleModule = new SimpleModule();
simpleModule
// 新增 Long 类型序列化规则,数值超过 2^53-1在 JS 会出现精度丢失问题,因此 Long 自动序列化为字符串类型
.addSerializer(Long.class, NumberSerializer.INSTANCE)
.addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
// 新增 LocalDateTime 序列化、反序列化规则
.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
// 1.2 注册到 objectMapper
objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule));
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof ObjectMapper)) {
return bean;
}
ObjectMapper objectMapper = (ObjectMapper) bean;
SimpleModule simpleModule = new SimpleModule();
/*
* 1. 新增Long类型序列化规则数值超过2^53-1在JS会出现精度丢失问题因此Long自动序列化为字符串类型
* 2. 新增LocalDateTime序列化、反序列化规则
*/
simpleModule
.addSerializer(Long.class, NumberSerializer.INSTANCE)
.addSerializer(Long.TYPE, NumberSerializer.INSTANCE)
.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)
.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)
.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
objectMapper.registerModules(simpleModule);
JsonUtils.init(objectMapper);
log.info("初始化 jackson 自动配置");
return bean;
}
};
// 2. 设置 objectMapper 到 JsonUtils {
JsonUtils.init(CollUtil.getFirst(objectMappers));
log.info("[init][初始化 JsonUtils 成功]");
return new JsonUtils();
}
}

View File

@ -11,7 +11,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
@ -33,6 +32,7 @@ import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
@ -83,9 +83,6 @@ public class GlobalExceptionHandler {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
}
if (ex instanceof RequestNotPermitted) {
return requestNotPermittedExceptionHandler(request, (RequestNotPermitted) ex);
}
if (ex instanceof ServiceException) {
return serviceExceptionHandler((ServiceException) ex);
}
@ -186,8 +183,7 @@ public class GlobalExceptionHandler {
/**
* 处理 Resilience4j 限流抛出的异常
*/
@ExceptionHandler(value = RequestNotPermitted.class)
public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, RequestNotPermitted ex) {
public CommonResult<?> requestNotPermittedExceptionHandler(HttpServletRequest req, Throwable ex) {
log.warn("[requestNotPermittedExceptionHandler][url({}) 访问过于频繁]", req.getRequestURL(), ex);
return CommonResult.error(TOO_MANY_REQUESTS);
}
@ -226,7 +222,12 @@ public class GlobalExceptionHandler {
return tableNotExistsResult;
}
// 情况二:处理异常
// 情况二:部分特殊的库的处理
if (Objects.equals("io.github.resilience4j.ratelimiter.RequestNotPermitted", ex.getClass().getName())) {
return requestNotPermittedExceptionHandler(req, ex);
}
// 情况三:处理异常
log.error("[defaultExceptionHandler]", ex);
// 插入异常日志
this.createExceptionLog(req, ex);

View File

@ -21,6 +21,7 @@ import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.cr
@AutoConfiguration
@EnableConfigurationProperties(XssProperties.class)
@ConditionalOnProperty(prefix = "yudao.xss", name = "enable", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
/**

View File

@ -1,6 +1,5 @@
cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration