diff --git a/接口防抖防重复提交/woniu-springboot-interface-demo-master/pom.xml b/接口防抖防重复提交/woniu-springboot-interface-demo-master/pom.xml index 071937b..bc3d89d 100644 --- a/接口防抖防重复提交/woniu-springboot-interface-demo-master/pom.xml +++ b/接口防抖防重复提交/woniu-springboot-interface-demo-master/pom.xml @@ -23,6 +23,12 @@ spring-boot-starter-web + + org.redisson + redisson-spring-boot-starter + 3.10.6 + + mysql diff --git a/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/config/RedissonConfig.java b/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/config/RedissonConfig.java new file mode 100644 index 0000000..3d9993d --- /dev/null +++ b/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/config/RedissonConfig.java @@ -0,0 +1,22 @@ +package com.woniu.demo.config; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedissonConfig { + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + // 这里假设你使用单节点的Redis服务器 + config.useSingleServer() + // 使用与Spring Data Redis相同的地址 + .setAddress("redis://127.0.0.1:6379"); + return Redisson.create(config); + } +} + diff --git a/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/redisson/RedissonRequestLockAspect.java b/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/redisson/RedissonRequestLockAspect.java new file mode 100644 index 0000000..aa102da --- /dev/null +++ b/接口防抖防重复提交/woniu-springboot-interface-demo-master/src/main/java/com/woniu/demo/redisson/RedissonRequestLockAspect.java @@ -0,0 +1,126 @@ +package com.woniu.demo.redisson; + +import com.woniu.demo.annotation.RequestKeyParam; +import com.woniu.demo.annotation.RequestLock; +import com.woniu.demo.exception.biz.BizException; +import com.woniu.demo.model.response.ResponseCodeEnum; +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 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; + +/** + * 接口优化,如何接口防抖(防重复提交)? + * 分布式锁实现方案! + * @description + */ +@Aspect +@Configuration +@Order(2) +public class RedissonRequestLockAspect { + + private RedissonClient redissonClient; + + @Autowired + public RedissonRequestLockAspect(RedissonClient redissonClient) { + this.redissonClient = redissonClient; + } + + @Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)") + public Object interceptor(ProceedingJoinPoint joinPoint) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + RequestLock requestLock = method.getAnnotation(RequestLock.class); + if (StringUtils.isEmpty(requestLock.prefix())) { + throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "重复提交前缀不能为空"); + } + //获取自定义key + final String lockKey = getLockKey(joinPoint); + // 使用Redisson分布式锁的方式判断是否重复提交 + RLock lock = redissonClient.getLock(lockKey); + boolean isLocked = false; + try { + //尝试抢占锁 + isLocked = lock.tryLock(); + //没有拿到锁说明已经有了请求了 + if (!isLocked) { + throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试"); + } + //拿到锁后设置过期时间 + lock.lock(requestLock.expire(), requestLock.timeUnit()); + try { + return joinPoint.proceed(); + } catch (Throwable throwable) { + throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系统异常"); + } + } catch (Exception e) { + throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试"); + } finally { + //释放锁 + if (isLocked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + + } + + public String getLockKey(ProceedingJoinPoint joinPoint) { + //获取连接点的方法签名对象 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + //Method对象 + Method method = methodSignature.getMethod(); + //获取Method对象上的注解对象 + RequestLock requestLock = method.getAnnotation(RequestLock.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(requestLock.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(requestLock.delimiter()).append(ReflectionUtils.getField(field, object)); + } + } + } + //返回指定前缀的key + return requestLock.prefix() + sb; + } +}