From b300e5d47ba7ac2a19349394bf3d631214dbaeb1 Mon Sep 17 00:00:00 2001 From: weitang Date: Tue, 27 May 2025 11:30:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=A9=E5=B1=95=E4=B8=80=E4=B8=AA=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=98=B2=E9=87=8D=E5=A4=8D=E8=B0=83=E7=94=A8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E4=BD=BF=E7=94=A8Idempotent=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=8F=AF=E4=BB=A5=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- riis-system/pom.xml | 8 ++ .../yfd/platform/annotation/Idempotent.java | 46 +++++++++++ .../platform/annotation/RequestKeyParam.java | 20 +++++ .../yfd/platform/aspect/IdempotentAspect.java | 73 ++++++++++++++++++ .../controller/AlgorithmLogsController.java | 2 + .../platform/utils/RequestKeyGenerator.java | 76 +++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 riis-system/src/main/java/com/yfd/platform/annotation/Idempotent.java create mode 100644 riis-system/src/main/java/com/yfd/platform/annotation/RequestKeyParam.java create mode 100644 riis-system/src/main/java/com/yfd/platform/aspect/IdempotentAspect.java create mode 100644 riis-system/src/main/java/com/yfd/platform/utils/RequestKeyGenerator.java diff --git a/riis-system/pom.xml b/riis-system/pom.xml index e17185b..4243e98 100644 --- a/riis-system/pom.xml +++ b/riis-system/pom.xml @@ -224,6 +224,14 @@ TarsosDSP 2.4 + + + + org.redisson + redisson-spring-boot-starter + 3.40.2 + + diff --git a/riis-system/src/main/java/com/yfd/platform/annotation/Idempotent.java b/riis-system/src/main/java/com/yfd/platform/annotation/Idempotent.java new file mode 100644 index 0000000..68296f4 --- /dev/null +++ b/riis-system/src/main/java/com/yfd/platform/annotation/Idempotent.java @@ -0,0 +1,46 @@ +package com.yfd.platform.annotation; + +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: zcg + * @Description: 幂等注解 + * @Date: 2024/3/12 + **/ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Idempotent { + + /** + * 幂等的超时时间,默认为 1 秒 + * + * 注意,如果执行时间超过它,请求还是会进来 + */ + int timeout() default 1; + + /** + * 时间单位,默认为 SECONDS 秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * redis锁前缀 + * @return + */ + String keyPrefix() default "idempotent"; + + /** + * key分隔符 + * @return + */ + String delimiter() default "|"; + + /** + * 提示信息,正在执行中的提示 + */ + String message() default "重复请求,请稍后重试"; +} diff --git a/riis-system/src/main/java/com/yfd/platform/annotation/RequestKeyParam.java b/riis-system/src/main/java/com/yfd/platform/annotation/RequestKeyParam.java new file mode 100644 index 0000000..b86a012 --- /dev/null +++ b/riis-system/src/main/java/com/yfd/platform/annotation/RequestKeyParam.java @@ -0,0 +1,20 @@ +package com.yfd.platform.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @description 加上这个注解可以将参数设置为key + */ +@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface RequestKeyParam { + +} + diff --git a/riis-system/src/main/java/com/yfd/platform/aspect/IdempotentAspect.java b/riis-system/src/main/java/com/yfd/platform/aspect/IdempotentAspect.java new file mode 100644 index 0000000..d9d5225 --- /dev/null +++ b/riis-system/src/main/java/com/yfd/platform/aspect/IdempotentAspect.java @@ -0,0 +1,73 @@ +package com.yfd.platform.aspect; + +import cn.hutool.core.util.StrUtil; +import com.yfd.platform.annotation.Idempotent; +import com.yfd.platform.utils.RequestKeyGenerator; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import javax.annotation.Resource; +import java.lang.reflect.Method; + +/** + * @Author: zcg + * @Description: 幂等切面实现 + * @Date: 2024/3/12 + **/ +@Aspect +@Configuration +@Order(2) +@Slf4j +public class IdempotentAspect { + + @Resource + private RedissonClient redissonClient; + + @Around("execution(public * * (..)) && @annotation(com.yfd.platform.annotation.Idempotent)") + public Object interceptor(ProceedingJoinPoint joinPoint) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + Idempotent idempotent = method.getAnnotation(Idempotent.class); + if (StrUtil.isBlank(idempotent.keyPrefix())) { + throw new RuntimeException("重复提交前缀不能为空"); + } + //获取自定义key + final String lockKey = RequestKeyGenerator.getLockKey(joinPoint); + // 使用Redisson分布式锁的方式判断是否重复提交 + RLock lock = redissonClient.getLock(lockKey); + boolean isLocked = false; + try { + //尝试抢占锁 + isLocked = lock.tryLock(); + //没有拿到锁说明已经有了请求了 + if (!isLocked) { + throw new RuntimeException(idempotent.message()); + } + //拿到锁后设置过期时间 + lock.lock(idempotent.timeout(), idempotent.timeUnit()); + try { + return joinPoint.proceed(); + } catch (Throwable throwable) { + log.info("系统异常,", throwable); + throw new RuntimeException("系统异常," + throwable.getMessage()); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } finally { + //释放锁 + if (isLocked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} + + diff --git a/riis-system/src/main/java/com/yfd/platform/modules/algorithm/controller/AlgorithmLogsController.java b/riis-system/src/main/java/com/yfd/platform/modules/algorithm/controller/AlgorithmLogsController.java index f284767..8fbb4c0 100644 --- a/riis-system/src/main/java/com/yfd/platform/modules/algorithm/controller/AlgorithmLogsController.java +++ b/riis-system/src/main/java/com/yfd/platform/modules/algorithm/controller/AlgorithmLogsController.java @@ -2,6 +2,7 @@ package com.yfd.platform.modules.algorithm.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Idempotent; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.algorithm.domain.AlgorithmLogs; import com.yfd.platform.modules.algorithm.service.IAlgorithmLogsService; @@ -45,6 +46,7 @@ public class AlgorithmLogsController { // 算法分类,区域,间隔,部件 @PostMapping("/testQueryWrapperBuilder") @ApiOperation("测试构造器") + @Idempotent public ResponseResult testQueryWrapperBuilder(@RequestBody QueryCondition queryCondition) { Page page = new Page<>(); page.setSize(queryCondition.getSize()); diff --git a/riis-system/src/main/java/com/yfd/platform/utils/RequestKeyGenerator.java b/riis-system/src/main/java/com/yfd/platform/utils/RequestKeyGenerator.java new file mode 100644 index 0000000..c65b6d8 --- /dev/null +++ b/riis-system/src/main/java/com/yfd/platform/utils/RequestKeyGenerator.java @@ -0,0 +1,76 @@ +package com.yfd.platform.utils; + +import com.yfd.platform.annotation.Idempotent; +import com.yfd.platform.annotation.RequestKeyParam; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +/** + * @Author: zcg + * @Description: 生成LockKey + * @Date: 2024/3/12 + **/ +public class RequestKeyGenerator { + + /** + * 获取LockKey + * + * @param joinPoint 切入点 + * @return + */ + public static String getLockKey(ProceedingJoinPoint joinPoint) { + //获取连接点的方法签名对象 + MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); + //Method对象 + Method method = methodSignature.getMethod(); + //获取Method对象上的注解对象 + Idempotent idempotent = method.getAnnotation(Idempotent.class); + //获取方法参数 + final Object[] args = joinPoint.getArgs(); + //获取Method对象上所有的注解 + final Parameter[] parameters = method.getParameters(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < parameters.length; i++) { + final RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class); + //如果属性不是RequestKeyParam注解,则不处理 + if (keyParam == null) { + continue; + } + //如果属性是RequestKeyParam注解,则拼接 连接符 "& + RequestKeyParam" + sb.append(idempotent.delimiter()).append(args[i]); + } + //如果方法上没有加RequestKeyParam注解 + if (StringUtils.isEmpty(sb.toString())) { + //获取方法上的多个注解(为什么是两层数组:因为第二层数组是只有一个元素的数组) + final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + //循环注解 + for (int i = 0; i < parameterAnnotations.length; i++) { + final Object object = args[i]; + //获取注解类中所有的属性字段 + final Field[] fields = object.getClass().getDeclaredFields(); + for (Field field : fields) { + //判断字段上是否有RequestKeyParam注解 + final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class); + //如果没有,跳过 + if (annotation == null) { + continue; + } + //如果有,设置Accessible为true(为true时可以使用反射访问私有变量,否则不能访问私有变量) + field.setAccessible(true); + //如果属性是RequestKeyParam注解,则拼接 连接符" & + RequestKeyParam" + sb.append(idempotent.delimiter()).append(ReflectionUtils.getField(field, object)); + } + } + } + //返回指定前缀的key + return idempotent.keyPrefix() + sb; + } +} +