防重复提交注解:Resubmit
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 重复提交标注注解
*
* @author Neo
* @since 2021/12/13 10:05
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resubmit {
/**
* 锁定时间(单位:秒,默认:10s)
*/
int seconds() default 10;
}
防重复提交生成 token 工具类:ResubmitUtils
import cn.hutool.crypto.digest.MD5;
import com.google.common.collect.Lists;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.InputStreamSource;
import java.io.File;
import java.util.List;
import java.util.Objects;
/**
* 重复提交工具类
*
* @author Neo
* @since 2021/12/13 10:01
*/
public class ResubmitUtils {
private ResubmitUtils() {
}
/**
* 重复提交标记
*/
public static final String RESUBMIT_KEY = "resubmit:";
/**
* 默认锁定时间:10s
*/
public static final int DEFAULT_LOCK_SECOND = 10;
private static final ResubmitExclusionStrategy EXCLUSION_STRATEGY;
private static final Gson RESUBMIT_GSON;
static {
EXCLUSION_STRATEGY = new ResubmitExclusionStrategy();
RESUBMIT_GSON = new GsonBuilder()
.serializeNulls()
.addSerializationExclusionStrategy(EXCLUSION_STRATEGY)
.create();
}
/**
* 生成重复提交 Token
* 将参数序列化后进行 MD5 加密
*
* @author Neo
* @since 2021/12/14 14:05
*/
public static String token(Object o) {
if (Objects.isNull(o)) {
return StringUtils.EMPTY;
}
String json = RESUBMIT_GSON.toJson(o);
return MD5.create().digestHex(json);
}
/**
* 重复提交序列化策略
*
* @author Neo
* @since 2021/12/14 14:03
*/
private static class ResubmitExclusionStrategy implements ExclusionStrategy {
public static final List<Class<?>> EXCLUSION_CLASS = Lists.newArrayList(
InputStreamSource.class,
File.class
);
@Override
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
for (Class<?> exclusionClass : EXCLUSION_CLASS) {
if (exclusionClass.isAssignableFrom(clazz)) {
return true;
}
}
return false;
}
}
}
防重复提交拦截器:ResubmitInterceptor
import org.aspectj.lang.JoinPoint;
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.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 重复提交拦截器
*
* @author Neo
* @since 2021/12/13 10:03
*/
@Aspect
@Component
public class ResubmitInterceptor {
@Resource(name = "distributedLock")
private ILock distributedLock;
@Pointcut("@annotation(com.ceair.shoppingservice.common.util.resubmit.Resubmit)")
public void pointcutOfAnnotation() {
}
@Around("pointcutOfAnnotation()")
public Object around(ProceedingJoinPoint joinPoint) {
String key = lockKey(joinPoint);
int lockSecond = lockSecond(joinPoint);
boolean locked = distributedLock.tryLock(key, lockSecond);
BaseBusinessException.ifThrow(!locked, ResultCode.RESUBMIT_EXCEPTION);
try {
return joinPoint.proceed();
} catch (BaseBusinessException e) {
distributedLock.unlock(key);
throw e;
} catch (Throwable throwable) {
distributedLock.unlock(key);
SysLogHelper.error("重复提交捕获业务处理异常", throwable);
throw new BaseBusinessException(ResultCode.SYSTEM_EXCEPTION);
}
}
/**
* 方法名
*
* @author Neo
* @since 2021/12/14 14:26
*/
public static String methodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
}
/**
* 锁定 Key
*
* @author Neo
* @since 2021/12/14 15:11
*/
public static String lockKey(JoinPoint joinPoint) {
String methodName = methodName(joinPoint);
List<Object> params = new ArrayList<>();
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
continue;
}
params.add(arg);
}
String token = ResubmitUtils.token(params);
return ResubmitUtils.RESUBMIT_KEY + methodName + ":" + token;
}
/**
* 获取锁定时间
*
* @author Neo
* @since 2021/12/14 15:03
*/
public static int lockSecond(JoinPoint joinPoint) {
try {
Class<?> targetClass = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = targetClass.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
Resubmit annotation = targetMethod.getAnnotation(Resubmit.class);
return Objects.isNull(annotation) ? ResubmitUtils.DEFAULT_LOCK_SECOND : annotation.seconds();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return ResubmitUtils.DEFAULT_LOCK_SECOND;
}
}
需要保证 ResubmitInterceptor 被 spring 容器加载到