惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
The GitHub Blog
The GitHub Blog
I
InfoQ
A
About on SuperTechFans
GbyAI
GbyAI
宝玉的分享
宝玉的分享
爱范儿
爱范儿
博客园 - 【当耐特】
博客园 - 司徒正美
博客园 - 聂微东
P
Proofpoint News Feed
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
B
Blog RSS Feed
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
J
Java Code Geeks
博客园 - 叶小钗

博客园 - 华安

C#中Microsoft.Extensions.Caching.Memory 与 System.Runtime.Caching.MemoryCache区别 自适应网格系统:CSS Grid中repeat()、auto-fill与auto-fit的深度解析 CSS3中响应式布局两大神器display:flex和display:grid springBoot中的 pom.xml文件 bulid学习 CSS中元素的display显示方式有多种,隐藏、块级、内联、内联-块级 SQl Server 中的 go 是什么作用 CSS中 display:flex的align-items: stretch; 移动端浏览器(尤其是 iOS Safari)的橡皮筋回弹效果(Overscroll / Bounce Effect) 手机端浏览器上ES6中的Fetch回调执行 window.open没效果 用Flex实现兼容性好的全屏布局 在 VS Code 中使用 C# Dev Kit 和 Unity Tools 调试 Unity 2022 Unity 可编程物件(ScriptableObject) 微信小程序中的 联系客服 最基本的使用方法 wx.requestSubscribeMessage(Object object) 和 wx.requestSubscribeDeviceMessage(Object object) 这两个有什么区别 微信小程序中 wx.hideLoading() 后调用 wx.showToast()的问题 Windows 中启动 Nginx的常用命令 CSS进阶技巧:字体渐变、描边、倒影与渐变色描边全解析 netCore 中各DLL引用了 SkiaSharp.dll的问题 unity中预制体解包 在 Unity 中,Time.timeScale实现游戏暂停加速等 微信小程序中关联微信支付 unity中的 Navigation AI使用 C#中TaskCompletionSource(简称 TCS)学习 Unity 编辑器 中,快捷键 Ctrl + Shift + F 的功能 unity中按下 F键,让物体聚焦 微信中进入定页面的,判断时通过扫二维码进入的,还是点小程序名称进入的 MYSQL中从JSON字符串中提取指定的值 Unity2022中创建动画 Animation(旧方法) Unity 中区别,public 和 [SerializeField] Unity中 onCollisionEnter2D与OnTriggerEnter2D 区别
Spring-boot 中基于 IP 的限流和自动封禁 Filter
华安 · 2026-01-28 · via 博客园 - 华安
package com.idmt.difyweb.filter;

import com.idmt.difyweb.common.WebUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.*;
import org.springframework.core.annotation.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@FunctionalInterface
interface MyLogger {
    void apply(String message);
}

