欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-31 17:33:33
...

项目开发一个比较常见的需求就是防止重复提交,一般来说前端可以通过将提交按钮置灰等操作达到目的,但这个方案仍旧有一些缺陷,所以最好由后端来做控制。本文笔者将用自定义注解加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.MICROSECONDS);
        // 继续执行方法
        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当中,并且利用过期时间来设置防重复提交的间隔,然后继续执行用户请求的方法。

相关标签: 奇技淫巧