Java 实现 对外接口进行aop限流-包括获取访问IP工具类

发布于:2025-03-31 ⋅ 阅读:(20) ⋅ 点赞:(0)

Java 实现 对外接口进行aop限流

1.需要的maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.限流注解InterfaceLimit.java

特此注意: 这是一个注解类 @interface

package com.safesoft.config.customlimit;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * [自定义接口限流注解]
 *
 * @author Kevin.Wan
 * @date 2023/8/1
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface InterfaceLimit {

    /**
     * 资源的key,唯一
     * 作用:不同的接口,不同的流量控制
     */
    String key() default "";

    /**
     * 最多的访问限制次数
     */
    double permitsPerSecond () ;

    /**
     * 获取令牌最大等待时间
     */
    long timeout();

    /**
     * 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
     */
    TimeUnit timeunit() default TimeUnit.MILLISECONDS;


}

3.限流注解的切面类LimitAop.java

package com.safesoft.config.customlimit;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.safesoft.domain.utils.IPUtils;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;

import static com.safesoft.domain.common.Constants.DATE_TIME_FORMATTER;

/**
 * [基于aop切面实现限流]
 *
 * @author Kevin.Wan
 * @date 2023/8/1
 **/
@Slf4j
@Aspect
@Component
public class LimitAop {
    /**
     * 不同的接口,不同的流量控制
     * map的key为 Limiter.key
     */
    private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();

    @Around("@annotation(com.safesoft.config.customlimit.InterfaceLimit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //获取当前时间
        final String format_time = LocalDateTime.now().format(DATE_TIME_FORMATTER);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //拿limit的注解
        InterfaceLimit limit = method.getAnnotation(InterfaceLimit.class);
        if (limit != null) {
            //key作用:不同的接口,不同的流量控制
            String key=limit.key();
            RateLimiter rateLimiter = null;
            //验证缓存是否有命中key
            if (!limitMap.containsKey(key)) {
                // 创建令牌桶
                rateLimiter = RateLimiter.create(limit.permitsPerSecond());
                limitMap.put(key, rateLimiter);
                log.info("新建了令牌桶={},容量={}",key,limit.permitsPerSecond());
            }
            rateLimiter = limitMap.get(key);
            // 拿令牌
            boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
            //获取request
            final HttpServletRequest request = getHttpServletRequest();
            // 请求的真实ip
            final String realIP = IPUtils.getRealIP(request);
            // 拿不到命令,直接返回异常提示
            if (!acquire) {
                log.error("当前请求人数过多进入等待,请求ip:{},请求时间:{}", realIP, format_time);
                throw new IllegalStateException("请求次数太频繁,请稍后...");
            }
            log.info("获取令牌成功,IP:{},时间:{}", realIP, format_time);
        }
        return joinPoint.proceed();
    }

    protected final HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }


}

4.里面用到了获取访问IP工具类(可以不用)

package com.safesoft.domain.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;


/**
 * @Author Sherlock.shen
 * @Date 2021/3/25 17:39
 * @Description: 获取访问ip工具类
 */
public class IPUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(IPUtils.class);
    private static final String STR_UNKNOWN_LOWER_CASE = "unKnown";
    private static final String STR_COMMA = ",";
    public static final int NUM_ZERO = 0;
    public static final int NUM_ONE = 1;

    public static String getRealIP(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != NUM_ZERO && !STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(STR_COMMA) != -NUM_ONE) {
                ip = ip.split(STR_COMMA)[NUM_ZERO];
            }
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            LOGGER.info("Proxy-Client-IP ip,{}", ip);
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            LOGGER.info("WL-Proxy-Client-IP,{}", ip);
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            LOGGER.info("HTTP_CLIENT_IP ip,{}", ip);
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            LOGGER.info("HTTP_X_FORWARDED_FOR,{}", ip);
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
            LOGGER.info("X-Real-IP ip,{}", ip);
        }
        if (ip == null || ip.length() == NUM_ZERO || STR_UNKNOWN_LOWER_CASE.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            LOGGER.info("getRemoteAddr ip,{}", ip);
        }
        return ip;
    }
}

5.然后就可以使用了 例如

/**
 * 水位监测看板数据==》也就是根据设备查询实时数据
 */
@LogAction("对外水位监测看板数据")
@PostMapping(URLConstant.WATER_LEVEL_OPEN_MONITORING)
@InterfaceLimit(key = "getWaterLevel",permitsPerSecond = 5,timeout = 500,timeunit = TimeUnit.MILLISECONDS)
public JSONObject getWaterLevel(@RequestBody @Validated ExternalParam externalParam){

    LOGGER.info("对外接口-获取水位监测看板数据");

    //stop1 验签
    final Boolean aBoolean = externalService.signatureCheck(externalParam);
    if (aBoolean.equals(Boolean.FALSE)){
        throw new IllegalStateException("验签失败");
    }

    //stop2 判断是否有组编号
    String groupId = "";
    if (!Strings.isNullOrEmpty(externalParam.getGroupId())){
        groupId = externalParam.getGroupId();
    }

    //stop3 根据组编号查询设备实时数据

    return cockpitService.getWaterLevelRealTimeData(groupId);
}

其中 @InterfaceLimit(key = “getWaterLevel”,permitsPerSecond = 5,timeout = 500,timeunit = TimeUnit.MILLISECONDS) 这个就是

key任意起,不重复即可,,后面代表的是 每秒最多5个请求 每秒的前500毫秒响应


网站公告

今日签到

点亮在社区的每一天
去签到