1、需要redis
2、原理:记录用户Token,接口名,请求参数 使用Hash算法压缩拼接 字符串防止生成的redis key过长。使用AOP切面编程
code:
注解:
import java.lang.annotation.*;
/**
* @className: PreventDuplication
* @description: 防止重复提交
* @author: lbw
* @createTime: 2022-10-19 09:40
* @version: 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventDuplication {
/**
* 防重复操作限时标记数值(存储redis限时标记数值)
*/
String value() default "value" ;
/**
* 防重复操作过期时间(借助redis实现限时控制)
*/
long expireSeconds() default 2;
}
AOP实现
import com.alibaba.fastjson.JSONObject;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springblade.common.annotation.PreventDuplication;
import org.springblade.common.cache.CacheNames;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.cache.BladeRedis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.DigestUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* 防止重复
*
* @author bingwu-liu
* @date 2022/10/19
*/
@Slf4j
@Aspect
@Component
public class PreventDuplicationAspect {
@Autowired
private BladeRedis bladeRedis;
/**
* 定义切点
*/
@Pointcut("@annotation(org.springblade.common.annotation.PreventDuplication)")
public void preventDuplication() {
}
/**
* 环绕通知 (可以控制目标方法前中后期执行操作,目标方法执行前后分别执行一些代码)
*
* @param joinPoint
* @return
*/
@Around("preventDuplication()")
public Object before(ProceedingJoinPoint joinPoint) throws Exception {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Assert.notNull(request, "request cannot be null.");
//获取执行方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取防重复提交注解
PreventDuplication annotation = method.getAnnotation(PreventDuplication.class);
// 获取token以及方法标记,生成redisKey和redisValue
String token = request.getHeader(TokenConstant.ACCESS_TOKEN);
if (token == null) {
token = "token";
}
String redisKey = CacheNames.PREVENT_DUPLICATION_PREFIX
.concat(token)
.concat(getMethodSign(method, joinPoint.getArgs()));
String redisValue = "1";
log.info("redisKey:[{}]",redisKey);
if (!bladeRedis.exists(redisKey)) {
//设置防重复操作限时标记(前置通知)
bladeRedis.setEx(redisKey, redisValue, annotation.expireSeconds());
try {
//正常执行方法并返回
//ProceedingJoinPoint类型参数可以决定是否执行目标方法,且环绕通知必须要有返回值,返回值即为目标方法的返回值
return joinPoint.proceed();
} catch (Throwable throwable) {
//确保方法执行异常实时释放限时标记(异常后置通知)
bladeRedis.del(redisKey);
throw new RuntimeException(throwable);
}
} else {
throw new ServiceException("请勿重复提交");
}
}
/**
* 生成方法标记:采用数字签名算法SHA1对方法签名字符串加签
*
* @param method
* @param args
* @return
*/
private String getMethodSign(Method method, Object... args) {
StringBuilder sb = new StringBuilder(method.toString());
for (Object arg : args) {
sb.append(toString(arg));
}
return DigestUtils.sha1DigestAsHex(sb.toString());
}
private String toString(Object arg) {
if (Objects.isNull(arg)) {
return "null";
}
if (arg instanceof Number) {
return arg.toString();
}
return JSONObject.toJSONString(arg);
}
}