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

推荐订阅源

J
Java Code Geeks
G
Google Developers Blog
有赞技术团队
有赞技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客
博客园 - 聂微东
V
Visual Studio Blog
博客园_首页
D
DataBreaches.Net
腾讯CDC
I
InfoQ
F
Fortinet All Blogs
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
月光博客
月光博客
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Check Point Blog

博客园 - 华安

自适应网格系统: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 区别 asp.netCore中给动态请求路径加客户端缓存
利用Spring Boot的 filter 结合ConcurrentHashMap 实现“同一I...
华安 · 2026-01-23 · via 博客园 - 华安

实现思路:

  • 使用两个ConcurrentHashMap
    • 一个存储每个IP的请求统计信息(请求数、404数、统计更新时间)
    • 一个存储被封禁的IP及封禁到期时间
  • 通过过滤器中的doFilter(),在请求前检查是否被封禁,在请求后检查响应状态码,统计404次数
  • 超过404阈值时封禁IP,直到30分钟后自动解封

代码示例

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@WebFilter(urlPatterns = "/*")
public class RateLimitFilter implements Filter {

    private static class IpStats {
        int requestCount = 0;
        int error404Count = 0;
        long lastResetTime = System.currentTimeMillis();
    }

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

    // 配置参数
    private static final int MAX_REQUESTS_PER_MINUTE = 300;
    private static final int MAX_404_COUNT = 300;
    private static final long BLOCK_TIME_MILLIS = 30 * 60 * 1000; // 30分钟

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String ip = request.getRemoteAddr();
        long currentTime = System.currentTimeMillis();

        // 检查是否被封禁
        Long blockUntil = blackListMap.get(ip);
        if (blockUntil != null && blockUntil > currentTime) {
            ((HttpServletResponse) response).setStatus(429); // 太多请求
            response.getWriter().write("访问受限,请稍后重试。");
            return;
        } else if (blockUntil != null && blockUntil <= currentTime) {
            // 解封
            blackListMap.remove(ip);
        }

        // 统计请求信息
        IpStats stats = ipStatsMap.computeIfAbsent(ip, k -> new IpStats());

        // 存在跨分钟的情况,重置统计
        if (currentTime - stats.lastResetTime > 60 * 1000) {
            stats.requestCount = 0;
            stats.error404Count = 0;
            stats.lastResetTime = currentTime;
        }

        // 请求计数
        stats.requestCount++;
        ipStatsMap.put(ip, stats);

        // 处理请求
        // 使用自定义Response包装器捕获状态码
        StatusCaptureResponseWrapper wrappedResponse = new StatusCaptureResponseWrapper((HttpServletResponse) response);
        chain.doFilter(request, wrappedResponse);

        // 判断响应状态码
        if (wrappedResponse.getStatus() == 404) {
            stats.error404Count++;
        }

        // 超过404阈值,加入黑名单
        if (stats.error404Count >= MAX_404_COUNT) {
            blackListMap.put(ip, currentTime + BLOCK_TIME_MILLIS);
            // 可选: 清理统计数据
            ipStatsMap.remove(ip);
        }
    }

    /**
     * 自定义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;
        }
    }
}

配置说明

  • 这个Filter需要添加到Spring Boot中,可以通过@WebFilter注解,或者在配置类中注册。
  • 请求到达时,先检查IP是否被封禁。
  • 请求结束后,根据响应状态码更新404计数。
  • 超过404阈值,会封禁IP30分钟。

小结

  • 这是一个纯Filter方案示例,适合单实例部署。
  • 生产环境建议结合缓存(如Redis)或中间件以支持多实例。