当前位置: 代码网 > it编程>编程语言>Java > springboot自定义注解RateLimiter限流注解技术文档详解

springboot自定义注解RateLimiter限流注解技术文档详解

2025年07月26日 Java 我要评论
什么是限流限流是一种控制系统访问频率的技术手段,就像高速公路的收费站控制车流量一样。生活场景类比:银行atm机:每张卡每天最多取款5次手机验证码:每个手机号每分钟最多发送1条网站登录:每个ip每分钟最

什么是限流

限流是一种控制系统访问频率的技术手段,就像高速公路的收费站控制车流量一样。

生活场景类比:

  • 银行atm机:每张卡每天最多取款5次
  • 手机验证码:每个手机号每分钟最多发送1条
  • 网站登录:每个ip每分钟最多尝试5次

技术价值:

  1. 防止恶意攻击:阻止暴力破解、恶意爬虫
  2. 保护系统稳定:避免瞬间大量请求压垮服务器
  3. 提升用户体验:确保正常用户的访问质量
  4. 节约成本:减少不必要的资源消耗

系统架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   用户请求      │───→│   限流切面      │───→│   业务接口      │
│   (http api)   │    │  (aop拦截)     │    │  (controller)   │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   限流服务      │
                    │ (核心逻辑处理)  │
                    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   缓存存储      │
                    │ (ehcache/redis) │
                    └─────────────────┘

工作流程:

  1. 用户发起http请求
  2. spring aop切面拦截带有@ratelimiter注解的方法
  3. 限流服务根据注解配置生成限流键
  4. 从缓存中获取当前访问次数
  5. 判断是否超过限制,决定放行或拒绝
  6. 更新缓存中的计数器

核心组件详解

1. 限流注解 (@ratelimiter)

这是系统的核心注解,定义了限流的各种参数:

package cn.jbolt.config.anno.ratelimiter;

import org.springframework.core.annotation.aliasfor;
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.type, elementtype.method})
public @interface ratelimiter {

    /**
     * 缓存前缀 - 用于区分不同业务的限流数据
     */
    string prefix() default "jblimit:";

    /**
     * 时间窗口(秒) - 限流的时间范围
     */
    int time() default 60;

    /**
     * 允许访问次数 - 时间窗口内最大访问次数
     */
    @aliasfor(attribute = "count")
    int value() default 12;

    /**
     * 限制类型 - 决定按什么维度限流
     */
    ratelimittype limittype() default ratelimittype.default;

    /**
     * 限制提示消息 - 触发限流时返回的错误信息
     */
    string msg() default "操作过于频繁,请稍后重试";

    /**
     * 允许访问次数 - 与value互为别名
     */
    @aliasfor(attribute = "value")
    int count() default 12;

    /**
     * 自定义键 - 当limittype为custom时使用
     */
    string customkey() default "";

    /**
     * 是否启用 - 可用于动态开关限流功能
     */
    boolean enabled() default true;

    /**
     * 额外的时间窗口限制(秒)
     * 实现双重限流:比如1秒最多1次 + 1分钟最多10次
     */
    int extratime() default -1;

    /**
     * 额外时间窗口内的允许访问次数
     */
    int extracount() default -1;

    /**
     * 额外限制的提示消息
     */
    string extramsg() default "";
}

2. 限流类型枚举 (ratelimittype)

package cn.jbolt.config.anno.ratelimiter;

public enum ratelimittype {
    /**
     * 默认限制(全局)
     * 所有请求共享一个计数器
     */
    default,
    
    /**
     * 基于ip地址限制
     * 每个ip独立计数
     */
    ip,
    
    /**
     * 基于用户id限制
     * 每个登录用户独立计数
     */
    user,
    
    /**
     * 基于自定义key限制
     * 根据业务逻辑自定义限流维度
     */
    custom
}

3. 限流异常类 (ratelimitexception)

package cn.jbolt.config.exception;

public class ratelimitexception extends runtimeexception {
    
