当前位置: 首页 > news >正文

一种基于注解与AOP的Spring Boot接口限流防刷方案

1. 添加Maven依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

2. 创建自定义注解

import java.lang.annotation.*;/*** 接口防刷注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AccessLimit {/*** 限制时间范围(秒)*/int time() default 60;/*** 时间范围内最大访问次数*/int maxCount() default 10;/*** 是否检查IP地址*/boolean checkIp() default true;/*** 是否检查用户身份(需要登录)*/boolean checkUser() default false;/*** 触发限制时的提示信息*/String message() default "操作过于频繁,请稍后再试";
}

3. 创建AOP切面实现防护逻辑

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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
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.lang.reflect.Method;
import java.util.concurrent.TimeUnit;@Aspect
@Component
public class AccessLimitAspect {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Around("@annotation(accessLimit)")public Object around(ProceedingJoinPoint joinPoint, AccessLimit accessLimit) throws Throwable {// 获取请求对象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes == null) {return joinPoint.proceed();}HttpServletRequest request = attributes.getRequest();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();// 构建Redis keyString key = buildKey(request, method, accessLimit);// 获取当前计数ValueOperations<String, Object> operations = redisTemplate.opsForValue();Integer count = (Integer) operations.get(key);if (count == null) {// 第一次访问operations.set(key, 1, accessLimit.time(), TimeUnit.SECONDS);} else if (count < accessLimit.maxCount()) {// 计数增加operations.increment(key);} else {// 超出限制,抛出异常throw new RuntimeException(accessLimit.message());}return joinPoint.proceed();}/*** 构建Redis key*/private String buildKey(HttpServletRequest request, Method method, AccessLimit accessLimit) {StringBuilder key = new StringBuilder("access_limit:");// 添加方法标识key.append(method.getDeclaringClass().getName()).append(".").append(method.getName()).append(":");// 添加IP标识if (accessLimit.checkIp()) {String ip = getClientIp(request);key.append(ip).append(":");}// 添加用户标识(需要实现获取当前用户的方法)if (accessLimit.checkUser()) {// 这里需要根据你的用户系统实现获取当前用户ID的方法String userId = getCurrentUserId();if (userId != null) {key.append(userId).append(":");}}return key.toString();}/*** 获取客户端IP*/private String getClientIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {// 多次反向代理后会有多个ip值,第一个ip才是真实ipif (ip.contains(",")) {ip = ip.split(",")[0];}}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}/*** 获取当前用户ID(需要根据实际情况实现)*/private String getCurrentUserId() {// 实现获取当前用户ID的逻辑// 可以从Session、Token或Spring Security上下文等获取return null;}
}

4. 创建全局异常处理器

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(RuntimeException.class)public Result handleRuntimeException(RuntimeException e) {return Result.error(e.getMessage());}
}

5. 在Controller中使用注解

import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api")
public class DemoController {// 基于IP的限制:60秒内最多访问10次@AccessLimit(time = 60, maxCount = 10, checkIp = true, checkUser = false)@GetMapping("/public/data")public String getPublicData() {return "这是公开数据";}// 基于用户的限制:30秒内最多访问5次@AccessLimit(time = 30, maxCount = 5, checkIp = false, checkUser = true)@GetMapping("/user/data")public String getUserData() {return "这是用户数据";}// 同时基于IP和用户的限制:60秒内最多访问3次@AccessLimit(time = 60, maxCount = 3, checkIp = true, checkUser = true)@PostMapping("/submit")public String submitData(@RequestBody String data) {return "提交成功: " + data;}
}

文章转载自:

http://rYqo7itT.bsrqy.cn
http://5ATrCWNz.bsrqy.cn
http://oWnzii5r.bsrqy.cn
http://bbCRepss.bsrqy.cn
http://ZBhXU9an.bsrqy.cn
http://FgsNQvDZ.bsrqy.cn
http://buDcjzIU.bsrqy.cn
http://i2SmymLL.bsrqy.cn
http://k0sdc8S7.bsrqy.cn
http://wM4vKfP4.bsrqy.cn
http://ffrCr2V4.bsrqy.cn
http://w6jvhyab.bsrqy.cn
http://MTTkc5Wf.bsrqy.cn
http://b1baR2NF.bsrqy.cn
http://dkBoANok.bsrqy.cn
http://OWZz9I6b.bsrqy.cn
http://wxUBFMcU.bsrqy.cn
http://4NYa3WWk.bsrqy.cn
http://qD12x46L.bsrqy.cn
http://mtDuJA9Y.bsrqy.cn
http://omQE0LBd.bsrqy.cn
http://dn1Qrfqd.bsrqy.cn
http://JnnJSVo9.bsrqy.cn
http://YpdCGEGV.bsrqy.cn
http://ACum1l3N.bsrqy.cn
http://IdCzwimy.bsrqy.cn
http://pe7UhLwh.bsrqy.cn
http://j3VYi0xZ.bsrqy.cn
http://UL0JRltL.bsrqy.cn
http://VSsreVTo.bsrqy.cn
http://www.dtcms.com/a/369572.html

相关文章:

  • 新启航开启深孔测量新纪元:激光频率梳技术攻克光学遮挡,达 130mm 深度 2μm 精度
  • ZyperWin++一个超好用的工具
  • 共用体与枚举:C++高效内存技巧
  • Semi-Supervised 3-D Medical
  • 解决网络太慢问题
  • IP5326_BZ 支持C同口输入输出的移动电源芯片 2.4A的充放电电流 支持4LED指示灯
  • 如何通过 Gitee API 上传文件到指定仓库
  • 商密保护密码:非公知性鉴定的攻防之道
  • 从零到上线:Docker、Docker Compose 与 Runtime 安装部署全指南(含实战示例与应用场景)
  • 2025 年 8 个最佳网站内容管理系统(CMS)
  • Java中的包
  • 彻底搞懂深度学习:强化学习和智能体(动图讲解)
  • 基于STM32单片机FM调频TEA5767功放收音机液晶显示设计
  • 邪修实战系列(1)
  • 今日行情明日机会——20250905
  • MCP(Model Context Protocol)与大模型一起运用
  • 【Lin通信】AUTOSAR架构下TC3xx芯片Lin报文收发详解
  • SDRAM详细分析—06 存储单元架构和放大器
  • stm32——NVIC,EXIT
  • Leetcode每日一练--20
  • 关机之前未正确关闭代理,导致DNS出现问题无法上网的解决方法(windows和linux)
  • Linux查看设备树信息
  • *MOS 半导体功率器件简介 | 结构 / 制程 / 简史
  • @Autowired注解(二)
  • Linux基础指令(入门必备2.0)
  • 打工人日报#20250905
  • 【Leetcode】高频SQL基础题--610.判断三角形
  • CLIP学习
  • docker重启redis报错:iptables failed
  • 一文教您学会Ubuntu安装python