mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-17 20:45:06 +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