用Caffeine和自定义注解+AOP优雅实现本地防抖接口限流

一、背景与需求

在实际项目开发中,经常遇到接口被前端高频触发按钮被多次点击或者接口重复提交的问题,导致服务压力变大、数据冗余、甚至引发幂等性/安全风险。

常规做法是前端节流/防抖、后端用Redis全局限流、或者API网关限流。但在很多场景下:

  • 接口只要求单机(本地)防抖,不需要全局一致性;

  • 只想让同一个业务对象(同一手机号、同一业务ID、唯一标识)在自定义设置秒内只处理一次

  • 想要注解式配置,让代码更优雅、好维护。

这个时候,Caffeine+自定义注解+AOP的本地限流(防抖)方案非常合适。


二、方案设计

1. Caffeine介绍

Caffeine 是目前Java领域最热门、性能最高的本地内存缓存库,QPS可达百万级,适用于低延迟、高并发、短TTL缓存场景。
在本地限流、防抖、接口去重等方面天然有优势。

2. 自定义注解+AOP

用自定义注解(如@DebounceLimit)标记要防抖的接口,AOP切面拦截后判断是否需要限流,核心思路是:

  • 以唯一标识作为key;

  • 每次访问接口,先查询本地Caffeine缓存;

  • 如果key在2秒内已被处理过,则直接拦截;

  • 否则执行业务逻辑,并记录处理时间。

这种方式无侵入、代码简洁、可扩展性强,适合绝大多数本地场景。

效果图如下:


三、完整实现步骤

1.Pom依赖如下

        <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>

2. 定义自定义注解

 
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DebounceLimit {/*** 唯一key(支持SpEL表达式,如 #dto.id)*/String key();/*** 防抖时间,单位秒*/int ttl() default 2;/*** 是否返回上次缓存的返回值*/boolean returnLastResult() default true;
}


3. 配置Caffeine缓存Bean

 
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configuration
public class DebounceCacheConfig {@Beanpublic Cache<String, Object> debounceCache() {return Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(100_000).build();}
}


4. 编写AOP切面

 
import com.github.benmanes.caffeine.cache.Cache;
import com.lps.anno.DebounceLimit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;@Slf4j
@Aspect
@Component
public class DebounceLimitAspect {@Autowiredprivate Cache<String, Object> debounceCache;private final ExpressionParser parser = new SpelExpressionParser();@Around("@annotation(debounceLimit)")public Object around(ProceedingJoinPoint pjp, DebounceLimit debounceLimit) throws Throwable {// 1. 获取方法、参数MethodSignature methodSignature = (MethodSignature) pjp.getSignature();Method method = methodSignature.getMethod();Object[] args = pjp.getArgs();String[] paramNames = methodSignature.getParameterNames();StandardEvaluationContext context = new StandardEvaluationContext();for (int i = 0; i < paramNames.length; i++) {context.setVariable(paramNames[i], args[i]);}// 2. 解析SpEL表达式得到唯一keyString key = parser.parseExpression(debounceLimit.key()).getValue(context, String.class);String cacheKey = method.getDeclaringClass().getName() + "." + method.getName() + ":" + key;long now = System.currentTimeMillis();DebounceResult<Object> debounceResult = (DebounceResult<Object>) debounceCache.getIfPresent(cacheKey);if (debounceResult != null && (now - debounceResult.getTimestamp() < debounceLimit.ttl() * 1000L)) {String methodName = pjp.getSignature().toShortString();log.error("接口[{}]被限流, key={}", methodName, cacheKey);// 是否返回上次结果if (debounceLimit.returnLastResult() && debounceResult.getResult() != null) {return debounceResult.getResult();}// 统一失败响应,可自定义异常或返回结构return new RuntimeException("操作过于频繁,请稍后再试!");}Object result = pjp.proceed();debounceCache.put(cacheKey, new DebounceResult<>(result, now));return result;}@Getterstatic class DebounceResult<T> {private final T result;private final long timestamp;public DebounceResult(T result, long timestamp) {this.result = result;this.timestamp = timestamp;}}
}


5. 控制器里直接用注解实现防抖

 
@RestController
@RequiredArgsConstructor
@Slf4j
public class DebounceControl {private final UserService userService;@PostMapping("/getUsernameById")@DebounceLimit(key = "#dto.id", ttl = 10)public String test(@RequestBody User dto) {log.info("在{}收到了请求,参数为:{}", DateUtil.now(), dto);return userService.getById(dto.getId()).getUsername();}
}

只要加了这个注解,同一个id的请求在自定义设置的秒内只处理一次,其他直接被拦截并打印日志。


四、扩展与注意事项

