1.防止重复提交注解
package com.risesun.common.core.annotation;
import java.lang.annotation.*;
/**
* @Author LiXiangrong
* @Description 防止重复提交注解
* @Date 2023/04/03 9:04:15
**/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
/**
* 防重复操作限时标记数值(存储redis限时标记数值)
*/
String value() default "value" ;
/**
* 防重复操作过期时间(借助redis实现限时控制)
*/
long expireSeconds() default 10;
}
2.AOP实现防止请求重复提交
package com.risesun.business.aspect;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.risesun.common.core.annotation.RepeatSubmit;
import com.risesun.common.core.constant.TokenConstants;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
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;
import java.util.concurrent.TimeUnit;
/**
* @Author LiXiangrong
* @Description AOP实现防止请求重复提交
* @Date 2023/04/03 8:41:04
**/
@Slf4j
@Component
@Aspect
public class NoRepeatSubmitAspect
{
@Autowired
private RedisTemplate redisTemplate;
private final String SIGN = "submit duplication";
private final String PREFIX = "PREVENT_DUPLICATION_PREFIX:";
/**
* @Author LiXiangrong
* @Description 定义切点
* @Date 2023/04/03 8:53:11
* @Return void
**/
@Pointcut("@annotation(com.risesun.common.core.annotation.RepeatSubmit)")
public void preventDuplication() {}
/**
* @Author LiXiangrong
* @Description 环绕方法
* @Date 2023/04/03 8:52:50
* @Param pjp
* @Return java.lang.Object
**/
@Around("NoRepeatSubmitAspect.preventDuplication()")
public Object around(ProceedingJoinPoint pjp) throws Throwable
{
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取执行方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
//获取防重复提交注解
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
// 获取authorization以及方法标记,生成redisKey和redisValue
String authorization = request.getHeader(TokenConstants.AUTHENTICATION);
String url = request.getRequestURI();
// 通过前缀 + url + token + 函数参数签名 来生成redis上的 key
String redisKey = PREFIX.concat(url).concat(authorization).concat(getMethodSign(method, pjp.getArgs()));
// 判断redisTemplate是否存在标记,如果没有,则是首次提交该请求,需要把设置标记并且正常返回
if (!redisTemplate.hasKey(redisKey) && Objects.nonNull(annotation))
{
// 这个值只是为了标记,不重要
String redisValue = redisKey.concat(annotation.value()).concat(SIGN);
// 设置防重复操作的限时标记(前置通知)
redisTemplate.opsForValue().set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS);
try
{
// 正常执行方法并返回 ProceedingJoinPoint类型参数可以决定是否执行目标方法,
// 环绕通知必须要有返回值,返回值即为目标方法的返回值
return pjp.proceed();
} catch (Throwable throwable)
{
//确保方法执行异常实时释放限时标记(异常后置通知)
redisTemplate.delete(redisKey);
throw throwable;
}
} else
{
throw new RuntimeException("数据正在处理,请勿重复提交!");
}
}
/**
* @Author LiXiangrong
* @Description 获取方法签名生成方法标记,采用数字签名算法SHA1对方法签名字符串加签
* @Date 2023/04/03 8:49:45
* @Param method
* @Param args
* @Return java.lang.String
**/
private String getMethodSign(Method method, Object... args)
{
StringBuilder sb = new StringBuilder(method.toString());
for (Object arg : args)
{
sb.append(toString(arg));
}
return DigestUtil.sha1Hex(sb.toString());
}
/**
* @Author LiXiangrong
* @Description 对参数重写toString方法,避免空参造成的影响
* @Date 2023/04/03 8:50:58
* @Param arg
* @Return java.lang.String
**/
private String toString(Object arg)
{
if (Objects.isNull(arg))
{
return "null";
}
if (arg instanceof Number)
{
return arg.toString();
}
return JSONUtil.toJsonStr(arg);
}
}
评论 (0)