使用aop,redis计数来控制单位时间内对某接口的访问量,防止刷验证码接口之类的

限频注解实现
本文介绍了一种基于Spring Boot、AOP及Redis实现的方法访问频率限制方案。通过自定义注解控制方法调用频率,利用Aspect切面进行访问计数与限制判断。

转自:https://blog.youkuaiyun.com/tianyaleixiaowu/article/details/74549145

使用自定义注解的方式,在需要被限制访问频率的方法上加注解即可控制。

看实现方式,基于springboot,aop,redis。

新建Springboot工程,引入redis,aop。

创建注解

package com.tianyalei.annotation;
 
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
 
import java.lang.annotation.*;
 
/**
 * Created by wuwf on 17/7/6.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
//最高优先级
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RequestLimit {
    /**
     * 允许访问的次数
     */
    int count() default 5;
 
    /**
     * 时间段,多少时间段内运行访问count次
     */
    long time() default 60000;
 
}

Aspect切面处理逻辑

package com.tianyalei.aspect;
 
import com.tianyalei.annotation.RequestLimit;
import com.tianyalei.util.HttpRequestUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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.util.concurrent.TimeUnit;
 
/**
 * Created by wuwf on 17/7/6.
 */
@Component
@Aspect
public class RequestLimitAspect {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
 
    @Before("execution(public * com.tianyalei.controller.*.*(..)) && @annotation(limit)")
    public void requestLimit(JoinPoint joinpoint, RequestLimit limit) {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
 
        String ip = HttpRequestUtil.getIpAddr(request);
        String url = request.getRequestURL().toString();
        String key = "req_limit_".concat(url).concat(ip);
 
        //加1后看看值
        long count = redisTemplate.opsForValue().increment(key, 1);
        //刚创建
        if (count == 1) {
            //设置1分钟过期
            redisTemplate.expire(key, limit.time(), TimeUnit.MILLISECONDS);
        }
        if (count > limit.count()) {
            logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]");
            throw new RuntimeException("超出访问次数限制");
        }
    }

获取IP的工具类

package com.tianyalei.util;
 
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
 
/**
 * Created by admin on 17/7/6.
 */
