1.用于后端重复提交和重复查询控制,控制幂等性; 2.使用redis键值set机制 3.可用于分布式场景 实现步骤: 1.增加自定义注解@RepeatSubmit
/**
* 防止重复提交
*
* @author dyxu4
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RepeatSubmit {
/**
* 锁定时间,根据接口情况给定锁时间,单位毫秒
*
* @return 锁定时间
*/
long lockTime() default 1000;
/**
* 是否查询操作,默认否
*
* @return true/false 是否查询操作
*/
boolean isSelect() default false;
}
2.增加切面实现
/**
* 切面-防止重复提交
*
* @author dyxu4
*/
@Aspect
@Slf4j
@Component
public class RepeatSubmitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 加锁的key值开头
*/
private static final String LOCK_HEADER = "lock:";
/**
* 切点
*
* @param repeatSubmit 注解参数
*/
@Pointcut("@annotation(repeatSubmit)")
public void pointCut(RepeatSubmit repeatSubmit) {
}
/**
* 利用环绕通知进行处理重复提交问题
*
* @param pjp 切点信息
* @param repeatSubmit 注解信息
*/
@Around(value = "pointCut(repeatSubmit)", argNames = "pjp,repeatSubmit")
public Object around(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
//获取锁定的时间,单位毫秒
long lockSeconds = repeatSubmit.lockTime();
boolean isSelect = repeatSubmit.isSelect();
//获得request对象
HttpServletRequest request = httpServletRequest();
try {
Assert.notNull(request, "request can not null");
} catch (Exception e) {
throw new BusinessException("请求异常,请重试");
}
// 此处可以用token或者JSessionId
String token = request.getHeader("token");
String path = request.getServletPath();
String key = getKey(token, path);
String clientId = getClientId();
//锁定多少秒
boolean isSuccess = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, clientId, lockSeconds, TimeUnit.MILLISECONDS));
if (isSuccess) {
log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
// 获取锁成功, 执行进程
Object result;
try {
result = pjp.proceed();
} finally {
//解锁
redisTemplate.delete(key);
log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
}
return result;
} else {
// 获取锁失败,认为是重复提交的请求
log.info("tryLock fail, key = [{}]", key);
String returnMsg = "重复请求,请稍后再试!";
if (isSelect) {
returnMsg = "请勿频繁刷新,稍后再试!";
}
throw new BusinessException(returnMsg);
}
}
/**
* 获得request对象
*
* @return 客户端请求request
*/
private HttpServletRequest httpServletRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return Objects.requireNonNull(requestAttributes).getRequest();
}
/**
* 获得请求key
*
* @param token 用户token
* @param path 请求路径
* @return key
*/
private String getKey(String token, String path) {
return LOCK_HEADER + token + path;
}
/**
* 获得uuid
*
* @return 随机uuid
*/
private String getClientId() {
return UUID.randomUUID().toString();
}
}
3.将注解添加到想要控制的接口(controller)
/**
* 系统任务分页列表
*
* @return PageVue<TaskListVo>
*/
@PostMapping("/page")
@RepeatSubmit(lockTime = 2000,isSelect = true)
@OperationLog(name = "系统任务分页列表", type = OperationLogType.LIST)
@ApiOperation(value = "系统任务分页列表", response = TaskPageListVo.class)
public ApiResult<PageVue<TaskPageListVo>> getTaskPage(@Validated @RequestBody TaskPageParam taskPageParam) {
LoginUserVo loginUserVo = LoginUtil.getLoginUserVo();
PageVue<TaskPageListVo> pageVue = taskApplicationService.getTaskPage(loginUserVo, taskPageParam);
return ApiResult.ok(pageVue);
}