利用自定义注解+aop+redis防止重复提交

项目开发一个比较常见的需求就是防止重复提交,一般来说前端可以通过将提交按钮置灰等操作达到目的,但这个方案仍旧有一些缺陷,黑客可以绕过前端js直接向后台发送连续请求,所以最好由后端来做控制。本文笔者将用自定义注解加redis和aop来实现。
1 、我的项目是springboot项目,首先添加redis和aspectj的依赖。

<dependency>
   <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

2 、添加自定义注解,之后想让哪一个方法防止重复提交,只需要在这个方法上加上这个自定义注解便可。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidDuplicateSubmit {
    /**
     * 指定时间内不可重复提交,单位毫秒
     * @return
     */
    long timeout() default 5000;
}

3 、添加处理注解的切面类

@Component
@Aspect // 定义切面类
public class AvoidDuplicateSubmitAspect {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Around("@annotation(com.chengxi.examples.annotation.AvoidDuplicateSubmit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取request对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String ip = Iputil.getIp(request);
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String ipKey = String.format("%s#%s", className, methodName);
        int hashCode = Math.abs(ipKey.hashCode());
        // 拼接redisKey,如:127.0.0.1_1898984393
        String redisKey = String.format("%s_%d", ip, hashCode);

        String value = (String) redisTemplate.opsForValue().get(redisKey);
        if (!StringUtils.isEmpty(value)) {
            return ReturnJson.fail("请勿重复提交");
        }

        // 获取注解
        AvoidDuplicateSubmit avoidDuplicateSubmit = method.getAnnotation(AvoidDuplicateSubmit.class);
        long timeout = avoidDuplicateSubmit.timeout();
        if (timeout < 0) {
            timeout = 5000;
        }
        // 第一次提交,插入redis
        redisTemplate.opsForValue().set(redisKey, UUID.randomUUID().toString(), timeout, TimeUnit.MILLISECONDS);
        // 继续执行方法
        return joinPoint.proceed();
    }
}

获取ip的Iputil:

public class Iputil {
    public static String getIp(HttpServletRequest request) {
        String unknown = "unknown";
        if (request == null) {
            return unknown;
        }
        
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        
        return ip;
    }
}

其中,我们利用自定义注解@AvoidDuplicateSubmit 标记了需要防止重复提交的方法,用户请求该方法时,利用Spring Aop来捕获相关的信息。如类名,方法名,以及ip,通过这三个信息,按一定规则组合成一个key,到redis里去查询是否存在值。若存在,说明规定时间内已经提交过一次,所以返回错误信息。否则则将该key存入redis当中,并且利用过期时间来设置防重复提交的间隔,然后继续执行用户请求的方法。

你可能感兴趣的:(奇技淫巧,重复提交,AOP,自定义注解,redis)