@Component
public class RateLimitFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(RateLimitFilter.class);
    MyLogger myLogger=logger::info; //System.out::println; //
    // 配置参数
    //统计一分钟的限流 1分钟60秒 1秒1000毫秒
    private static  final int MAX_Restricted_Milliseconds = 10000; //10 * 1000; //10秒
    private static final int MAX_REQUESTS_PER_MINUTE = 10; //150的请求,指定时间内
    private static final int MAX_404_COUNT = 10; //5次返回 404,指定时间内
    private static final long BLOCK_TIME_MILLIS = 30 * 60 * 1000; // 限制 30分钟
    private static final long CLEANUP_INTERVAL = 2 * 60 * 1000; // 5分钟清理一次
    private static class IpStats {
        private final AtomicInteger requestCount = new AtomicInteger(0);//0;
        private final AtomicInteger error404Count =  new AtomicInteger(0); //0;
        private final AtomicLong lastResetTime = new AtomicLong(System.currentTimeMillis());

        public boolean isExpired(long currentTime) {
            return currentTime - lastResetTime.get() > MAX_Restricted_Milliseconds;
        }
        public void reset(long currentTime) {
            requestCount.set(0);
            error404Count.set(0);
            lastResetTime.set(currentTime);
        }
    }

    // 存储IP的请求统计信息
    private final ConcurrentHashMap<String, IpStats> ipStatsMap = new ConcurrentHashMap<>();
    // 存储被封禁的IP及其解封时间
    private final ConcurrentHashMap<String, Long> blackListMap = new ConcurrentHashMap<>();

    private ScheduledExecutorService cleanupExecutor;

    private void printRresult(ConcurrentHashMap<String, Long> hashMap){
        for (Map.Entry<?, ?> entry : hashMap.entrySet()) {
            myLogger.apply("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
    private void printRresultIPStats(ConcurrentHashMap<String, IpStats> hashMap){
        for (Map.Entry<?, ?> entry : hashMap.entrySet()) {
            IpStats ipStats=(IpStats)entry.getValue();
            myLogger.apply("Key: " + entry.getKey() +
                    ", requestCount: " + ipStats.requestCount.get()+
                    ", error404Count: " + ipStats.error404Count.get()+
                    ", lastResetTime:"+ipStats.lastResetTime.get()
                    );
        }
    }
    private void printALl(){
        myLogger.apply("---封闭的IP----");
        printRresult(blackListMap);
        myLogger.apply("---IP的404次数----");
        printRresultIPStats(ipStatsMap);
    }
    //封闭IP
    private void blockIP(String ip,long currentTime){
        myLogger.apply("开始封闭IP");
        blackListMap.put(ip, currentTime + BLOCK_TIME_MILLIS);
        // 可选: 清理统计数据
        ipStatsMap.remove(ip);
    }
    /**
     * 清理过期数据,防止内存泄漏
     */
    private void cleanup() {
        myLogger.apply("execute cleanup");
        long currentTime = System.currentTimeMillis();
        // 清理过期IP统计
        ipStatsMap.entrySet().removeIf(entry ->
                entry.getValue().isExpired(currentTime)
        );
        // 清理已解封的IP
        blackListMap.entrySet().removeIf(entry ->
                entry.getValue() <= currentTime
        );
        printALl();
    }
    @Override
    public void init(FilterConfig filterConfig) {

        // 定期清理过期IP和黑名单
        cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "RateLimit-Cleanup");
            t.setDaemon(true);
            return t;
        });
        //cleanupExecutor.scheduleAtFixedRate(this::cleanup, CLEANUP_INTERVAL, CLEANUP_INTERVAL, TimeUnit.MILLISECONDS);
        cleanupExecutor.scheduleWithFixedDelay(this::cleanup, CLEANUP_INTERVAL, CLEANUP_INTERVAL, TimeUnit.MILLISECONDS);
    }
    @Override
    public void destroy() {
        myLogger.apply("--开始清理--");
        if (cleanupExecutor != null) {
            cleanupExecutor.shutdown();
        }
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       /* myLogger.apply("--开始过滤--");*/
        printALl();
        HttpServletRequest request1=(HttpServletRequest)request;
        String ip = WebUtil.getClientIp(request1); //request.getRemoteAddr();
        long currentTime = System.currentTimeMillis();

        // 检查是否被封禁
        Long blockUntil = blackListMap.get(ip);
        if (blockUntil != null && blockUntil > currentTime) {
            HttpServletResponse res=(HttpServletResponse) response;
            res.setStatus(429); // 太多请求
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("Access restricted, please try again later。");
            return;
        } else if (blockUntil != null && blockUntil <= currentTime) {
            // 解封
            blackListMap.remove(ip);
        }
        // 统计请求信息
        IpStats stats = ipStatsMap.computeIfAbsent(ip, k -> new IpStats());

        // 存在跨分钟的情况,重置统计
        if (stats.isExpired(currentTime)) {
            stats.reset(currentTime);
        }
        // 请求计数
        stats.requestCount.incrementAndGet();
        ipStatsMap.put(ip, stats);

        // 3. 检查限流(在 请求处理前 检查)
        if (stats.requestCount.get() > MAX_REQUESTS_PER_MINUTE) {
            blockIP(ip,currentTime);
            HttpServletResponse res=(HttpServletResponse) response;
            res.setStatus(429);
            res.setContentType("text/plain;charset=UTF-8");
            res.getWriter().write("Requests are too frequent, please try again later");
            return;
        }

        // 处理请求
        // 使用自定义Response包装器捕获状态码
        StatusCaptureResponseWrapper wrappedResponse = new StatusCaptureResponseWrapper((HttpServletResponse) response);
        try {
            chain.doFilter(request, wrappedResponse);
        }
        finally {
            // 判断响应状态码
            if (wrappedResponse.getStatus() == 404) {
                stats.error404Count.incrementAndGet();
            }

            // 超过404阈值,加入黑名单
            if (stats.error404Count.get() >= MAX_404_COUNT) {
                blockIP(ip,currentTime);
            }
        }
    }

    /**
     * 自定义ResponseWrapper,用于捕获状态码
     */
    private static class StatusCaptureResponseWrapper extends HttpServletResponseWrapper {
        private int httpStatus = 200;

        public StatusCaptureResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void setStatus(int sc) {
            super.setStatus(sc);
            this.httpStatus = sc;
        }

        @Override
        public void sendError(int sc) throws IOException {
            super.sendError(sc);
            this.httpStatus = sc;
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            super.sendError(sc, msg);
            this.httpStatus = sc;
        }

        public int getStatus() {
            return this.httpStatus;
        }
    }
}