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


Posted in Python onMarch 02, 2021

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

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

一、编写注解类 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, "访问成功!");
  }

}

五、测试访问

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

附: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

到此这篇关于基于注解实现 SpringBoot 接口防刷的方法的文章就介绍到这了,更多相关SpringBoot 接口防刷内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
50行代码实现贪吃蛇(具体思路及代码)
Apr 27 Python
Python冲顶大会 快来答题!
Jan 17 Python
基于循环神经网络(RNN)实现影评情感分类
Mar 26 Python
解决Python requests库编码 socks5代理的问题
May 07 Python
攻击者是如何将PHP Phar包伪装成图像以绕过文件类型检测的(推荐)
Oct 11 Python
Django上线部署之IIS的配置方法
Aug 22 Python
Python的互斥锁与信号量详解
Sep 12 Python
python实现在线翻译功能
Mar 03 Python
Python API len函数操作过程解析
Mar 05 Python
Python Celery异步任务队列使用方法解析
Aug 10 Python
python opencv pytesseract 验证码识别的实现
Aug 28 Python
Python 高效编程技巧分享
Sep 10 Python
python Autopep8实现按PEP8风格自动排版Python代码
Mar 02 #Python
pycharm配置安装autopep8自动规范代码的实现
Mar 02 #Python
Python实现我的世界小游戏源代码
Mar 02 #Python
VSCode中autopep8无法运行问题解决方案(提示Error: Command failed,usage)
Mar 02 #Python
python 基于pygame实现俄罗斯方块
Mar 02 #Python
使用Python快速打开一个百万行级别的超大Excel文件的方法
Mar 02 #Python
Autopep8的使用(python自动编排工具)
Mar 02 #Python
You might like
php readfile下载大文件失败的解决方法
2017/05/22 PHP
PHP区块查询实现方法分析
2018/05/12 PHP
在线编辑器中换行与内容自动提取
2009/04/24 Javascript
jquery 打开窗口返回值实现代码
2010/03/04 Javascript
jquery select下拉框操作的一些说明
2010/04/02 Javascript
JQUERY设置IFRAME的SRC值的代码
2010/11/30 Javascript
javascript中的对象创建 实例附注释
2011/02/08 Javascript
Json和Jsonp理论实例代码详解
2013/11/15 Javascript
js获取url中的参数且参数为中文时通过js解码
2014/03/19 Javascript
jquery仿搜索自动联想功能代码
2014/05/23 Javascript
sliderToggle在写jquery的计时器setTimeouter中不生效
2014/05/26 Javascript
jquery队列函数用法实例
2014/12/16 Javascript
javascript中使用new与不使用实例化对象的区别
2015/06/22 Javascript
Vue.js基础知识汇总
2016/04/27 Javascript
JS封装的三级联动菜单(使用时只需要一行js代码)
2016/10/24 Javascript
DWR3 访问WEB元素的两种方法实例详解
2017/01/03 Javascript
用Vue-cli搭建的项目中引入css报错的原因分析
2017/07/20 Javascript
Vue 父子组件的数据传递、修改和更新方法
2018/03/01 Javascript
vue基于mint-ui实现城市选择三级联动
2020/06/30 Javascript
Vue实现按钮旋转和移动位置的实例代码
2018/08/09 Javascript
详解json串反转义(消除反斜杠)
2019/08/12 Javascript
简单了解JavaScript sort方法
2019/11/25 Javascript
Vue学习之组件用法实例详解
2020/01/06 Javascript
vue打包静态资源后显示空白及static文件路径报错的解决
2020/09/02 Javascript
[02:40]DOTA2超级联赛专访430 从小就爱玩对抗性游戏
2013/06/18 DOTA
[06:35]2014DOTA2国际邀请赛 老男孩梦圆西雅图中国军团世界最强
2014/07/22 DOTA
[03:20]2015国际邀请赛全明星表演赛
2015/08/08 DOTA
Python FTP两个文件夹间的同步实例代码
2018/05/25 Python
python 通过 socket 发送文件的实例代码
2018/08/14 Python
html5跳转小程序wx-open-launch-weapp踩坑
2020/12/02 HTML / CSS
Farfetch阿联酋:奢侈品牌时尚购物平台
2019/07/26 全球购物
杭州信雅达系统.NET工程师面试试题
2015/02/08 面试题
vue路由实现登录拦截
2021/03/24 Vue.js
大众服装店创业计划书范文
2014/01/01 职场文书
公司年会晚宴演讲稿
2014/01/06 职场文书
节能宣传周活动总结
2014/05/08 职场文书