All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.fmeng.limiter.util.HitKeyUtils Maven / Gradle / Ivy

The newest version!
package me.fmeng.limiter.util;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import me.fmeng.limiter.Hitter;
import me.fmeng.limiter.configure.bean.LimiterItemProperties;
import me.fmeng.limiter.configure.bean.LimiterResourceProperties;
import me.fmeng.limiter.constant.LimiterConstant;
import me.fmeng.limiter.exception.LimiterConfigException;
import me.fmeng.limiter.exception.RequestParamException;
import me.fmeng.limiter.infrastructure.hitter.ResourceBO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 生成限流器的key
 *
 * @author fmeng
 * @since 2018/07/27
 */
@Slf4j
public class HitKeyUtils {

    private HitKeyUtils() {
    }

    /**
     * 生成限流器的key
     *
     * @param appId                 前缀
     * @param limiterItemProperties 限流项
     * @return key
     */
    public static String generateKey(String appId, ResourceBO resourceBO, LimiterItemProperties limiterItemProperties) {
        List resList = Lists.newArrayListWithCapacity(10);
        fillAppInfo(resList, LimiterConstant.LIMITER_KEY_PREFIX, appId);
        fillConfigInfo(resList, limiterItemProperties);
        if (limiterItemProperties.getResource() != null) {
            fillMergedRequestInfo(resList, limiterItemProperties.getResource(), resourceBO);
        }
        return resList.stream().filter(StringUtils::isNotBlank).collect(Collectors.joining(LimiterConstant.LIMITER_KEY_SEPARATOR));
    }


    /**
     * 判断是否命中配置指定的限流项
     *
     * @param requestBO 请求资源信息
     * @param item      限流配置
     * @return true:命中
     */
    public static boolean match(ResourceBO requestBO, LimiterItemProperties item) {
        switch (item.getLimiterStrategyType()) {
            case CUSTOM:
                // 委派的命中器
                Class customHitterClass = item.getCustomHitterClass();
                Preconditions.checkNotNull(customHitterClass, "customHitterClass不能为空");
                Hitter hitter = SpringBeanUtils.getBean(customHitterClass);
                return hitter.hit(requestBO, item);
            case ANNOTATION:
                // 注解限流
                Set requestNames = requestBO.getAnnotationLimiterNames() == null ? Collections.emptySet() : requestBO.getAnnotationLimiterNames();
                boolean hit = requestNames.contains(item.getName());
                if (item.getResource() != null && item.getResource().getReverse()) {
                    return !hit;
                }
                return hit;
            case URL:
                // 请求限流
                return requestHit(requestBO, item);
            default:
                // 安全控制
                throw new RuntimeException("不支持的限流类型");
        }
    }

    /**
     * 请求
     *
     * @param requestBO 请求的参数, 不可能为null
     * @param item      配置项, 不可能为null
     * @return 所有参数都匹配成功, true:匹配成功
     */
    private static boolean requestHit(ResourceBO requestBO, LimiterItemProperties item) {
        boolean hit = match(requestBO, item.getResource());
        if (item.getResource().getReverse()) {
            return !hit;
        }
        return hit;
    }

    /**
     * 判读是否成功匹配所有的资源参数
     *
     * @param requestBO        请求的参数, 不可能为null
     * @param tryToHitResource 配置的参数, 不可能为null
     * @return 所有参数都匹配成功, true:匹配成功
     */
    private static boolean match(ResourceBO requestBO, LimiterResourceProperties tryToHitResource) {
        // 请求方法
        if (!tryToHitResource.getRequestMethods().contains(requestBO.getRequestMethod())) {
            return false;
        }
        // 请求url
        String requestUrl = requestBO.getRequestUrl() == null ? "" : requestBO.getRequestUrl();
        if (!Pattern.matches(tryToHitResource.getPathRegex(), requestUrl)) {
            return false;
        }
        // 业务参数
        Map requestParam = requestBO.getParameterMap() == null ? Collections.emptyMap() : requestBO.getParameterMap();
        List params = tryToHitResource.getParams() == null ? Collections.emptyList() : tryToHitResource.getParams();
        for (LimiterResourceProperties.ParamProperties configParam : params) {
            String[] requestValues = requestParam.get(configParam.getParamName());
            if (ArrayUtils.isEmpty(requestValues) || requestValues.length > 1) {
                log.error("匹配限流器,请求参数不合法,没有找到参数名或匹配到多个值, key={}, values={}", configParam.getParamName(), requestValues);
                throw new RequestParamException("请求参数不合法");
            }
            if (!configParam.getDynamic()) {
                if (CollectionUtils.isEmpty(configParam.getParamValues())) {
                    log.error("匹配限流器,限流器配置信息异常,匹配到的参数不能为空, param={}", configParam);
                    throw new LimiterConfigException("限流配置错误");
                }
                boolean configContainsRequest = Sets.newHashSet(configParam.getParamValues()).contains(requestValues[0]);
                if (!configContainsRequest) {
                    return false;
                }
            }
        }
        // 限流项匹配成功
        return true;
    }

    /**
     * 填充应用信息
     *
     * @param resList   被填充的容器
     * @param keyPrefix 限流器前缀
     * @param appId     应用ID
     */
    private static void fillAppInfo(List resList, String keyPrefix, String appId) {
        resList.add(keyPrefix);
        resList.add(appId);
    }

    /**
     * 填充配置信息
     *
     * @param resList    被填充的容器
     * @param configInfo 配置信息
     */
    private static void fillConfigInfo(List resList, LimiterItemProperties configInfo) {
        resList.add(configInfo.getName());
        resList.add(configInfo.getPermits().toString());
        resList.add(configInfo.getRateInterval().toString());
        resList.add(configInfo.getTimeUnit().toString());
        resList.add(DigestUtils.md5DigestAsHex(configInfo.toString().getBytes(Charsets.UTF_8)));
    }

    /**
     * 填充动态合并的信息
     *
     * @param resList    被填充的容器
     * @param configInfo 配置信息
     * @param resourceBO 请求信息
     */
    private static void fillMergedRequestInfo(List resList, LimiterResourceProperties configInfo, ResourceBO resourceBO) {
        // 请求信息
        Map requestParameters = resourceBO.getParameterMap() == null ? Collections.emptyMap() : resourceBO.getParameterMap();
        // 配置信息
        List params = configInfo.getParams() == null ? Collections.emptyList() : configInfo.getParams();
        // 合并信息
        for (LimiterResourceProperties.ParamProperties param : params) {
            resList.add(param.getParamName());
            resList.add(param.getDynamic().toString());
            if (param.getDynamic()) {
                // 动态构造
                String[] requestValues = requestParameters.get(param.getParamName());
                if (ArrayUtils.isEmpty(requestValues) || requestValues.length > 1) {
                    log.error("生成hitKey,请求参数不合法,没有找到参数名或匹配到多个值, key={}, values={}", param.getParamName(), requestValues);
                    throw new RequestParamException("请求参数不合法");
                }
                resList.add(requestValues[0]);
            } else {
                // 配置信息
                if (CollectionUtils.isEmpty(param.getParamValues())) {
                    log.error("生成hitKey,限流器配置信息异常,匹配到的参数不能为空, param={}", param);
                    throw new LimiterConfigException("限流配置错误");
                }
                resList.add(DigestUtils.md5DigestAsHex(param.getParamValues().toString().getBytes(Charsets.UTF_8)));
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy