注解定义
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 防止重复提交注解
* @author
*/
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RejectRepeatedSubmit {
/**
* 防止重复提交锁的过期时间
*
* @return 重复提交锁的过期时间
*/
long duration() default 500;
/**
* 时长单位
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 重试次数,0 代表不重试,默认不重试
*
* @return 重试次数
*/
int retryTimes() default 0;
/**
* 锁定类型 0 处理完成后解锁, 1 处理完成不解锁,等待自动过期
* @return 锁定类型
*/
int lockType() default 0;
}
AOP切面定义
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 防止重复提交切面
* @author
*/
@Log4j2
@Component
@Aspect
public class RejectRepeatedSubmitAspect {
@Autowired
private SimpleRedisLockUtil simpleRedisLockUtil;
@Pointcut("@annotation(com.yunti.wanmo.annotation.RejectRepeatedSubmit)")
public void pointCut() {
}
@Around("pointCut()")
public Object invoke(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
String simpleName = signature.getDeclaringType().getName().replace(".", ":");
String methodName = simpleName + ":" + method.getName();
RejectRepeatedSubmit annotation = method.getAnnotation(RejectRepeatedSubmit.class);
String keyPrefix = RedisConstant.genKey(RedisConstant.NAMESPACE_WANMO, RedisConstant.TYPE_STRING, RedisBizConstant.REJECT_REPEATED_SUBMIT_PREFIX);
String uniqueKey = keyPrefix + getUniqueKey(point.getArgs(), annotation, methodName);
boolean lock = false;
try {
for (int retry = annotation.retryTimes() + 1; retry > 0; --retry) {
lock = this.simpleRedisLockUtil.lock(uniqueKey, annotation.timeUnit().toMillis(annotation.duration()));
if (lock) {
break;
}
}
if (lock) {
return point.proceed();
}
throw new BizException("操作频繁,请稍后再试...");
} finally {
if (lock && Objects.equals(0, annotation.lockType())) {
simpleRedisLockUtil.unLock(uniqueKey);
}
}
}
/**
* 注解与参数配合获取唯一标识
*
* @param args The method params
* @param annotation {@link RejectRepeatedSubmit}
* @param methodName
* @return
*/
private String getUniqueKey(Object[] args, RejectRepeatedSubmit annotation, String methodName) {
StringBuilder uniqueKey = new StringBuilder(methodName);
if (args.length > 0) {
for (Object arg : args) {
Class<?> paramClass = null;
if (Objects.nonNull(arg)) {
paramClass = arg.getClass();
}
String processedParam = null;
try {
processedParam = processParams(arg, paramClass, true);
} catch (Exception e) {
log.error("参数处理异常", e);
}
if (Objects.nonNull(processedParam)) {
uniqueKey.append("@");
uniqueKey.append(processedParam);
}
}
}
return uniqueKey.toString();
}
/**
* 处理参数
*
* @param param The method param
* @param paramClass param class type
* @return 带全类名+方法名+唯一标识的键
*/
private <P> String processParams(Object param, Class<P> paramClass, boolean isContinue) {
String uniqueKey;
// 处理参数 基本类型
if (Objects.isNull(param)) {
uniqueKey = "null";
} else if (param instanceof LoginDTO) {
uniqueKey = ((LoginDTO) param).getUserId().toString();
} else if (paramClass.isPrimitive()) {
uniqueKey = String.valueOf(param);
} else if (param instanceof String) {
// String 类型
uniqueKey = (String) param;
} else if (param instanceof Number) {
// 数值类型
uniqueKey = String.valueOf(param);
} else {
if (!paramClass.getName().contains("com.yunti")) {
return null;
}
if (isContinue) {
StringBuilder obj = new StringBuilder();
Field[] fields = param.getClass().getDeclaredFields();
for (int i = 0, size = fields.length; i < size; i++) {
if (i > 0) {
obj.append("@");
}
try {
fields[i].setAccessible(true);
Object value = fields[i].get(param);
String objParam = processParams(value, value.getClass(), false);
if (Objects.nonNull(objParam)) {
obj.append(objParam);
}
} catch (IllegalAccessException e) {
log.error("参数处理异常", e);
}
}
return obj.toString();
} else {
// 对象,暂时md5值判断
String jsonString = JSON.toJSONString(param);
uniqueKey = DigestUtils.md5DigestAsHex(jsonString.getBytes());
}
}
return uniqueKey;
}
}