  1. SpEL表达式灵活

    • 可以用 #dto.id#dto.mobile#paramName等,非常适合多参数、复杂唯一性业务场景。

  2. returnLastResult适合有“缓存返回结果”的场景

    • 比如查询接口、表单重复提交直接复用上次的返回值。

  3. 本地限流仅适用于单机环境

    • 多节点部署建议用Redis分布式限流,原理一样。

  4. 缓存key建议加上方法签名

    • 避免不同接口之间key冲突。

  5. Caffeine最大缓存、过期时间应根据业务并发和内存合理设置

    • 绝大多数接口几千到几万key都没压力。


五、适用与不适用场景

适用:

  • 单机接口防抖/限流

  • 短时间重复提交防控

  • 按业务唯一标识维度防刷

  • 秒杀、报名、投票等接口本地保护

不适用:

  • 分布式场景(建议用Redis或API网关限流)

  • 需要全局一致性的业务

  • 内存非常敏感/极端高并发下,需结合Redis做混合限流


六、总结

Caffeine + 注解 + AOP的本地限流防抖方案,实现简单、代码优雅、性能极高、扩展灵活
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/81594.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【IP101】纹理特征提取与分析:从统计方法到深度表征的系统解析

纹理分析详解 &#x1f3a8; 纹理分析就像是给图像做"指纹识别"&#xff01;每种纹理都有其独特的"指纹"&#xff0c;就像木纹的条纹、布料的编织、草地的随机分布一样。让我们一起来探索这个既有趣又实用的图像处理领域吧&#xff01; 目录 1. 什么是纹理…

机器学习中采样哪些事

在机器学习中采样主要分为两种&#xff0c;过采样&#xff08;Oversample&#xff09;和欠采样(Undersample)。过采样就是通过增加少数类样本的数量来平衡数据集。而欠采样就是通过减少多数类样本的数量来平衡数据集。 通常在进行采样中以下是几种常用的方法: 1. 随机采样 随…

fastadmin 数据导出,设置excel行高和限制图片大小

fastadmin默认导出图片全部都再一块&#xff0c;而且不在单元格里 话不多说&#xff0c;上代码 修改文件的路径&#xff1a; /public/assets/js/require-table.js exportOptions: {fileName: export_ Moment().format("YYYY-MM-DD"),preventInjection: false,mso…

鸿蒙OSUniApp开发的商品详情展示页面(鸿蒙系统适配版)#三方框架 #Uniapp

使用UniApp开发的商品详情展示页面&#xff08;鸿蒙系统适配版&#xff09; 前言 随着移动电商的普及&#xff0c;一个体验良好的商品详情页对于提高用户转化率至关重要。本文将分享我在使用UniApp开发商品详情页时的实践经验&#xff0c;并特别关注如何适配鸿蒙系统&#xf…

redis中key的过期和淘汰

一、过期&#xff08;redis主动删除&#xff09; 设置了ttl过期时间的key&#xff0c;在ttl时间到的时候redis会删除过期的key。但是redis是惰性过期。惰性过期&#xff1a;redis并不会立即删除过期的key&#xff0c;而是会在获取key的时候判断key是否过期&#xff0c;如果发现…

Qwen3 - 0.6B与Bert文本分类实验:深度见解与性能剖析

Changelog [25/04/28] 新增Qwen3-0.6B在Ag_news数据集Zero-Shot的效果。新增Qwen3-0.6B线性层分类方法的效果。调整Bert训练参数&#xff08;epoch、eval_steps&#xff09;&#xff0c;以实现更细致的观察&#xff0c;避免严重过拟合的情况。 TODO&#xff1a; 利用Qwen3-0.6…

HTML应用指南:利用POST请求获取全国京东快递服务网点位置信息

京东快递作为中国领先的智能供应链与综合物流服务提供商,自2007年成立以来,始终致力于通过技术创新与高效运营,为客户提供安全、可靠、快速的物流解决方案。京东快递依托京东集团的强大资源支持,凭借其自营仓储、干线运输、末端配送一体化的物流网络,在激烈的市场竞争中脱…

js中eval的用法风险与替代方案全面解析

1. 前言 在 JavaScript 里&#xff0c;eval是一个既强大又充满争议的函数。它为开发者提供了一种动态执行字符串代码的能力&#xff0c;在某些特定场景下能发挥出独特的作用。然而&#xff0c;由于其特殊的运行机制&#xff0c;也带来了诸多潜在的风险和问题。本文将深入探讨e…

antd树结构

一、场景实现 1、左侧为查询条件&#xff0c;查询条件为树和多选。点击查询条件在右上方显示搜索条件的内容&#xff0c;右上方查询条件 tag 删除后&#xff0c;左侧条件也对应删除。 2、树结构&#xff1a;默认第一层下所有节点都展开。 1、页面效果图 2、查询效果图 二、前端…

Jenkins 安装与配置指南

Jenkins 安装与配置指南&#xff08;MD 示例&#xff09; markdown Jenkins 安装与配置指南 ## 一、环境准备 1. **系统要求** - 操作系统&#xff1a;Linux/macOS/Windows - Java 版本&#xff1a;JDK 8 或更高&#xff08;建议 JDK 11&#xff09;2. **安装方式** - **L…

[Linux性能优化] 线程卡顿优化。Linux加入USB(HID)热插拔线程占用CPU优化。Linux中CPU使用率过高优化

文章目录 [Linux性能优化] 线程卡顿优化。0、省流版本一、问题定位&#xff1a;CPU 资源分析二、线程卡顿现场复现线程优化前图片 三、线程卡顿优化方向1.如果是轮询方式2.如果是事件驱动方式 四、修改方式线程优化后图片 [Linux性能优化] 线程卡顿优化。 0、省流版本 如果采…

ip与mac-数据包传输过程学习

你管这破玩意叫网络&#xff1f; 内容来源于飞天闪客&#xff0c;以前没有学习过网络的相关基础知识&#xff0c;只会去瞎设置&#xff0c;现在终于是弄明白了。 多台电脑之间想要通信&#xff0c;可以直接通过一条网线进行连接。但是随着网线的增加&#xff0c;这个就会比较…

数值分析知识重构

数值分析知识重构 一 Question 请构造一下数值分析中的误差概念以及每一个具体数值方法的误差是如何估计的&#xff1f; 二 Question 已知 n 1 n1 n1个数据点 ( x i , y i ) , i 0 , 1 , ⋯ , n (x_i,y_i),i0,1,\cdots,n (xi​,yi​),i0,1,⋯,n,请使用多种方法建立数据之间…

使用 Qt QGraphicsView/QGraphicsScene 绘制色轮

使用 Qt QGraphicsView/QGraphicsScene 绘制色轮 本文介绍如何在 Qt 中利用 QGraphicsView 和 QGraphicsScene 实现基础圆形绘制&#xff0c;以及进阶的色轮&#xff08;Color Wheel&#xff09;效果。 色轮是色彩选择器的常见控件&#xff0c;广泛应用于图形设计、绘画和 UI …

移除链表元素数据结构oj题(力扣题206)

目录 题目描述&#xff1a; 题目解读&#xff08;分析&#xff09; 解决代码 题目描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 题目解读&#xff08;分析&#…

GLPK(GNU线性规划工具包)中建模语言MathProg的使用

GNU MathProg是一种用于描述线性数学规划模型的建模语言。用GNU MathProg语言编写的模型描述由一组语句和数据块组成。 在MathProg中&#xff0c;模型以集合、参数、变量、约束和目标(sets, parameters, variables, constraints, objectives称为模型对象)的形式进行描述。 在Ma…

《Python星球日记》 第77天:模型部署与总结

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、模型部署技术1. 模型文件导出不同模型格式对比2. 使用Flask构建RESTful API3. 使用FastAPI构建高性能API4. 部署优化与最佳实践二、部署架构…

【JavaWeb】MySQL(准备篇)

1 MySQL安装 1.1 解压 下载完成后我们得到的是一个压缩包&#xff08;所有文件均在文末安装包中&#xff09;&#xff0c;将其解压&#xff0c;我们就可以得到MySQL 8.0.34 的软件本体了(就是一个文件夹)&#xff0c;我们可以把它放在你想安装的位置 。 1.2 配置 1.2.1 配置…

国产数据库工具突围:SQLynx如何解决Navicat的三大痛点?深度体验报告

引言&#xff1a;Navicat的"中国困境" 当开发者面对达梦数据库的存储过程调试&#xff0c;或是在人大金仓中处理复杂查询时&#xff0c;Navicat突然变得力不从心——这不是个例。 真实痛点&#xff1a;某政务系统迁移至OceanBase后&#xff0c;开发团队发现Navicat无…

ETL数据集成产品选型需要关注哪些方面?

ETL&#xff08;Extract&#xff0c;Transform&#xff0c;Load&#xff09;工具作为数据仓库和数据分析流程中的关键环节&#xff0c;其选型对于企业的数据战略实施有着深远的影响。谷云科技在 ETL 领域耕耘多年&#xff0c;通过自身产品的实践应用&#xff0c;对 ETL 产品选型…