暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JAVA:Spring Boot 实现接口防抖的技术指南

拾荒的小海螺 2024-08-19
1109

1、简述

在 Web 应用中,接口防抖是一种常见的技术手段,用于防止客户端在短时间内多次触发同一接口,从而减轻服务器的负担和防止重复操作。本文将介绍如何在 Spring Boot 项目中实现接口防抖功能,并通过实例展示其应用场景。


2、防抖

2.1 什么是接口防抖?

接口防抖(Debouncing)是一种在用户频繁触发操作时,通过延迟响应来减少接口调用次数的技术。它通过设定一个时间窗口,在该时间窗口内,如果同一接口被多次调用,则只会执行最后一次调用的操作,其他调用将被忽略。


2.2 为什么需要接口防抖?

在实际开发中,用户可能会由于网络延迟或重复点击等原因频繁触发接口调用,导致服务器压力增大,甚至引发重复的数据库操作。接口防抖可以有效地避免这些问题,从而提升应用的性能和用户体验。


3、时间戳实现防抖

3.1 使用注解和 AOP 实现防抖逻辑

我们可以通过自定义注解和 Spring AOP(面向切面编程)来实现接口防抖功能。首先,创建一个自定义注解 @Debounce,用于标记需要防抖的接口。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;


    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Debounce {
    long delay() default 1000; // 默认防抖时间为 1 秒
    }



    3.2 实现防抖切面逻辑

    接下来,创建一个 AOP 切面类,拦截带有 @Debounce 注解的方法,并实现防抖逻辑。

      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;


      import java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;


      @Aspect
      @Component
      public class DebounceAspect {


      private final Map<String, Long> requestTimestamps = new ConcurrentHashMap<>();


      @Around("@annotation(debounce)")
      public Object around(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable {
      String key = joinPoint.getSignature().toString();
      long currentTime = System.currentTimeMillis();


      Long lastTime = requestTimestamps.get(key);
      if (lastTime != null && (currentTime - lastTime) < debounce.delay()) {
      // 如果在防抖时间内,则忽略这次调用
      return null;
      }


      // 更新上次调用的时间戳
      requestTimestamps.put(key, currentTime);


      // 执行目标方法
      return joinPoint.proceed();
      }
      }

      这个切面类中使用 ConcurrentHashMap 来记录每个接口的最近调用时间,并在调用前判断是否在防抖时间内。如果在防抖时间内则忽略这次调用,否则更新调用时间并继续执行接口逻辑。


      3.3  应用防抖注解

      在实际使用中,只需要将 @Debounce 注解添加到需要防抖的接口方法上即可。例如:

        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;


        @RestController
        @RequestMapping("/api")
        public class DebounceController {


        @GetMapping("/submit")
        @Debounce(delay = 2000)
        public String submitForm() {
        return "表单提交成功";
        }
        }

        在这个例子中,当用户在 2 秒内多次触发 api/submit 接口时,只有第一次请求会被处理,其余请求将被忽略。


        4、Redis实现防抖

        Redis 是一个高性能的内存数据库,具有极快的读写速度和丰富的数据类型。使用 Redis 可以非常高效地实现防抖功能,因为它支持键的过期时间功能,可以根据需要灵活设置请求的限制时间。

        4.1 配置

        首先,需要在 pom.xml 中引入 Spring Boot 的 Redis 依赖:

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>


          在 application.yml 或 application.properties 文件中配置 Redis 连接信息:

            spring:
            redis:
            host: localhost
            port: 6379
            # 如果使用密码,可以添加以下配置
            # password: yourpassword


            4.2 创建 Redis 工具类

            为了方便操作 Redis,可以创建一个工具类来封装常用的方法:

              import org.springframework.beans.factory.annotation.Autowired;
              import org.springframework.data.redis.core.StringRedisTemplate;
              import org.springframework.stereotype.Component;


              import java.util.concurrent.TimeUnit;


              @Component
              public class RedisUtil {


              @Autowired
              private StringRedisTemplate redisTemplate;


              /**
              * 设置键值对,并设置过期时间
              *
              * @param key 键
              * @param value 值
              * @param timeout 超时时间
              * @param unit 时间单位
              */
              public void set(String key, String value, long timeout, TimeUnit unit) {
              redisTemplate.opsForValue().set(key, value, timeout, unit);
              }


              /**
              * 检查键是否存在
              *
              * @param key 键
              * @return 如果存在返回 true,否则返回 false
              */
              public boolean hasKey(String key) {
              return Boolean.TRUE.equals(redisTemplate.hasKey(key));
              }
              }


              4.3 实现防抖功能

              接下来,我们将实现一个拦截器,用于在请求到达控制器之前进行防抖处理。

                import org.springframework.beans.factory.annotation.Autowired;
                import org.springframework.stereotype.Component;
                import org.springframework.web.servlet.HandlerInterceptor;


                import javax.servlet.http.HttpServletRequest;
                import javax.servlet.http.HttpServletResponse;


                @Component
                public class DebounceInterceptor implements HandlerInterceptor {


                @Autowired
                private RedisUtil redisUtil;


                private static final long DEBOUNCE_TIME = 2; // 防抖时间(秒)


                @Override
                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                String key = "debounce:" + request.getSession().getId() + ":" + request.getRequestURI();

                if (redisUtil.hasKey(key)) {
                response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
                return false; // 拦截请求
                }


                // 设置防抖时间
                redisUtil.set(key, "1", DEBOUNCE_TIME, TimeUnit.SECONDS);
                return true; // 继续执行请求
                }
                }

                这个拦截器会为每个请求生成一个唯一的键(通过会话 ID 和请求 URI 组合),然后检查 Redis 中是否已经存在该键。如果存在,表示该请求在短时间内已被发起过,将返回 429(请求过多)状态码并拦截请求。否则,拦截器将继续处理请求,并将请求信息存储在 Redis 中,设置过期时间。


                4.4 测试防抖功能

                创建一个简单的控制器来测试防抖功能:

                  import org.springframework.web.bind.annotation.GetMapping;
                  import org.springframework.web.bind.annotation.RestController;


                  @RestController
                  public class TestController {


                  @GetMapping("/api/test")
                  public String testDebounce() {
                  return "Request successful!";
                  }
                  }

                  启动应用程序,然后在短时间内多次访问 api/test 接口。你应该会看到第一次请求返回 "Request successful!",后续快速的请求将被拦截,并返回状态码 429。


                  5、总结

                  接口防抖是一种简单而有效的手段,用于提高系统的健壮性和用户体验。在 Spring Boot 项目中,通过自定义注解和 AOP 可以轻松实现防抖功能。本文通过实例展示了如何将接口防抖应用于实际开发中,从而避免由于频繁调用导致的服务器压力和重复操作。

                  通过这种方法,你可以有效地减少接口的无效调用次数,优化系统性能。如果有其他需求或进一步探讨的内容,欢迎随时交流。


                  文章转载自拾荒的小海螺,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论