public class HttpRequestUtil {
    /**
     * 获取当前网络ip
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress = inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }
}

 

<think>好的,我现在需要帮助用户找到在Java程序中限制接口访问量的方法或实现方案。用户之前已经提供了一些相关的引用内容,我需要仔细分析这些引用,并结合我的知识来给出合适的回答。 首先,引用[1]提到了RateLimiter和令牌桶算法,这是Google Guava库中的一个工具,可以用来控制速率。用户给出的示例代码展示了如何使用RateLimiter来限制每秒的执行次数。这应该是一个有效的方法,因为令牌桶算法允许突发流量,同时又能维持平均速率。 接下来,引用[2]和[4]都涉及到使用Spring框架的AOP(面向切面编程)来实现访问次数限制。这里的关键是结合注解和切面来拦截方法调用,统计访问次数。例如,通过自定义注解@RequestLimit,然后在切面中检查单位时间内的调用次数,如果超过限制则抛出异常。引用[4]中的代码片段显示使用了一个ConcurrentHashMap来存储每个IP地址的访问次数,并设置了过期时间,这样可以自动清理旧的记录,避免内存泄漏。 引用[5]提到了记录和显示日访问量和总访问量,虽然主要关注的是统计,但其中的思路也可以用于限制访问量。比如,使用ServletContext来存储计数,但这种方法可能不够精确,尤其是在分布式系统中,不过对于单机应用可能有效。 现在需要综合这些方法,考虑用户可能的场景。用户可能是在开发Web应用,需要限制某个接口的访问频率防止恶意攻击或过度使用。根据引用内容,主要的实现方案有: 1. **Guava RateLimiter(令牌桶算法)**:适用于单机限流,配置简单,支持突发流量。 2. **Spring AOP + 自定义注解**:适合在Spring项目中实现方法级别的限流,可以结合IP或用户标识进行细粒度控制。 3. **分布式限流(Redis计数器)**:当应用部署在多个实例时,需要借助Redis这样的分布式存储来同步计数,确保全局限制。 4. **Semaphore信号量**:控制并发线程数,防止资源过载。 需要注意的是,RateLimiter和Semaphore都是单机的解决方案,如果应用是分布式的,可能需要使用Redis等中间件来实现跨实例的限流。另外,AOP的方式可以非侵入式地应用到现有代码中,提升可维护性。 用户可能还需要了解如何选择这些方案,比如根据应用架构是单机还是分布式,或者是否需要支持突发流量。例如,令牌桶算法允许一定程度的突发,而漏桶算法则更平滑。此外,引用[4]中的代码使用ConcurrentHashMap和自定义的过期策略,这可能在高并发下存在性能问题,而使用Caffeine或Guava的Cache库可以更高效地处理过期条目。 最后,应该提醒用户考虑限流后的处理策略,比如直接拒绝、排队等待或返回降级内容。同时,异常处理和监控也是重要部分,确保系统稳定性和可观察性。</think>在Java程序中实现接口访问量限制主要有以下五种方案,根据应用场景和实现复杂度可选择不同方案: ### 一、Guava RateLimiter(令牌桶算法) 使用Google Guava库提供的令牌桶算法实现单机限流,适合控制接口访问速率: ```java public class ApiLimiter { private static final RateLimiter limiter = RateLimiter.create(5); // 每秒5个令牌 public void processRequest() { if (limiter.tryAcquire()) { // 非阻塞获取令牌 // 执行业务逻辑 } else { throw new RuntimeException("接口访问超限"); } } } ``` 优势:支持突发流量,配置简单[^1] ### 二、Spring AOP + 自定义注解 通过AOP切面实现方法级细粒度控制: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestLimit { int count() default 5; int time() default 60000; // 单位毫秒 } @Aspect @Component public class RequestLimitAspect { private ConcurrentHashMap<String, AtomicInteger> counter = new ConcurrentHashMap<>(); @Around("@annotation(limit)") public Object checkLimit(ProceedingJoinPoint pjp, RequestLimit limit) throws Throwable { String key = getClientIP(); // 获取客户端标识 AtomicInteger count = counter.computeIfAbsent(key, k -> new AtomicInteger(0)); if (count.incrementAndGet() > limit.count()) { throw new RequestLimitException("访问过于频繁"); } try { return pjp.proceed(); } finally { count.decrementAndGet(); } } } ``` 特点:支持方法级注解配置,可与IP/用户绑定[^2][^4] ### 三、分布式限流(Redis实现) 使用Redis+Lua脚本保证原子操作: ```java public boolean accessLimit(String key, int limit, int timeout) { Jedis jedis = jedisPool.getResource(); String luaScript = "local current = redis.call('incr',KEYS[1]) " + "if current == 1 then redis.call('expire',KEYS[1],ARGV[2]) end " + "return current <= tonumber(ARGV[1])"; Object result = jedis.eval(luaScript, 1, key, String.valueOf(limit), String.valueOf(timeout)); return (Long)result == 1; } ``` 适用场景:微服务架构下的分布式限流 ### 四、Semaphore信号量控制 使用JDK原生并发工具控制并发量: ```java public class SemaphoreLimiter { private final Semaphore semaphore = new Semaphore(10); // 允许10个并发 public void process() throws InterruptedException { semaphore.acquire(); try { // 执行业务逻辑 } finally { semaphore.release(); } } } ``` 特点:控制并发线程数,防止资源耗尽 ### 五、Servlet Filter全局控制 通过过滤器实现接口访问统计: ```java @WebFilter("/*") public class AccessFilter implements Filter { private AtomicInteger counter = new AtomicInteger(0); private static final int MAX_ACCESS = 1000; public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { if (counter.incrementAndGet() > MAX_ACCESS) { ((HttpServletResponse)res).sendError(429); return; } chain.doFilter(req, res); counter.decrementAndGet(); } } ``` 特点:适合全局请求控制[^5] ### 方案选型建议表 | 方案 | 适用场景 | 性能 | 分布式支持 | |------|---------|------|-----------| | RateLimiter | 单机速率控制 | 高 | ❌ | | Spring AOP | 方法级细粒度控制 | 中 | ❌ | | Redis | 分布式系统 | 中 | ✔️ | | Semaphore | 资源并发控制 | 高 | ❌ | | Servlet Filter | 全局请求限制 | 中 | ❌ |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值