返回

SpringBoot自定义注解+AOP防止重复提交(保姆级教程,建议收藏)

后端

前言

在实际开发中,我们经常会遇到这样的场景:用户连续点击按钮,导致后端接口被重复调用,从而产生重复的数据。这不仅会给数据库带来压力,还会给业务逻辑带来混乱。为了防止这种情况的发生,我们需要对接口进行防重复提交处理。

重复提交产生的原因

重复提交产生的原因有很多,主要有以下几种:

  • 网络延迟: 在网络延迟的情况下,用户可能会在一段时间内连续点击按钮,导致后端接口被重复调用。
  • 页面卡顿: 如果页面的加载速度较慢,用户可能会在页面卡顿的情况下连续点击按钮,导致后端接口被重复调用。
  • 浏览器缓存: 如果浏览器的缓存策略不当,可能会导致用户在刷新页面后再次提交表单,从而导致后端接口被重复调用。
  • 恶意攻击: 恶意攻击者可能会利用工具或脚本对接口进行重复提交攻击,从而导致后端接口被重复调用。

SpringBoot如何防止重复提交

SpringBoot提供了多种方法来防止重复提交,主要有以下几种:

  • 分布式锁: 使用分布式锁可以保证在同一时刻只有一个请求能够访问资源,从而防止重复提交。
  • 乐观锁: 乐观锁通过在数据表中添加一个版本号字段来实现并发控制,从而防止重复提交。
  • Idempotency: Idempotency是指接口在被多次调用时,其结果是一致的。通过实现接口的Idempotency,可以防止重复提交。

自定义注解+AOP实现防止重复提交

在SpringBoot中,我们可以通过自定义注解+AOP的方式来实现接口的防重复提交。具体步骤如下:

  1. 定义自定义注解@PreventRepeatSubmit
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventRepeatSubmit {
    // 过期时间,单位:秒
    long expireTime() default 60;
}
  1. 定义AOP切面:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

@Aspect
@Component
public class PreventRepeatSubmitAspect {

    @Pointcut("@annotation(com.example.demo.annotation.PreventRepeatSubmit)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String token = request.getHeader("token");
        if (token == null) {
            throw new RuntimeException("请求头中没有token");
        }

        // 从缓存中获取该token对应的过期时间
        long expireTime = redisTemplate.getExpire(token);
        if (expireTime <= 0) {
            throw new RuntimeException("token已过期");
        }

        // 生成新的token
        String newToken = UUID.randomUUID().toString();

        // 将新的token和过期时间放入缓存
        redisTemplate.opsForValue().set(newToken, expireTime, expireTime, TimeUnit.SECONDS);

        // 将新的token放入请求头中
        request.setAttribute("token", newToken);
    }
}
  1. 在需要防止重复提交的接口上添加@PreventRepeatSubmit注解:
@RestController
@RequestMapping("/api")
public class TestController {

    @PreventRepeatSubmit
    @PostMapping("/test")
    public String test() {
        // 业务逻辑
        return "success";
    }
}

总结

通过自定义注解+AOP的方式,我们可以轻松实现SpringBoot接口的防重复提交。这种方式简单易用,而且可以与其他防重复提交方式结合使用,从而实现更安全的防重复提交机制。