    private final string message;
    private final int retryafter;
    
    public ratelimitexception(string message) {
        this(message, 0);
    }
    
    public ratelimitexception(string message, int retryafter) {
        super(message);
        this.message = message;
        this.retryafter = retryafter;
    }
    
    @override
    public string getmessage() {
        return message;
    }
    
    public int getretryafter() {
        return retryafter;
    }
}

4. 全局异常处理器 (ratelimitexceptionhandler)

package cn.jbolt.config.handler;

import cn.jbolt.config.exception.ratelimitexception;
import org.springframework.http.httpstatus;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;

import javax.servlet.http.httpservletresponse;
import java.util.hashmap;
import java.util.map;

@restcontrolleradvice
public class ratelimitexceptionhandler {
    
    @exceptionhandler(ratelimitexception.class)
    public responseentity<map<string, object>> handleratelimitexception(
            ratelimitexception e, httpservletresponse response) {
        
        map<string, object> result = new hashmap<>();
        result.put("code", httpstatus.too_many_requests.value());
        result.put("message", e.getmessage());
        result.put("data", null);
        
        // 设置http响应头,告诉客户端多久后可以重试
        if (e.getretryafter() > 0) {
            response.setheader("retry-after", string.valueof(e.getretryafter()));
        }
        response.setheader("x-ratelimit-window", "60");
        
        return responseentity.status(httpstatus.too_many_requests).body(result);
    }
}

5. ip工具类 (iputils)

package cn.jbolt.util;

import org.springframework.util.stringutils;
import javax.servlet.http.httpservletrequest;

public class iputils {
    
    private static final string[] ip_header_names = {
        "x-forwarded-for",
        "x-real-ip", 
        "proxy-client-ip",
        "wl-proxy-client-ip",
        "http_client_ip",
        "http_x_forwarded_for"
    };
    
    private static final string unknown = "unknown";
    private static final string localhost_ipv4 = "127.0.0.1";
    private static final string localhost_ipv6 = "0:0:0:0:0:0:0:1";
    
