第一步自定义注解,如下所示:
添加aop相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Target(ElementType.METHOD)//作用于方法上
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface NoRepeatSubmit {
/**
* 设置请求锁定时间 默认5秒
*/
int lockTime() default 3;
/**
* 当发生重复提交时候默认返回的错误信息
*/
String errMsg() default "重复提交,请 second 秒后重试";
}
第二步,基于spring aop原理,给加了此注解的接口做环绕通知,切面类如下:
package com.donlim.fms.common.aop;
import com.donlim.fms.annotation.NoRepeatSubmit;
import com.donlim.fms.exception.OutVO;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author 李安山
* @date 2023/6/1
* @version 1.0
*/
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Pointcut("@annotation(noRepeatSubmit)")
public void pointcut(NoRepeatSubmit noRepeatSubmit) {
}
@Around("@annotation(noRepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
log.warn("AOP防重复提交设置的加锁时间:{}", noRepeatSubmit.lockTime());
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// 请求的方法参数值 POST的JSON请求request.getParameter()是获取不到值的
Object[] args = joinPoint.getArgs();
JSONObject requestParams = ObjectUtils.isNotEmpty(args) ? JSONObject.fromObject(args[0]) : null;
log.warn("AOP防重复提交[" + request.getRequestURI() + "]接口的请求参数:{}",requestParams);
if (ObjectUtils.isNotEmpty(requestParams)){
String key = requestParams.toString();
log.info("key==》{}" ,key);
String s = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isNotEmpty(s)){
return OutVO.repeat("重复提交!");
}else {
stringRedisTemplate.opsForValue().set(key,"1",noRepeatSubmit.lockTime(), TimeUnit.SECONDS);
Object returnVale = joinPoint.proceed();
return returnVale;
}
}
return null;
}
/**
* @MethodName: tryLock
* @Param: [key --- key值, lockSeconds 时长]
* @Return: boolean 是否获取到
* @Author: Panguaxe
* @Date: 2020-05-26 16:13
* @Description: TODO 最终加强分布式锁
*/
private boolean tryLock(String key, int lockSeconds) {
//lambda表达式
return (Boolean) stringRedisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + lockSeconds + 1;
log.warn("失效时间:{}", expireAt);
Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
}
byte[] value = connection.get(key.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
if (Long.parseLong(new String(value)) < System.currentTimeMillis()) {
// 如果锁已经过期
byte[] oldValue = connection.getSet(key.getBytes(),String.valueOf(System.currentTimeMillis() + lockSeconds + 1).getBytes());
// 防止死锁
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
return false;
});
}
}
第三步:
给需要防止重复操作的接口上加上此注解即可