springboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】

这篇具有很好参考价值的文章主要介绍了springboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

思路:通过AOP拦截注解标记的方法,在Redis中维护一个计数器来记录接口访问的频率,
并根据限流策略来判断是否允许继续处理请求。

另一篇:springboot 自定义注解 ,aop切面@Around; 为接口实现日志插入【强行喂饭版】

不多说,直接上代码:

一:创建限流类型

/**
 * 限流类型
 * 
 */

public enum LimitType
{
    /**
     * 默认策略全局限流
     */
    DEFAULT,

    /**
     * 根据请求者IP进行限流
     */
    IP
}



二:创建注解

import 你上面限流类型的路径.LimitType;

import java.lang.annotation.*;

/**
 * 限流注解
 * 
 */
// 注解的作用目标为方法
@Target(ElementType.METHOD) 

// 注解在运行时保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用
@Retention(RetentionPolicy.RUNTIME) 

// 指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值
@Documented 
public @interface LimiterToShareApi{

    /**
     * 限流key
     */
    public String key() default "";

    /**
     * 限流时间,单位秒
     */
    public int time() default 60;

    /**
     * 限流次数
     */
    public int count() default 100;

    /**
     * 限流类型,默认全局限流
     */
    public LimitType limitType() default LimitType.DEFAULT;
}



**三:编写业务异常类 **

/**
 * 业务异常
 * 
 */
public final class ServiceException extends RuntimeException
{
	// 序列化的版本号的属性
    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private Integer code;

    /**
     * 错误提示
     */
    private String message;

    /**
     * 空构造方法,避免反序列化问题
     */
    public ServiceException(){
    }

	 /**
     * 异常信息
     */
    public ServiceException(String message){
        this.message = message;
    }
    
}


四:实现aop切面拦截,限流逻辑处理

import 你上面限流类型的路径.LimitType;
import 你上面业务异常的路径.ServiceException;
import 你上面限流注解的路径.LimiterToShareApi;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

 
// 声明这是一个切面类
@Aspect
// 表明该类是一个组件,将该类交给spring管理。
@Component
// 指定执行顺序,值越小,越先执行。限流策略一般最先执行。
@Order(1) 
public class LimiterToShareApiAspect {

	// 记录日志的Logger对象
    private static final Logger log = LoggerFactory.getLogger(LimiterToShareApiAspect.class);

	// 操作Redis的RedisTemplate对象
    private RedisTemplate<Object, Object> redisTemplate;

	//在Redis中执行Lua脚本的对象
    private RedisScript<Long> limitScript;

    @Autowired
    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Autowired
    public void setLimitScript(RedisScript<Long> limitScript) {
        this.limitScript = limitScript;
    }

	// 这个注解作用及普及 见文章后面解析
    @Before("@annotation(limiter)")
    public void doBefore(JoinPoint point, LimiterToShareApi limiter) throws Throwable {
        
        // 根据业务需求,看看是否去数据库查询对应的限流策略,还是直接使用注解传递的值
        // 这里演示为 获取注解的值
        int time = limiter.time();
        int count = limiter.count();

        String combineKey = getCombineKey(limiter, point);
        List<Object> keys = Collections.singletonList(combineKey);
        try {
            Long number = redisTemplate.execute(limitScript, keys, count, time);
            if (number == null || number.intValue() > count) {
                throw new ServiceException("限流策略:访问过于频繁,请稍候再试");
            }
            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("服务器限流异常,请稍候再试");
        }
    }
    

    /**
     * 获取用于限流的组合键,根据LimterToShareApi注解和JoinPoint对象来生成。
     *
     * @param rateLimiter LimiterToShareApi注解,用于获取限流配置信息。
     * @param point       JoinPoint对象,用于获取目标方法的信息。
     * @return 生成的用于限流的组合键字符串。
     */
    public String getCombineKey(LimiterToShareApi rateLimiter, JoinPoint point) {
        // 创建一个StringBuffer用于拼接组合键
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key() + "-");

