扩展一个接口防重复调用功能,使用Idempotent注解可以实现

This commit is contained in:
weitang 2025-05-27 11:30:21 +08:00
parent 077ceba48c
commit b300e5d47b
6 changed files with 225 additions and 0 deletions

View File

@ -224,6 +224,14 @@
<artifactId>TarsosDSP</artifactId>
<version>2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.40.2</version>
</dependency>
</dependencies>
<build>

View File

@ -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 "重复请求,请稍后重试";
}

View File

@ -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 {
}

View File

@ -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();
}
}
}
}

View File

@ -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<AlgorithmLogs> page = new Page<>();
page.setSize(queryCondition.getSize());

View File

@ -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;
}
}