    /**
     * 获取客户端真实ip地址
     * 处理代理服务器、负载均衡器等场景
     */
    public static string getclientip(httpservletrequest request) {
        if (request == null) {
            return unknown;
        }
        
        string ip = null;
        
        // 依次检查各种可能的ip头
        for (string header : ip_header_names) {
            ip = request.getheader(header);
            if (isvalidip(ip)) {
                break;
            }
        }
        
        // 如果头信息中没有找到,则使用getremoteaddr
        if (!isvalidip(ip)) {
            ip = request.getremoteaddr();
            if (localhost_ipv6.equals(ip)) {
                ip = localhost_ipv4;
            }
        }
        
        // 处理多个ip的情况(x-forwarded-for可能包含多个ip)
        if (stringutils.hastext(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        
        return stringutils.hastext(ip) ? ip : unknown;
    }
    
    /**
     * 检查ip是否有效
     */
    private static boolean isvalidip(string ip) {
        return stringutils.hastext(ip) && !unknown.equalsignorecase(ip);
    }
}

技术实现原理

1. aop切面拦截

系统使用spring aop在方法执行前进行拦截,这是一个核心的限流切面类:

package cn.jbolt.config.aspect;

import cn.jbolt.config.anno.ratelimiter.ratelimiter;
import cn.jbolt.config.anno.ratelimiter.ratelimittype;
import cn.jbolt.config.exception.ratelimitexception;
import cn.jbolt.util.iputils;
import cn.jbolt.util.cache.ratelimitercache;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.stereotype.component;
import org.springframework.web.context.request.requestcontextholder;
import org.springframework.web.context.request.servletrequestattributes;

import javax.servlet.http.httpservletrequest;
import java.util.concurrent.timeunit;

@aspect
@component
public class ratelimiteraspect {
    
    private static final logger logger = loggerfactory.getlogger(ratelimiteraspect.class);
    
    @around("@annotation(ratelimiter)")
    public object around(proceedingjoinpoint point, ratelimiter ratelimiter) throws throwable {
        
        // 检查是否启用限流
        if (!ratelimiter.enabled()) {
            return point.proceed();
        }
        
        // 获取http请求对象
        httpservletrequest request = getcurrentrequest();
        if (request == null) {
            logger.warn("无法获取httpservletrequest,跳过限流检查");
            return point.proceed();
        }
        
        // 生成限流键
        string limitkey = generatelimitkey(point, ratelimiter, request);
        
        // 执行主要限流检查
        checkratelimit(limitkey, ratelimiter.time(), ratelimiter.count(), ratelimiter.msg());
        
        // 执行额外限流检查(如果配置了)
        if (ratelimiter.extratime() > 0 && ratelimiter.extracount() > 0) {
            string extralimitkey = limitkey + ":extra";
            string extramsg = ratelimiter.extramsg().isempty() ? ratelimiter.msg() : ratelimiter.extramsg();
            checkratelimit(extralimitkey, ratelimiter.extratime(), ratelimiter.extracount(), extramsg);
        }
        
        // 所有限流检查通过,继续执行业务方法
        return point.proceed();
    }
    
    /**
     * 执行限流检查
     */
    private void checkratelimit(string key, int timewindow, int maxcount, string message) {
        try {
            // 增加计数器并获取当前访问次数
            int currentcount = ratelimitercache.incrementandget(key, timewindow, timeunit.seconds);
            
            logger.debug("限流检查: key={}, 当前次数={}, 限制次数={}", key, currentcount, maxcount);
            
            // 检查是否超过限制
            if (currentcount > maxcount) {
                long ttl = ratelimitercache.getttl(key);
                logger.warn("触发限流: key={}, 当前次数={}, 限制次数={}, 剩余时间={}秒", 
                          key, currentcount, maxcount, ttl);
                throw new ratelimitexception(message, (int) ttl);
            }
            
        } catch (ratelimitexception e) {
            throw e;
        } catch (exception e) {
            logger.error("限流检查异常: key={}", key, e);
            // 限流服务异常时,选择放行而不是阻塞
        }
    }
    
    /**
     * 生成限流键
     */
    private string generatelimitkey(proceedingjoinpoint point, ratelimiter ratelimiter, httpservletrequest request) {
        stringbuilder keybuilder = new stringbuilder();
        keybuilder.append(ratelimiter.prefix());
        
        // 添加方法签名
        string methodsignature = point.getsignature().toshortstring();
        keybuilder.append(methodsignature);
        
        // 根据限流类型添加不同的标识
        switch (ratelimiter.limittype()) {
            case ip:
                keybuilder.append(":ip:").append(iputils.getclientip(request));
                break;
            case user:
                string userid = getcurrentuserid(request);
                keybuilder.append(":user:").append(userid != null ? userid : "anonymous");
                break;
            case custom:
                keybuilder.append(":custom:").append(ratelimiter.customkey());
                break;
            case default:
            default:
                keybuilder.append(":default:global");
                break;
        }
        
        // 添加时间窗口,确保不同时间窗口的限流独立
        keybuilder.append(":").append(ratelimiter.time());
        
        string finalkey = keybuilder.tostring();
        logger.debug("生成限流键: {}", finalkey);
        return finalkey;
    }
    
    /**
     * 获取当前http请求
     */
    private httpservletrequest getcurrentrequest() {
        try {
            servletrequestattributes attrs = (servletrequestattributes) requestcontextholder.getrequestattributes();
            return attrs != null ? attrs.getrequest() : null;
        } catch (exception e) {
            logger.warn("获取httpservletrequest失败", e);
            return null;
        }
    }
    
    /**
     * 获取当前用户id
     * 这里需要根据实际的用户认证体系来实现
     */
    private string getcurrentuserid(httpservletrequest request) {
        // 方案1:从session中获取
        object userid = request.getsession().getattribute("userid");
        if (userid != null) {
            return userid.tostring();
        }
        
        // 方案2:从jwt token中获取
        string token = request.getheader("authorization");
        if (token != null && token.startswith("bearer ")) {
            // 解析jwt获取用户id
            // return jwtutils.getuseridfromtoken(token);
        }
        
        // 方案3:从请求参数中获取
        string useridparam = request.getparameter("userid");
        if (useridparam != null) {
            return useridparam;
        }
        
        return null;
    }
}

2. 缓存数据结构

系统使用一个包装类来存储缓存数据:

package cn.jbolt.util.cache;

import java.io.serializable;
import java.util.concurrent.timeunit;

public class cachewrapper implements serializable {
    
    private static final long serialversionuid = 1l;
    
    private object value;
    private long timestamp;
    private long durationmillis;
    
    public cachewrapper() {
    }
    
    public cachewrapper(object value, long duration, timeunit unit) {
        this.value = value;
        this.timestamp = system.currenttimemillis();
        this.durationmillis = unit.tomillis(duration);
    }
    
    /**
     * 检查是否已过期
     */
    public boolean isexpired() {
        return system.currenttimemillis() - timestamp > durationmillis;
    }
    
    /**
     * 获取剩余过期时间(毫秒)
     */
    public long getremainingtime() {
        long elapsed = system.currenttimemillis() - timestamp;
        return math.max(0, durationmillis - elapsed);
    }
    
    // getter和setter方法
    public object getvalue() {
        return value;
    }
    
    public void setvalue(object value) {
        this.value = value;
    }
    
    public long gettimestamp() {
        return timestamp;
    }
    
    public void settimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    
    public long getdurationmillis() {
        return durationmillis;
    }
    
    public void setdurationmillis(long durationmillis) {
        this.durationmillis = durationmillis;
    }
}

完整代码示例

1. 控制器示例

package cn.jbolt.controller;

import cn.jbolt.config.anno.ratelimiter.ratelimiter;
import cn.jbolt.config.anno.ratelimiter.ratelimittype;
import org.springframework.web.bind.annotation.*;

@restcontroller
@requestmapping("/api")
public class democontroller {
    
    /**
     * 登录接口 - 防止暴力破解
     * 每个ip每分钟最多尝试5次
     */
    @postmapping("/login")
    @ratelimiter(
        limittype = ratelimittype.ip,
        time = 60, 
        count = 5,
        msg = "登录尝试过于频繁,请1分钟后重试"
    )
    public result login(@requestbody loginrequest request) {
        // 登录逻辑
        if (isvaliduser(request.getusername(), request.getpassword())) {
            return result.success("登录成功");
        } else {
            return result.error("用户名或密码错误");
        }
    }
    
    /**
     * 发送验证码 - 防止恶意发送
     * 每个ip每分钟最多3次
     */
    @postmapping("/sms/send")
    @ratelimiter(
        limittype = ratelimittype.ip,
        time = 60, 
        count = 3,
        msg = "验证码发送过于频繁,请稍后重试"
    )
    public result sendsms(@requestbody smsrequest request) {
        // 发送短信逻辑
        boolean success = smsservice.sendcode(request.getphone());
        return success ? result.success("发送成功") : result.error("发送失败");
    }
    
    /**
     * 查询接口 - 防止爬虫
     * 每个ip每分钟最多100次
     */
    @getmapping("/products")
    @ratelimiter(
        limittype = ratelimittype.ip,
        time = 60, 
        count = 100,
        msg = "查询过于频繁,请稍后重试"
    )
    public result getproducts(@requestparam(defaultvalue = "1") int page) {
        // 查询商品逻辑
        list<product> products = productservice.getproducts(page);
        return result.success(products);
    }
    
    /**
     * 用户操作 - 防止频繁操作
     * 每个用户每分钟最多30次
     */
    @postmapping("/user/update")
    @ratelimiter(
        limittype = ratelimittype.user,
        time = 60, 
        count = 30,
        msg = "操作过于频繁,请稍后重试"
    )
    public result updateuser(@requestbody userupdaterequest request) {
        // 更新用户信息逻辑
        boolean success = userservice.updateuser(request);
        return success ? result.success("更新成功") : result.error("更新失败");
    }
    
    /**
     * 关键操作 - 严格限流
     * 1秒最多1次 + 1分钟最多5次
     */
    @postmapping("/transfer")
    @ratelimiter(
        limittype = ratelimittype.user,
        time = 1, count = 1, msg = "操作过于频繁,请稍后再试",
        extratime = 60, extracount = 5, extramsg = "您在1分钟内的操作次数已达上限"
    )
    public result transfer(@requestbody transferrequest request) {
        // 转账逻辑
        boolean success = transferservice.transfer(request);
        return success ? result.success("转账成功") : result.error("转账失败");
    }
    
    /**
     * 自定义限流 - 按商品限制
     * 每个商品每分钟最多下单20次
     */
    @postmapping("/order/{productid}")
    @ratelimiter(
        limittype = ratelimittype.custom,
        customkey = "product_order",
        time = 60, 
        count = 20,
        msg = "该商品下单过于频繁,请稍后重试"
    )
    public result createorder(@pathvariable string productid, @requestbody orderrequest request) {
        // 创建订单逻辑
        order order = orderservice.createorder(productid, request);
        return result.success(order);
    }
    
    // 辅助方法
    private boolean isvaliduser(string username, string password) {
        // 实际的用户验证逻辑
        return "admin".equals(username) && "123456".equals(password);
    }
}

2. 统一返回对象

package cn.jbolt.common;

public class result {
    private int code;
    private string message;
    private object data;
    
    public static result success(object data) {
        result result = new result();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }
    
    public static result error(string message) {
        result result = new result();
        result.code = 500;
        result.message = message;
        result.data = null;
        return result;
    }
    
    // getter和setter方法
    public int getcode() {
        return code;
    }
    
    public void setcode(int code) {
        this.code = code;
    }
    
    public string getmessage() {
        return message;
    }
    
    public void setmessage(string message) {
        this.message = message;
    }
    
    public object getdata() {
        return data;
    }
    
    public void setdata(object data) {
        this.data = data;
    }
}

使用指南

1. 基本使用

// 最简单的用法 - 使用默认配置
@ratelimiter(limittype = ratelimittype.ip)
public string simpleapi() {
    return "success";
}

// 自定义时间窗口和次数
@ratelimiter(
    limittype = ratelimittype.ip,
    time = 60,    // 60秒
    count = 100   // 最多100次
)
public string customapi() {
    return "success";
}

2. 不同场景的配置建议

// 登录接口 - 严格限制
@ratelimiter(
    limittype = ratelimittype.ip,
    time = 60, count = 5,
    msg = "登录尝试过于频繁,请1分钟后重试"
)

// 查询接口 - 适中限制
@ratelimiter(
    limittype = ratelimittype.ip,
    time = 60, count = 100,
    msg = "查询过于频繁,请稍后重试"
)

// 用户操作 - 按用户限制
@ratelimiter(
    limittype = ratelimittype.user,
    time = 60, count = 30,
    msg = "操作过于频繁,请稍后重试"
)

// 全局保护 - 系统级限制
@ratelimiter(
    limittype = ratelimittype.default,
    time = 60, count = 200,
    msg = "系统繁忙,请稍后重试"
)

3. 双重限流配置

// 严格的双重限流:秒级 + 分钟级
@ratelimiter(
    limittype = ratelimittype.ip,
    time = 1, count = 1, msg = "请求过于频繁,请稍后再试",
    extratime = 60, extracount = 10, extramsg = "您在1分钟内的请求次数已达上限"
)

// 适中的双重限流:分钟级 + 小时级
@ratelimiter(
    limittype = ratelimittype.ip,
    time = 60, count = 100, msg = "1分钟内请求过多",
    extratime = 3600, extracount = 1000, extramsg = "1小时内请求过多"
)

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com