        // 根据LimterToShareApi注解的limitType判断是否需要添加IP地址信息到组合键中【判断限流类型 是否根据ip进行限流】
        if (rateLimiter.limitType() == LimitType.IP) {
            // 如果需要添加IP地址信息,调用IpUtils.getIpAddr()方法获取当前请求的IP地址,并添加到组合键中
            stringBuffer.append(getClientIp()).append("-");
        }

        // 使用JoinPoint对象获取目标方法的信息
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();

        // 将目标方法所属类的名称和方法名称添加到组合键中
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());

        // 返回生成的用于限流的组合键字符串
        return stringBuffer.toString();
    }


	 /**
     * 获取调用方真实ip [本机调用则得到127.0.0.1]
     * 首先尝试从X-Forwarded-For请求头获取IP地址,如果没有找到或者为unknown,则尝试从X-Real-IP请求头获取IP地址,
     * 最后再使用request.getRemoteAddr()方法作为备用方案。注意,在多个代理服务器的情况下,
     * X-Forwarded-For请求头可能包含多个IP地址,我们取第一个IP地址作为真实客户端的IP地址。
     */
    public String getClientIp() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("X-Real-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        // 多个代理服务器时,取第一个IP地址
        int index = ipAddress.indexOf(",");
        if (index != -1) {
            ipAddress = ipAddress.substring(0, index);
        }
        return ipAddress;
    }

}

五:哪里需要点哪里

@PostMapping("/接口api")
// 根据自己业务选择是否需要这些参数,如果是想从数据库读取,不填参数即可
// 这里意思为对key的限制为 全局 每60秒内2次请求,超过2次则限流
@LimiterToShareApi(key = "key",time = 60,count = 2,limitType = LimitType.DEFAULT)
public AjaxResult selectToUserId(参数){}


限流(代码)结果:
springboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】,spring boot,后端,javaspringboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】,spring boot,后端,javaspringboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】,spring boot,后端,java


解析:

@Before("@annotation(limiter)")- 使用了@Before 来表示这是一个切面注解,用于定义在目标方法执行前执行的逻辑

- @annotation(limiter) 中的limiter是指参数名称,而不是注解名称。

- @annotation(limiter) 中的limiter参数类型为LimiterToShareApi,
表示你将拦截被@LimiterToShareApi注解标记的方法,并且可以通过这个参数来获取@LimiterToShareApi注解的信息。
如果你想拦截其他注解,只需将第二个参数的类型修改为对应的注解类型即可。

普及:文章来源地址https://www.toymoban.com/news/detail-609972.html

JoinPointSpring AOP中的一个接口,它代表了在程序执行过程中能够被拦截的连接点(Join Point)。
连接点指的是在应用程序中,特定的代码块,比如方法的调用、方法的执行、构造器的调用等。

JoinPointAOP中的作用是用于传递方法调用的信息,比如方法名、参数、所属的类等等。
当AOP拦截到一个连接点时,就可以通过JoinPoint对象来获取这些信息,并根据需要进行相应的处理。

在AOP中,常见的通知类型(advice)如下:

@Before:在目标方法执行之前执行。
@After:在目标方法执行之后(无论是否抛出异常)执行。
@AfterReturning:在目标方法成功执行之后执行。
@AfterThrowing:在目标方法抛出异常后执行。
@Around:在目标方法执行前后都执行,可以控制目标方法的执行。

在以上各种通知中,可以使用JoinPoint参数来获取连接点的相关信息。
例如,在@Around通知中,可以使用JoinPoint对象来获取目标方法的信息,
比如方法名、参数等。这样,我们就可以根据这些信息来实现我们需要的切面逻辑。


eg:
// 获取方法名
String methodName = joinPoint.getSignature().getName();

//获取方法参数
Object[] args = joinPoint.getArgs();

// 获取所属类名
String className = joinPoint.getSignature().getDeclaringTypeName();

// 获取源代码位置信息
SourceLocation sourceLocation = joinPoint.getSourceLocation();

