实现逻辑
1.自定义防重复提交的注解和切面
2.在需要验证的接口上增加注解(一般是创建、修改的接口)
3.以每次调用的 用户唯一标识(userId或者sessionId或者token)+ 请求路径+参数 作为key,value任意值都可以,缓存起来(redis或本地缓存),并设置一个合适的缓存失效时间。
4.每次调用时根据key判断,缓存是否存在,存在则抛出异常或提示,不存在则执行业务逻辑
代码
防重复提交注解
**
package com.***.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>Description: [防重复提交注解]</p >
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 过期时长(毫秒)
*
* @return
*/
long expire() default 500;
}
注解处理实现类
**
package com.***.annotation;
import com.***.MD5Util;
import com.***.CacheKeyBuild;
import com.***.CacheService;
import com.***.ResultCodeEnum;
import com.***.RedisLock;
import com.***.ServiceException;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
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.util.Arrays;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {
private static final String TOKEN_KEY = "Authorization";
@Autowired
private CacheService cacheService;
@Autowired
private RedissonClient redissonClient;
@Pointcut("@annotation(com.fehorizon.erp.prepose.annotation.NoRepeatSubmit)")
public void serviceNoRepeat() {
// 这是一个标记方法
}
@Around("serviceNoRepeat()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader(TOKEN_KEY);
String url = request.getRequestURL().toString();
String params = Arrays.toString(pjp.getArgs());
String key = CacheKeyBuild.buildSysAccountKey(MD5Util.MD(token + "-" + url + "-" + params));
// 这里使用分布锁保证其线程安全(这里很关键),分布式锁实现可参考 [https://www.jianshu.com/p/d00999155166]
String lockKey = "Lock-" + key;
RLock lock = RedisLock.getLock(redissonClient, lockKey);
boolean res = RedisLock.lock(lock);
if(!res) {
throw new ServiceException(ResultCodeEnum.CODE_10007.code, ResultCodeEnum.CODE_10007.desc);
}
Optional<String> cacheValue = Optional.ofNullable(cacheService.getString(key));
log.info("======> " + Thread.currentThread() + "NoRepeatSubmitAop {} -> {}", key, cacheValue.isPresent());
if (!cacheValue.isPresent()) {
try {
Object o = pjp.proceed();
MethodSignature signature = (MethodSignature) pjp.getSignature();
NoRepeatSubmit noRepeatSubmit = signature.getMethod().getAnnotation(NoRepeatSubmit.class);
// 默认500毫秒内同一认证用户同一个地址同一个参数,视为重复提交
cacheService.set(key, "******", noRepeatSubmit.expire(), TimeUnit.MILLISECONDS);
return o;
}catch (ServiceException e){
log.error(e.getMessage(), e);
throw e;
}catch (Exception e){
log.error(ResultCodeEnum.CODE_10000.desc, e);
throw new ServiceException(ResultCodeEnum.CODE_10000.code, e.getMessage());
} finally {
RedisLock.unlock(lock);
}
} else {
RedisLock.unlock(lock);
throw new ServiceException(ResultCodeEnum.CODE_10007.code, ResultCodeEnum.CODE_10007.desc);
}
}
}
至此就完成了
————————————————
版权声明:本文为CSDN博主「葫芦胡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/HXNLYW/java/article/details/94560771
标题:使用注解@实现防止接口重复提交
作者:sharkshen@outlook.com
地址:https://linkjb.com/articles/2020/08/06/1596678264515.html
Comments | 0 条评论