当前位置 博文首页 > 基于注解实现 SpringBoot 接口防刷的方法

    基于注解实现 SpringBoot 接口防刷的方法

    作者:杀人偿命百岁 时间:2021-07-19 18:42

    该示例项目通过自定义注解,实现接口访问次数控制,从而实现接口防刷功能,项目结构如下:

    在这里插入图片描述

    一、编写注解类 AccessLimit

    package cn.mygweb.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 访问控制注解(实现接口防刷功能)
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface AccessLimit {
      /**
       * 限制周期(单位为秒)
       *
       * @return
       */
      int seconds();
    
      /**
       * 规定周期内限制次数
       *
       * @return
       */
      int maxCount();
    
      /**
       * 是否需要登录
       *
       * @return
       */
      boolean needLogin() default false;
    }
    

    二、在Interceptor拦截器中实现拦截逻辑

    package cn.mygweb.interceptor;
    
    import cn.mygweb.annotation.AccessLimit;
    import cn.mygweb.entity.Result;
    import cn.mygweb.entity.StatusCode;
    import com.alibaba.fastjson.JSON;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.OutputStream;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 访问控制拦截器
     */
    @Component
    public class AccessLimitInterceptor extends HandlerInterceptorAdapter {
    
      //模拟数据存储,实际业务中可以自定义实现方式
      private static Map<String, AccessInfo> accessInfoMap = new HashMap<>();
    
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                   Object handler) throws Exception {
        //判断请求是否属于方法的请求
        if (handler instanceof HandlerMethod) {
          HandlerMethod hm = (HandlerMethod) handler;
    
          //获取方法中的注解,看是否有该注解
          AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
          if (accessLimit == null) {
            return true;
          }
          int seconds = accessLimit.seconds();
          int maxCount = accessLimit.maxCount();
          boolean needLogin = accessLimit.needLogin();
          String key = request.getRequestURI();
          //如果需要登录
          if (needLogin) {
            //获取登录的session进行判断
            //……
            key += " " + "userA";//这里假设用户是userA,实际项目中可以改为userId
          }
    
          //模拟从redis中获取数据
          AccessInfo accessInfo = accessInfoMap.get(key);
          if (accessInfo == null) {
            //第一次访问
            accessInfo = new AccessInfo();
            accessInfo.setFirstVisitTimestamp(System.currentTimeMillis());
            accessInfo.setAccessCount(1);
            accessInfoMap.put(key, accessInfo);
          } else if (accessInfo.getAccessCount() < maxCount) {
            //访问次数加1
            accessInfo.setAccessCount(accessInfo.getAccessCount() + 1);
            accessInfoMap.put(key, accessInfo);
          } else {
            //超出访问次数,判断时间是否超出设定时间
            if ((System.currentTimeMillis() - accessInfo.getFirstVisitTimestamp()) <= seconds * 1000) {
              //如果还在设定时间内,则为不合法请求,返回错误信息
              render(response, "达到访问限制次数,请稍后重试!");
              return false;
            } else {
              //如果超出设定时间,则为合理的请求,将之前的请求清空,重新计数
              accessInfo.setFirstVisitTimestamp(System.currentTimeMillis());
              accessInfo.setAccessCount(1);
              accessInfoMap.put(key, accessInfo);
            }
          }
        }
        return true;
      }
    
      /**
       * 向页面发送消息
       *
       * @param response
       * @param msg
       * @throws Exception
       */
      private void render(HttpServletResponse response, String msg) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str = JSON.toJSONString(new Result(true, StatusCode.ACCESSERROR, msg));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
      }
    
      /**
       * 封装的访问信息对象
       */
      class AccessInfo {
    
        /**
         * 一个计数周期内第一次访问的时间戳
         */
        private long firstVisitTimestamp;
        /**
         * 访问次数统计
         */
        private int accessCount;
    
        public long getFirstVisitTimestamp() {
          return firstVisitTimestamp;
        }
    
        public void setFirstVisitTimestamp(long firstVisitTimestamp) {
          this.firstVisitTimestamp = firstVisitTimestamp;
        }
    
        public int getAccessCount() {
          return accessCount;
        }
    
        public void setAccessCount(int accessCount) {
          this.accessCount = accessCount;
        }
    
        @Override
        public String toString() {
          return "AccessInfo{" +
              "firstVisitTimestamp=" + firstVisitTimestamp +
              ", accessCount=" + accessCount +
              '}';
        }
      }
    }
    

    三、把Interceptor注册到springboot中

    package cn.mygweb.config;
    
    import cn.mygweb.interceptor.AccessLimitInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * 拦截器注册配置
     */
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器
        registry.addInterceptor(new AccessLimitInterceptor());
      }
    }
    

    四、在Controller中加入注解实现接口防刷

    package cn.mygweb.controller;
    
    import cn.mygweb.annotation.AccessLimit;
    import cn.mygweb.entity.Result;
    import cn.mygweb.entity.StatusCode;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/access")
    public class AccessController {
    
      @AccessLimit(seconds = 5, maxCount = 2)//访问控制,5秒内只能访问2次
      @GetMapping
      public Result access() {
        return new Result(true, StatusCode.OK, "访问成功!");
      }
    
    }
    

    五、测试访问

    在这里插入图片描述

    附:StatusCode.java、Result.java、application.yml

    StatusCode类

    package cn.mygweb.entity;
    
    /**
     * 返回状态码
     */
    public class StatusCode {
      public static final int OK = 20000;//成功
      public static final int ERROR = 20001;//失败
      public static final int LOGINERROR = 20002;//用户名或密码错误
      public static final int ACCESSERROR = 20003;//权限不足
      public static final int REMOTEERROR = 20004;//远程调用失败
      public static final int REPERROR = 20005;//重复操作
      public static final int NOTFOUNDERROR = 20006;//没有对应的抢购数据
    }
    

    Result类:

    package cn.mygweb.entity;
    
    import java.io.Serializable;
    
    /**
     * 响应结果
     */
    public class Result<T> implements Serializable {
      private boolean flag;//是否成功
      private Integer code;//返回码
      private String message;//返回消息
      private T data;//返回数据
    
      public Result(boolean flag, Integer code, String message, Object data) {
        this.flag = flag;
        this.code = code;
        this.message = message;
        this.data = (T) data;
      }
    
      public Result(boolean flag, Integer code, String message) {
        this.flag = flag;
        this.code = code;
        this.message = message;
      }
    
      public Result() {
        this.flag = true;
        this.code = StatusCode.OK;
        this.message = "操作成功!";
      }
    
      public boolean isFlag() {
        return flag;
      }
    
      public void setFlag(boolean flag) {
        this.flag = flag;
      }
    
      public Integer getCode() {
        return code;
      }
    
      public void setCode(Integer code) {
        this.code = code;
      }
    
      public String getMessage() {
        return message;
      }
    
      public void setMessage(String message) {
        this.message = message;
      }
    
      public T getData() {
        return data;
      }
    
      public void setData(T data) {
        this.data = data;
      }
    }
    

    applications.yml:

    server:
     port: 8080
    
    jsjbwy
    下一篇:没有了