基于注解实现 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常用函数详解
Sep 13 Python
TensorFlow在MAC环境下的安装及环境搭建
Nov 14 Python
Python把csv数据写入list和字典类型的变量脚本方法
Jun 15 Python
linux环境中没有网络怎么下载python
Jul 07 Python
python 模拟创建seafile 目录操作示例
Sep 26 Python
Pytorch中的variable, tensor与numpy相互转化的方法
Oct 10 Python
Anaconda配置pytorch-gpu虚拟环境的图文教程
Apr 16 Python
jupyter修改文件名方式(TensorFlow)
Apr 21 Python
python程序需要编译吗
Jun 19 Python
Python单元测试及unittest框架用法实例解析
Jul 09 Python
浅析Python迭代器的高级用法
Jul 16 Python
Python闭包的定义和使用方法
Apr 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
php实现图片文件与下载文件防盗链的方法
2014/11/03 PHP
推荐5款跨平台的PHP编辑器
2014/12/25 PHP
php微信公众号开发(3)php实现简单微信文本通讯
2016/12/15 PHP
PHP PDOStatement::fetchAll讲解
2019/01/31 PHP
PHP asXML()函数讲解
2019/02/03 PHP
PHP fprintf()函数用法讲解
2019/02/16 PHP
JS实现浏览器菜单命令
2006/09/05 Javascript
JQuery 简便实现页面元素数据验证功能
2007/03/24 Javascript
jQuery中的常用事件总结
2009/12/27 Javascript
ExtJS 下拉多选框lovcombo
2010/05/19 Javascript
jQuery.extend()的实现方式详解及实例
2013/06/29 Javascript
extjs 时间范围选择自动判断的实现代码
2014/06/24 Javascript
node.js中的fs.utimes方法使用说明
2014/12/15 Javascript
jQuery的实例及必知重要的jQuery选择器详解
2016/05/20 Javascript
JS正则验证多个邮箱完整实例【邮箱用分号隔开】
2017/04/19 Javascript
使用Fullpage插件快速开发整屏翻页的页面
2017/09/13 Javascript
Javascript将图片的绝对路径转换为base64编码的方法
2018/01/11 Javascript
vue使用element-ui的el-input监听不了回车事件的解决方法
2018/01/12 Javascript
一次Webpack配置文件的分离实战记录
2018/11/30 Javascript
详解nodejs 配置文件处理方案
2019/01/02 NodeJs
js实现图片放大并跟随鼠标移动特效
2019/01/18 Javascript
Element-ui中元素滚动时el-option超出元素区域的问题
2019/05/30 Javascript
Python 文件操作技巧(File operation) 实例代码分析
2008/08/11 Python
Python实现的圆形绘制(画圆)示例
2018/01/31 Python
Python排序函数的使用方法详解
2020/12/11 Python
使用简单的CSS3属性实现炫酷读者墙效果
2014/01/08 HTML / CSS
丝绸和人造花卉、植物和树木:Nearly Natural
2018/11/28 全球购物
招商经理岗位职责
2013/11/16 职场文书
大学生毕业鉴定
2014/01/31 职场文书
小学一年级评语大全
2014/04/22 职场文书
党员群众路线承诺书
2014/05/20 职场文书
高考标语大全
2014/06/05 职场文书
出差报告范文
2014/11/06 职场文书
感恩主题班会教案
2015/08/12 职场文书
MySQL 覆盖索引的优点
2021/05/19 MySQL
MySQL系列之十一 日志记录
2021/07/02 MySQL