mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	项目结构调整 x 16 : 将 monitor、sms、dict 等组件拆分出去
This commit is contained in:
		@@ -0,0 +1,42 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.config;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.aop.IdempotentAspect;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.redis.IdempotentRedisDAO;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 | 
			
		||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Configuration(proxyBeanMethods = false)
 | 
			
		||||
@AutoConfigureAfter(RedisAutoConfiguration.class)
 | 
			
		||||
public class IdempotentConfiguration {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public IdempotentAspect idempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
 | 
			
		||||
        return new IdempotentAspect(keyResolvers, idempotentRedisDAO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) {
 | 
			
		||||
        return new IdempotentRedisDAO(stringRedisTemplate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ========== 各种 IdempotentKeyResolver Bean ==========
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() {
 | 
			
		||||
        return new DefaultIdempotentKeyResolver();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() {
 | 
			
		||||
        return new ExpressionIdempotentKeyResolver();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.annotation;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 幂等注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Target({ElementType.METHOD})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface Idempotent {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 幂等的超时时间,默认为 1 秒
 | 
			
		||||
     *
 | 
			
		||||
     * 注意,如果执行时间超过它,请求还是会进来
 | 
			
		||||
     */
 | 
			
		||||
    int timeout() default 1;
 | 
			
		||||
    /**
 | 
			
		||||
     * 时间单位,默认为 SECONDS 秒
 | 
			
		||||
     */
 | 
			
		||||
    TimeUnit timeUnit() default TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 提示信息,正在执行中的提示
 | 
			
		||||
     */
 | 
			
		||||
    String message() default "重复请求,请稍后重试";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用的 Key 解析器
 | 
			
		||||
     */
 | 
			
		||||
    Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用的 Key 参数
 | 
			
		||||
     */
 | 
			
		||||
    String keyArg() default "";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.aop;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.redis.IdempotentRedisDAO;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Before;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 拦截声明了 {@link Idempotent} 注解的方法,实现幂等操作
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Aspect
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class IdempotentAspect {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * IdempotentKeyResolver 集合
 | 
			
		||||
     */
 | 
			
		||||
    private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;
 | 
			
		||||
 | 
			
		||||
    private final IdempotentRedisDAO idempotentRedisDAO;
 | 
			
		||||
 | 
			
		||||
    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
 | 
			
		||||
        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);
 | 
			
		||||
        this.idempotentRedisDAO = idempotentRedisDAO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Before("@annotation(idempotent)")
 | 
			
		||||
    public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
 | 
			
		||||
        // 获得 IdempotentKeyResolver
 | 
			
		||||
        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
 | 
			
		||||
        Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver");
 | 
			
		||||
        // 解析 Key
 | 
			
		||||
        String key = keyResolver.resolver(joinPoint, idempotent);
 | 
			
		||||
 | 
			
		||||
        // 锁定 Key。
 | 
			
		||||
        boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());
 | 
			
		||||
        // 锁定失败,抛出异常
 | 
			
		||||
        if (!success) {
 | 
			
		||||
            log.info("[beforePointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
 | 
			
		||||
            throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.keyresolver;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 幂等 Key 解析器接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public interface IdempotentKeyResolver {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 解析一个 Key
 | 
			
		||||
     *
 | 
			
		||||
     * @param idempotent 幂等注解
 | 
			
		||||
     * @param joinPoint  AOP 切面
 | 
			
		||||
     * @return Key
 | 
			
		||||
     */
 | 
			
		||||
    String resolver(JoinPoint joinPoint, Idempotent idempotent);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.hutool.crypto.SecureUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 默认幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
 | 
			
		||||
 *
 | 
			
		||||
 * 为了避免 Key 过长,使用 MD5 进行“压缩”
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
 | 
			
		||||
        String methodName = joinPoint.getSignature().toString();
 | 
			
		||||
        String argsStr = StrUtil.join(",", joinPoint.getArgs());
 | 
			
		||||
        return SecureUtil.md5(methodName + argsStr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
 | 
			
		||||
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.reflect.MethodSignature;
 | 
			
		||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
 | 
			
		||||
import org.springframework.core.ParameterNameDiscoverer;
 | 
			
		||||
import org.springframework.expression.Expression;
 | 
			
		||||
import org.springframework.expression.ExpressionParser;
 | 
			
		||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
 | 
			
		||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 基于 Spring EL 表达式,
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
 | 
			
		||||
 | 
			
		||||
    private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
 | 
			
		||||
    private final ExpressionParser expressionParser = new SpelExpressionParser();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
 | 
			
		||||
        // 获得被拦截方法参数名列表
 | 
			
		||||
        Method method = getMethod(joinPoint);
 | 
			
		||||
        Object[] args = joinPoint.getArgs();
 | 
			
		||||
        String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
 | 
			
		||||
        // 准备 Spring EL 表达式解析的上下文
 | 
			
		||||
        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
 | 
			
		||||
        if (ArrayUtil.isNotEmpty(parameterNames)) {
 | 
			
		||||
            for (int i = 0; i < parameterNames.length; i++) {
 | 
			
		||||
                evaluationContext.setVariable(parameterNames[i], args[i]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 解析参数
 | 
			
		||||
        Expression expression = expressionParser.parseExpression(idempotent.keyArg());
 | 
			
		||||
        return expression.getValue(evaluationContext, String.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Method getMethod(JoinPoint point) {
 | 
			
		||||
        // 处理,声明在类上的情况
 | 
			
		||||
        MethodSignature signature = (MethodSignature) point.getSignature();
 | 
			
		||||
        Method method = signature.getMethod();
 | 
			
		||||
        if (!method.getDeclaringClass().isInterface()) {
 | 
			
		||||
            return method;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 处理,声明在接口上的情况
 | 
			
		||||
        try {
 | 
			
		||||
            return point.getTarget().getClass().getDeclaredMethod(
 | 
			
		||||
                    point.getSignature().getName(), method.getParameterTypes());
 | 
			
		||||
        } catch (NoSuchMethodException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent.core.redis;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 幂等 Redis DAO
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class IdempotentRedisDAO {
 | 
			
		||||
 | 
			
		||||
    private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
 | 
			
		||||
            "idempotent:%s", // 参数为 uuid
 | 
			
		||||
            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
 | 
			
		||||
 | 
			
		||||
    private final StringRedisTemplate redisTemplate;
 | 
			
		||||
 | 
			
		||||
    public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) {
 | 
			
		||||
        String redisKey = formatKey(key);
 | 
			
		||||
        return redisTemplate.opsForValue().setIfAbsent(redisKey, "", timeout, timeUnit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String formatKey(String key) {
 | 
			
		||||
        return String.format(IDEMPOTENT.getKeyTemplate(), key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现
 | 
			
		||||
 * 实现原理是,相同参数的方法,一段时间内,有且仅能执行一次。通过这样的方式,保证幂等性。
 | 
			
		||||
 *
 | 
			
		||||
 * 使用场景:例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。
 | 
			
		||||
 *
 | 
			
		||||
 * 和 it4alla/idempotent 组件的差异点,主要体现在两点:
 | 
			
		||||
 *  1. 我们去掉了 @Idempotent 注解的 delKey 属性。原因是,本质上 delKey 为 true 时,实现的是分布式锁的能力
 | 
			
		||||
 * 此时,我们偏向使用 Lock4j 组件。原则上,一个组件只提供一种单一的能力。
 | 
			
		||||
 *  2. 考虑到组件的通用性,我们并未像 it4alla/idempotent 组件一样使用 Redisson RMap 结构,而是直接使用 Redis 的 String 数据格式。
 | 
			
		||||
 */
 | 
			
		||||
package cn.iocoder.yudao.framework.idempotent;
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.lock4j.config;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.ClassUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.lock4j.core.DefaultLockFailureStrategy;
 | 
			
		||||
import cn.iocoder.yudao.framework.lock4j.core.Lock4jRedisKeyConstants;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
 | 
			
		||||
@Configuration
 | 
			
		||||
public class Lock4jConfiguration {
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
 | 
			
		||||
        // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
 | 
			
		||||
        ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public DefaultLockFailureStrategy lockFailureStrategy() {
 | 
			
		||||
        return new DefaultLockFailureStrategy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.lock4j.core;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 | 
			
		||||
import com.baomidou.lock.LockFailureStrategy;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class DefaultLockFailureStrategy implements LockFailureStrategy {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
 | 
			
		||||
        log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout);
 | 
			
		||||
        throw new ServiceException(GlobalErrorCodeConstants.LOCKED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.lock4j.core;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 | 
			
		||||
import org.redisson.api.RLock;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Lock4j Redis Key 枚举类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
public interface Lock4jRedisKeyConstants {
 | 
			
		||||
 | 
			
		||||
    RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
 | 
			
		||||
            "lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类
 | 
			
		||||
            HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 分布式锁组件,使用 https://gitee.com/baomidou/lock4j 开源项目
 | 
			
		||||
 */
 | 
			
		||||
package cn.iocoder.yudao.framework.lock4j;
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 使用 Resilience4j 组件,实现服务保障,包括:
 | 
			
		||||
 * 1. 熔断器
 | 
			
		||||
 * 2. 限流器
 | 
			
		||||
 * 3. 舱壁隔离
 | 
			
		||||
 * 4. 重试
 | 
			
		||||
 * 5. 限时器
 | 
			
		||||
 */
 | 
			
		||||
package cn.iocoder.yudao.framework.resilience4j;
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
<https://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao>
 | 
			
		||||
		Reference in New Issue
	
	Block a user