后端通过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);
    }

}

posted @ 2021-09-14 10:34  小白白白白白白白白白  阅读(184)  评论(0编辑  收藏  举报