基于注解实现 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 相关文章推荐
Python 多线程实例详解
Mar 25 Python
Python绑定方法与非绑定方法详解
Aug 18 Python
Python数据分析之双色球统计单个红和蓝球哪个比例高的方法
Feb 03 Python
分析Python读取文件时的路径问题
Feb 11 Python
tensorflow实现对图片的读取的示例代码
Feb 12 Python
Numpy数组的保存与读取方法
Apr 04 Python
对Tensorflow中的矩阵运算函数详解
Jul 27 Python
Python OOP类中的几种函数或方法总结
Feb 22 Python
python增加图像对比度的方法
Jul 12 Python
pandas实现excel中的数据透视表和Vlookup函数功能代码
Feb 14 Python
Python读写csv文件流程及异常解决
Oct 20 Python
Python 匹配文本并在其上一行追加文本
May 11 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
加速XP搜索功能堪比vista
2007/03/22 PHP
redis 队列操作的例子(php)
2012/04/12 PHP
php 批量添加多行文本框textarea一行一个
2014/06/03 PHP
Symfony2学习笔记之系统路由详解
2016/03/17 PHP
laravel 输出最后执行sql 附:whereIn的使用方法
2019/10/10 PHP
Docker 安装 PHP并与Nginx的部署实例讲解
2021/02/27 PHP
Javascript的一种模块模式
2010/09/08 Javascript
HTTP 304错误的详细讲解
2013/11/13 Javascript
JQuery EasyUI 数字格式化处理示例
2014/05/05 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
2014/09/02 Javascript
深入解读JavaScript中的Iterator和for-of循环
2015/07/28 Javascript
js获取Get值的方法
2016/09/29 Javascript
如何用webpack4带你实现一个vue的打包的项目
2018/06/20 Javascript
Vue中的情侣属性$dispatch和$broadcast详解
2019/03/07 Javascript
微信小程序--特定区域滚动到顶部时固定的方法
2019/04/28 Javascript
如何使用proxy实现一个简单完整的MVVM库的示例代码
2019/09/17 Javascript
axios实现简单文件上传功能
2019/09/25 Javascript
解决vue admin element noCache设置无效的问题
2019/11/12 Javascript
js判断浏览器的环境(pc端,移动端,还是微信浏览器)
2020/12/24 Javascript
es6 super关键字的理解与应用实例分析
2020/02/15 Javascript
2020淘宝618理想生活列车自动领喵币js脚本的代码
2020/06/02 Javascript
js实现移动端图片滑块验证功能
2020/09/29 Javascript
[06:04]DOTA2英雄梦之声Vol19卓尔游侠
2014/06/20 DOTA
python使用装饰器和线程限制函数执行时间的方法
2015/04/18 Python
详解Django中的过滤器
2015/07/16 Python
Python内置模块turtle绘图详解
2017/12/09 Python
示例详解Python3 or Python2 两者之间的差异
2018/08/23 Python
Pandas之排序函数sort_values()的实现
2019/07/09 Python
python multiprocessing模块用法及原理介绍
2019/08/20 Python
python实现的爬取电影下载链接功能示例
2019/08/26 Python
python tkinter组件摆放方式详解
2019/09/16 Python
pytorch实现保证每次运行使用的随机数都相同
2020/02/20 Python
Python3爬虫中Ajax的用法
2020/07/10 Python
XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?
2016/01/12 面试题
自我推荐信怎么写
2015/03/24 职场文书
2015年社区统计工作总结
2015/04/21 职场文书