Redis+AOP实现接口限流防刷
·
项目开发中,需要防止用户频繁请求接口(如登录、注册、短信验证码等),提升系统安全性与稳定性。可以通过 Redis + 拦截器/AOP + 自定义注解 实现接口防刷功能(即 限流防刷)。
一、实现思路
-
自定义注解:标记需要防刷的controller层接口,配置限流规则(如:每 5 秒最多 3 次)。
-
自定义AOP :在请求执行前进行拦截,判断是否超过频率限制。
-
Redis:存储访问计数(可以设定
IP:接口路径或用户ID:接口等规则),利用INCR和EXPIRE实现滑动时间窗口计数。 -
拒绝策略:超过阈值则返回“请求过于频繁”。
二、实际代码
1、创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LimitRateByAop {
//限流key的前缀
String prefix() default "rate_limit:";
//时间窗口(秒)
int timeOut() default 60;
//最大请求次数
int maxCount() default 3;
//限流后提示内容
String message() default "请求次数过多,请稍后再试!";
//限流键的生成策略
KeyType keyType() default KeyType.IP;
enum KeyType{
IP,
USER,
PATH,
IP_PATH
}
}
2、实现AOP切面类
@Aspect
@Component
@Slf4j
public class MyRateLimitAspect {
@Resource
private RedisTemplate<String, Integer> redisTemplate;
@Around("@annotation(com.mdx.annotation.LimitRateByAop)")
public Object around (ProceedingJoinPoint proceedingJoinPoint){
try {
//获取请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//判断方法上是否有MyRateLimitByAop注解
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
LimitRateByAop limitRateByAop = method.getAnnotation(LimitRateByAop.class);
//未被标注,直接放过去
if(Objects.isNull(limitRateByAop)){
return proceedingJoinPoint.proceed();
}
//获取注解参数
int timeWindow = limitRateByAop.timeOut();
int maxCount = limitRateByAop.maxCount();
String message = limitRateByAop.message();
String key =generateKey(limitRateByAop, request, method);
if(Strings.isBlank(key)){
//无法生成key,跳过
return proceedingJoinPoint.proceed();
}
// 自增计数(Redis会自动初始化不存在的Key为0,再自增为1)
Long count = redisTemplate.opsForValue().increment(key, 1);
// 处理count为null的情况(Redis连接异常等)
if (count == null) {
log.warn("Redis自增操作返回null,key:{}", key);
return proceedingJoinPoint.proceed();
}
// 首次计数(Key刚创建),设置过期时间
if (count == 1) {
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
log.debug("限流Key首次创建,设置过期时间,key:{}, timeWindow:{}秒", key, timeWindow);
}
// 限流判断:计数超过最大限制,返回提示信息
if (count > maxCount) {
log.warn("接口限流触发,key:{}, 当前计数:{}, 最大限制:{}", key, count, maxCount);
// 建议返回统一格式的响应对象(而非直接返回字符串),示例:
// return Result.fail(429, message);
return message;
}
// 放行,执行目标方法
return proceedingJoinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException("Aop实现限流报错:{}",e.getCause());
}
}
private String generateKey(LimitRateByAop limitRateByAop, HttpServletRequest serverRequest, Method method){
String prefix = limitRateByAop.prefix();
LimitRateByAop.KeyType keyType = limitRateByAop.keyType();
String ip = getIpAddre(serverRequest);
String uri = serverRequest.getRequestURI();
String key = "";
switch (keyType){
case IP: key = prefix + ":" + ip;
case USER: key = prefix + ":" + "user";
case PATH: key = prefix + ":" + uri;
case IP_PATH: key = prefix + ":" + ip + ":" + uri;
}
return key;
}
private String getIpAddre(HttpServletRequest servletRequest){
String ip = null;
//X-Forwarded-For: 经过层层代理后,最终客户端真实IP在最前面
String xForwardedFor = servletRequest.getHeader("X-Forwarded-For");
if(Strings.isNotBlank(xForwardedFor) && "unKnown".equalsIgnoreCase(xForwardedFor)){
ip = xForwardedFor.split(",")[0].trim();
}
// Proxy-Client-IP: WebLogic代理服务器IP
if(Strings.isBlank(ip)){
ip = servletRequest.getHeader("Proxy-Client-IP");
}
//WL-Proxy-Client-IP: WebLogic代理服务器IP
if(Strings.isBlank(ip)){
ip = servletRequest.getHeader("WL-Proxy-Client-IP");
}
//获取远程地址
if(Strings.isBlank(ip)){
ip = servletRequest.getRemoteAddr();
}
// 处理IPv6本地回环地址
if("0:0:0:0:0:0:0:1".equals(ip)) {
ip ="127.0.0.1";
}
return ip;
}
}
3、controller层创建接口
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("sendSms")
@LimitRateByAop(
prefix = "rate_limit:sms",
timeOut = 5,
maxCount = 3,
keyType = LimitRateByAop.KeyType.IP_PATH
)
public String limitRateByAop(){
System.out.println("发送短信");
return "发送短信成功";
}
}
三、结果校验
输入地址:localhost:8021/user/sendSms后,可以查看发送短信成功,之后在5秒内连续刷新超过设置的最大访问次数,会触发接口限流,返回设置的提示信息。
1、首次刷新成功

2、5秒内连续刷新超过3次,触发限流


总结:
通过自定义注解@LimitRateByAop标记需要限流的接口,配置时间窗口和最大请求次数等参数。AOP切面类利用Redis实现滑动时间窗口计数,通过INCR和EXPIRE命令进行访问计数和过期时间设置。当请求超过阈值时返回提示信息,否则放行请求。
如果对大家有帮助的话,点个免费的赞和关注。防止找不到,可以点击收藏呦。
更多推荐

所有评论(0)