到了这里,关于springboot 自定义注解 ,实现接口限流(计数器限流)【强行喂饭版】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go-zero 是如何实现计数器限流的?

    原文链接: 如何实现计数器限流? 上一篇文章 go-zero 是如何做路由管理的? 介绍了路由管理,这篇文章来说说限流,主要介绍计数器限流算法,具体的代码实现,我们还是来分析微服务框架 go-zero 的源码。 在微服务架构中,一个服务可能需要频繁地与其他服务交互,而过多

    2024年02月13日
    浏览(27)
  • Spring Boot 3自定义注解+拦截器+Redis实现高并发接口限流

    在当今互联网应用开发中,高并发访问是一个常见的挑战。为了保障系统的稳定性和可靠性,我们需要对接口进行限流,防止因过多的请求导致系统崩溃。 本文将介绍如何利用Spring Boot 3中的自定义注解、拦截器和Redis实现高并发接口限流,帮助程序员解决这一挑战。 1. 自定

    2024年04月28日
    浏览(36)
  • go限流、计数器固定窗口算法/计数器滑动窗口算法

    问题1:后端接口只能支撑每10秒1w个请求,要怎么来保护它呢? 问题2:发短信的接口,不超过100次/时,1000次/24小时,要怎么实现? 所谓固定窗口,就是只设置了一个时间段,给这个时间段加上一个计数器。 常见的就是统计每秒钟的请求量。 这里就是一个QPS计数器。 在这一

    2024年04月26日
    浏览(30)
  • Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截

    🏷️ 个人主页 :牵着猫散步的鼠鼠  🏷️ 系列专栏 :Java全栈-专栏 🏷️ 个人学习笔记,若有缺误,欢迎评论区指正   前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站AI学习网站。 目录 前言 1.导入Redisson 引入依

    2024年02月21日
    浏览(42)
  • 限流:计数器、漏桶、令牌桶 三大算法的原理与实战(史上最全)

    限流是面试中的常见的面试题(尤其是大厂面试、高P面试) 注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请到文末《技术自由圈》公号获取 为什么要限流 简单来说: 限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统

    2023年04月17日
    浏览(27)
  • SpringBoot中通过自定义Jackson注解实现接口返回数据脱敏

    SpringBoot中整合Sharding Sphere实现数据加解密/数据脱敏/数据库密文,查询明文: SpringBoot中整合Sharding Sphere实现数据加解密/数据脱敏/数据库密文,查询明文_霸道流氓气质的博客-CSDN博客 上面讲的是数据库中存储密文,查询时使用明文的脱敏方式,如果是需要数据库中存储 明文

    2024年02月16日
    浏览(34)
  • SpringBoot中接口幂等性实现方案-自定义注解+Redis+拦截器实现防止订单重复提交

    SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数): SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数)_redis防刷_霸道流氓气质的博客-CSDN博客 以下接口幂等性的实现方式与上面博客类似,可参考。 什么是幂等性? 幂等

    2024年02月15日
    浏览(38)
  • 使用 redis 实现分布式接口限流注解 RedisLimit

    前言 很多时候,由于种种不可描述的原因,我们需要针对单个接口实现接口限流,防止访问次数过于频繁。这里就用 redis+aop 实现一个限流接口注解 @RedisLimit 代码 点击查看RedisLimit注解代码 AOP代码 点击查看aop代码 lua脚本代码 注意:脚本代码是放在 resources 文件下的,它的类型是

    2024年02月08日
    浏览(43)
  • SpringBoot自定义注解+AOP+redis实现防接口幂等性重复提交,从概念到实战

    本文为千锋教育技术团独家创作,更多技术类知识干货,点个关注持续追更~ 接口幂等性是Web开发中非常重要的一个概念,它可以保证多次调用同一个接口不会对结果产生影响。如果你想了解更多关于接口幂等性的知识,那么本文就是一个不错的起点。 在Web开发中,我们经常

    2024年02月03日
    浏览(45)
  • LR中监控ORACLE数据库常用计数器(如何自定义Oracle计数器)

    目录 一、添加自定义计数器的方法 1、要创建自定义查询,请执行以下操作: 2、配置文件示例对象 二、常用自定义计数器列表 三、LR中监控ORACLE数据库常用计数器遇到问题及处理 1. 在安装路径的Mercury LoadRunnerdatmonitors找到vmon.cfg文件,打开。 2. 在vmon.cfg文件的第三行中,

    2024年02月15日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包