后端通过Aop实现防止请求重复提交功能
简单说一下思路,通过AspectJ环绕通知,拦截所有加了@RequestMapping注解的方法,(@GetMapping,@PostMapping等是@RequestMapping的子类,也会被拦截)
使用环绕通知在拦截方法之前将该请求的url,请求类型,参数,当前用户id组合作为唯一标识存到redis,
拦截方法之后将redis中该数值删除,在此添加删除期间,如果用户发起相同请求,则会被系统拒绝。
话不多说,上代码
/**
* request方法拦截切面类
*
* @author wangmeng
* @since 2021/8/2
*/
@EnableAspectJAutoProxy
@Component
@Aspect
@Slf4j
@Order(1)
public class RequestCommitAop {
private final static String REQUEST_COMMIT = "request_commit";
private final static String PARAMS = "params";
/**
* request请求拦截方法
*
* @param joinPoint 切入点
* @param requestMapping 该方法requestMapping注解
* @return 方法返回值
*/
@Around("execution(* com.vanew.trade.erp.web.controller..*.*(..)) && @within(requestMapping)")
public Object requestCommit(ProceedingJoinPoint joinPoint, RequestMapping requestMapping) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
//不拦截get及参数含file的请求
boolean containFile = Arrays.stream(joinPoint.getArgs()).anyMatch(item -> item instanceof MultipartFile || item instanceof MultipartFile[]);
if (RequestMethod.GET.toString().equals(request.getMethod()) || containFile) {
return joinPoint.proceed(joinPoint.getArgs());
}
String redisKey = beforeRequest(joinPoint, request);
//执行对应方法
Object result = joinPoint.proceed(joinPoint.getArgs());
//删除缓存
afterRequest(redisKey);
return result;
}
/**
* 检验请求是否三秒内提交
*
* @param joinPoint
* @param request request
*/
public String beforeRequest(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
// 请求基本信息
String method = request.getMethod();
String uri = request.getRequestURI();
Object[] filterArgs = Arrays.stream(joinPoint.getArgs()).filter(item -> !(item instanceof ServletResponse) && !(item instanceof ServletRequest)).toArray();
String paramJson = JsonUtils.toStr(filterArgs);
String redis_key = getRedisKey(UserContextUtil.getUserId(), method, uri);
String redisResult = (String) RedisUtils.hget(REQUEST_COMMIT, redis_key);
RequestCommitOvertimeDto redisObject = JsonUtils.toObject(redisResult, RequestCommitOvertimeDto.class);
//删除旧的redisKey
if (redisObject == null) {
RedisUtils.hset(REQUEST_COMMIT, redis_key, JsonUtils.toStr(new RequestCommitOvertimeDto(System.currentTimeMillis(), paramJson)));
return redis_key;
}
String overtime = DictUtils.getDictValue(DictConstant.REQUEST_COMMIT_CHECK_TIME, DictConstant.REQUEST_COMMIT_CHECK_TIME, "5");
if (StringUtils.equals(redisObject.getParams(), paramJson) && (System.currentTimeMillis() - redisObject.getTimeMillis()) / 1000 < Integer.parseInt(overtime)) {
log.info("request请求" + uri + "重复提交!");
throw new CustomException("该请求三秒内已提交,请勿重复请求!");
}
//设置缓存
RedisUtils.hset(REQUEST_COMMIT, redis_key, JsonUtils.toStr(new RequestCommitOvertimeDto(System.currentTimeMillis(), paramJson)));
return redis_key;
}
/**
* 获取redis 对应的key
*
* @param userId
* @param methodType
* @param uri
* @return
*/
private String getRedisKey(String userId, String methodType, String uri) {
return userId + "_" + methodType + "_" + uri;
}
/**
* request请求之后删除缓存
*/
public void afterRequest(String redisKey) {
//移除对应redis key
RedisUtils.hdel(REQUEST_COMMIT, redisKey);
}
}