详解Springboot如何通过注解实现接口防刷

目录
  • 前言
  • 1、实现防刷切面PreventAop.java
    • 1.1 定义注解Prevent
    • 1.2 实现防刷切面PreventAop
  • 2、使用防刷切面
  • 3、演示

前言

本文介绍一种极简洁、灵活通用接口防刷实现方式、通过在需要防刷的方法加上@Prevent 注解即可实现短信防刷;

使用方式大致如下:

/**
     * 测试防刷
     *
     * @param request
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/testPrevent")
    @Prevent //加上该注解即可实现短信防刷(默认一分钟内不允许重复调用,支持扩展、配置)
    public Response testPrevent(TestRequest request) {
        return Response.success("调用成功");
    }

1、实现防刷切面PreventAop.java

大致逻辑为:定义一切面,通过@Prevent注解作为切入点、在该切面的前置通知获取该方法的所有入参并将其Base64编码,将入参Base64编码+完整方法名作为redis的key,入参作为reids的value,@Prevent的value作为redis的expire,存入redis;每次进来这个切面根据入参Base64编码+完整方法名判断redis值是否存在,存在则拦截防刷,不存在则允许调用;

1.1 定义注解Prevent

package com.zetting.aop;

import java.lang.annotation.*;

/**
 * 接口防刷注解
 * 使用:
 * 在相应需要防刷的方法上加上
 * 该注解,即可
 *
 * @author: zetting
 * @date:2018/12/29
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prevent {

    /**
     * 限制的时间值(秒)
     *
     * @return
     */
    String value() default "60";

    /**
     * 提示
     */
    String message() default "";

    /**
     * 策略
     *
     * @return
     */
    PreventStrategy strategy() default PreventStrategy.DEFAULT;
}

1.2 实现防刷切面PreventAop

package com.zetting.aop;

import com.alibaba.fastjson.JSON;
import com.zetting.common.BusinessException;
import com.zetting.util.RedisUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Base64;

/**
 * 防刷切面实现类
 *
 * @author: zetting
 * @date: 2018/12/29 20:27
 */
@Aspect
@Component
public class PreventAop {
    private static Logger log = LoggerFactory.getLogger(PreventAop.class);

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.zetting.aop.Prevent)")
    public void pointcut() {
    }

    /**
     * 处理前
     *
     * @return
     */
    @Before("pointcut()")
    public void joinPoint(JoinPoint joinPoint) throws Exception {
        String requestStr = JSON.toJSONString(joinPoint.getArgs()[0]);
        if (StringUtils.isEmpty(requestStr) || requestStr.equalsIgnoreCase("{}")) {
            throw new BusinessException("[防刷]入参不允许为空");
        }

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(methodSignature.getName(),
                methodSignature.getParameterTypes());

        Prevent preventAnnotation = method.getAnnotation(Prevent.class);
        String methodFullName = method.getDeclaringClass().getName() + method.getName();

        entrance(preventAnnotation, requestStr,methodFullName);
        return;
    }

    /**
     * 入口
     *
     * @param prevent
     * @param requestStr
     */
    private void entrance(Prevent prevent, String requestStr,String methodFullName) throws Exception {
        PreventStrategy strategy = prevent.strategy();
        switch (strategy) {
            case DEFAULT:
                defaultHandle(requestStr, prevent,methodFullName);
                break;
            default:
                throw new BusinessException("无效的策略");
        }
    }

    /**
     * 默认处理方式
     *
     * @param requestStr
     * @param prevent
     */
    private void defaultHandle(String requestStr, Prevent prevent,String methodFullName) throws Exception {
        String base64Str = toBase64String(requestStr);
        long expire = Long.parseLong(prevent.value());

        String resp = redisUtil.get(methodFullName+base64Str);
        if (StringUtils.isEmpty(resp)) {
            redisUtil.set(methodFullName+base64Str, requestStr, expire);
        } else {
            String message = !StringUtils.isEmpty(prevent.message()) ? prevent.message() :
                    expire + "秒内不允许重复请求";
            throw new BusinessException(message);
        }
    }

    /**
     * 对象转换为base64字符串
     *
     * @param obj 对象值
     * @return base64字符串
     */
    private String toBase64String(String obj) throws Exception {
        if (StringUtils.isEmpty(obj)) {
            return null;
        }
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] bytes = obj.getBytes("UTF-8");
        return encoder.encodeToString(bytes);
    }
}

注:

以上只展示核心代码、其他次要代码(例如redis配置、redis工具类等)可下载源码查阅

2、使用防刷切面

在MyController 使用防刷

package com.zetting.modules.controller;

import com.zetting.aop.Prevent;
import com.zetting.common.Response;
import com.zetting.modules.dto.TestRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 切面实现入参校验
 */
@RestController
public class MyController {

    /**
     * 测试防刷
     *
     * @param request
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/testPrevent")
    @Prevent
    public Response testPrevent(TestRequest request) {
        return Response.success("调用成功");
    }

    /**
     * 测试防刷
     *
     * @param request
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/testPreventIncludeMessage")
    @Prevent(message = "10秒内不允许重复调多次", value = "10")//value 表示10表示10秒
    public Response testPreventIncludeMessage(TestRequest request) {
        return Response.success("调用成功");
    }
}

3、演示

GIF.gif

gitee 源码:https://gitee.com/Zetting/my-gather/tree/master/springboot-aop-prevent

到此这篇关于详解Springboot如何通过注解实现接口防刷的文章就介绍到这了,更多相关Springboot接口防刷内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于注解实现 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.Targ

  • SpringBoot项目中接口防刷的完整代码

    一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * @author Yang * @version 1.0 * @date 2021/2/22

  • Spring Boot如何利用拦截器加缓存完成接口防刷操作

    目录 为什么需要接口防刷 技术解析 主要代码 测试结果 总结 为什么需要接口防刷 为了减缓服务器压力,将服务器资源留待给有价值的请求,防止恶意访问,一般的程序都会有接口防刷设置,接下来介绍一种简单灵活的接口防刷操作 技术解析 主要采用的技术还是拦截+缓存,我们可以通过自定义注解,将需要防刷的接口给标记出来管理,利用缓存统计指定时间区间里,具体的某个ip访问某个接口的频率,如果超过某个阈值,就让他进一会儿小黑屋,到期自动解放 主要代码 前置环境搭建,Spring Boot项目,引入Web和Redi

  • 详解Springboot如何通过注解实现接口防刷

    目录 前言 1.实现防刷切面PreventAop.java 1.1 定义注解Prevent 1.2 实现防刷切面PreventAop 2.使用防刷切面 3.演示 前言 本文介绍一种极简洁.灵活通用接口防刷实现方式.通过在需要防刷的方法加上@Prevent 注解即可实现短信防刷: 使用方式大致如下: /** * 测试防刷 * * @param request * @return */ @ResponseBody @GetMapping(value = "/testPrevent") @P

  • 详解SpringBoot定制@ResponseBody注解返回的Json格式

     1.引言 在SpringMVC的使用中,后端与前端的交互一般是使用Json格式进行数据传输,SpringMVC的@ResponseBody注解可以很好的帮助我们进行转换,但是后端返回数据给前端往往都有约定固定的格式,这时候我们在后端返回的时候都要组拼成固定的格式,每次重复的操作非常麻烦. 2.SpringMVC对@ResponseBody的处理 SpringMVC处理@ResponseBody注解声明的Controller是使用默认的.RequestResponseBodyMethodProc

  • 详解SpringBoot中@ConditionalOnClass注解的使用

    目录 一.@ConditionalOnClass注解初始 二.@ConditionalOnClass注解用法 1.使用value属性 2.使用name属性 三.@ConditionalOnClass是怎么实现的 四.总结 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? 一.@ConditionalOnClass注解初始 看下@CodidtionalOnClass注解的定义, 需要注意的有两点,

  • 详解springboot使用异步注解@Async获取执行结果的坑

    目录 一.引言 二.获取异步执行结果 1.环境介绍 2.错误的方式 3.正确方式 三.异步执行@Async注解 四.总结 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他的请求一般都是同步的,也就是每个请求都是阻塞的,那么这个处理时间必定是很长的,有没有一种方法可以让多个请求异步处理那,答案是有的. springboot中提供了很便利的方式可以解决上面的问题,那就是异步注解@Async.正确的使用该注

  • 详解springboot通过Async注解实现异步任务及回调的方法

    目录 前言 什么是异步调用? 1. 环境准备 2. 同步调用 3. 异步调用 4. 异步回调 前言 什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行.异步调用可以减少程序执行时间. 1. 环境准备 在 Spring Boot 入口类上配置 @EnableAsync 注解开启异步处理.创建任务抽象类 AbstractTask,并实现三个任务方法 doTaskOne(),doTas

  • 详解springboot中mybatis注解形式

    springboot整合mybatis对数据库进行访问,本实例采用注解的方式,如下: pom.xml文件 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <pr

  • 详解springboot测试类注解

    目录 创建一个TextHello类 注解 主启动类 配置文件格式 区别 创建一个TextHello类 TextHello类的代码如下 @Controller @RequestMapping("/hello") public class TextHello { @GetMapping("/hello") @ResponseBody public String hello(){ return "hello,程程呀"; } } 我是在pom.xml文件

  • 详解SpringBoot中添加@ResponseBody注解会发生什么

    SpringBoot版本2.2.4.RELEASE. [1]SpringBoot接收到请求 ① springboot接收到一个请求返回json格式的列表,方法参数为JSONObject 格式,使用了注解@RequestBody 为什么这里要说明返回格式.方法参数.参数注解?因为方法参数与参数注解会影响你使用不同的参数解析器与后置处理器!通常使用WebDataBinder进行参数数据绑定结果也不同. 将要调用的目标方法如下: @ApiOperation(value="分页查询") @Re

  • 详解Springboot集成sentinel实现接口限流入门

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

随机推荐