init
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>BladeX-Tool</artifactId>
|
||||
<groupId>org.springblade</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-starter-ratelimit</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<module.name>org.springblade.blade.starter.ratelimit</module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--Redis 限流原语-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-redis</artifactId>
|
||||
</dependency>
|
||||
<!--缓存工具-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<!--JdbcTemplate 默认规则装载-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<!-- Auto -->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-auto</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.springblade.core.ratelimit.RateLimitConstant.RULE_SELECT_STATEMENT;
|
||||
|
||||
/**
|
||||
* 限流规则处理器默认实现,经本地数据源直取,适用于单体及持有规则表的服务。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BladeRateLimitHandler implements IRateLimitHandler {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public List<RateLimitRule> listEnabledRules() {
|
||||
return jdbcTemplate.query(RULE_SELECT_STATEMENT, new BeanPropertyRowMapper<>(RateLimitRule.class));
|
||||
}
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springblade.core.launch.server.ServerInfo;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.constant.BladeConstant;
|
||||
import org.springblade.core.tool.support.IdGenerator;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.UrlUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.springblade.core.ratelimit.RateLimitConstant.RATE_LIMIT_LOG_INSERT;
|
||||
|
||||
/**
|
||||
* 限流触发日志处理器默认实现。
|
||||
* <p>
|
||||
* 在主线程采集触发上下文 (请求信息与登录态), 经有界单线程池异步落库, 异常仅记录不影响主流程。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class BladeRateLimitLogHandler implements IRateLimitLogHandler {
|
||||
|
||||
/**
|
||||
* 日志落库专用线程池。
|
||||
* <p>
|
||||
* 限流被触发往往伴随高频请求, 直接走 {@code commonPool} 无界提交会瞬时打满线程与数据库连接。
|
||||
* 此处以单线程串行落库 + 有界队列收口, 队列溢出 (洪峰拒绝) 时丢弃多余日志, 保住主流程与库连接。
|
||||
*/
|
||||
private static final ExecutorService LOG_EXECUTOR = new ThreadPoolExecutor(
|
||||
1, 1, 0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(1024),
|
||||
runnable -> {
|
||||
Thread thread = new Thread(runnable, "rate-limit-log");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
},
|
||||
new ThreadPoolExecutor.DiscardPolicy());
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final BladeProperties bladeProperties;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
@Override
|
||||
public void saveLog(RateLimitRule rule, String limitKey, HttpServletRequest request) {
|
||||
// 在主线程采集参数 (请求与登录态上下文此刻有效), 仅落库动作交由有界线程池异步执行
|
||||
Object[] params = collectLogParams(rule, limitKey, request);
|
||||
LOG_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
jdbcTemplate.update(RATE_LIMIT_LOG_INSERT, params);
|
||||
} catch (Exception logException) {
|
||||
log.error("限流日志保存失败: {}", logException.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 采集日志参数
|
||||
*
|
||||
* @param rule 触发的限流规则
|
||||
* @param limitKey 实际限流计数 key
|
||||
* @param request 当前请求对象
|
||||
* @return SQL 参数数组
|
||||
*/
|
||||
private Object[] collectLogParams(RateLimitRule rule, String limitKey, HttpServletRequest request) {
|
||||
String tenantId = Func.toStrWithEmpty(AuthUtil.getTenantId(), BladeConstant.ADMIN_TENANT_ID);
|
||||
String createBy = Func.toStrWithEmpty(AuthUtil.getUserAccount(), StringPool.EMPTY);
|
||||
String requestUri = UrlUtil.getPath(request.getRequestURI());
|
||||
String httpMethod = request.getMethod();
|
||||
String remoteIp = WebUtil.getIP(request);
|
||||
String userAgent = Func.toStr(request.getHeader(WebUtil.USER_AGENT_HEADER));
|
||||
String params = WebUtil.getRequestContent(request);
|
||||
return new Object[]{
|
||||
IdGenerator.getId(), tenantId,
|
||||
bladeProperties.getName(), serverInfo.getIpWithPort(), serverInfo.getHostName(), bladeProperties.getEnv(),
|
||||
rule.getId(), rule.getLimitName(), rule.getLimitDimension(), rule.getLimitCount(), rule.getLimitPeriod(), rule.getTimeUnit(), limitKey,
|
||||
requestUri, httpMethod, remoteIp, userAgent, params,
|
||||
createBy, new Date()
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流规则处理器接口。
|
||||
* <p>
|
||||
* 抽象规则的取数来源:单体经本地数据源直取,微服务经 Feign 远程取数。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IRateLimitHandler {
|
||||
|
||||
/**
|
||||
* 获取全部启用的限流规则,按优先级升序、跨租户全局。
|
||||
*
|
||||
* @return 启用的限流规则集合,无规则时返回空集
|
||||
*/
|
||||
List<RateLimitRule> listEnabledRules();
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 限流触发日志处理器接口。
|
||||
* <p>
|
||||
* 请求被限流时记录触发上下文。下游工程可自定义实现以支持不同的日志存储策略 (如消息队列、远程服务等)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IRateLimitLogHandler {
|
||||
|
||||
/**
|
||||
* 保存限流触发日志。在主请求线程中调用, 实现类可在内部自行决定同步或异步策略。
|
||||
*
|
||||
* @param rule 触发的限流规则
|
||||
* @param limitKey 实际限流计数 key
|
||||
* @param request 当前请求对象
|
||||
*/
|
||||
void saveLog(RateLimitRule rule, String limitKey, HttpServletRequest request);
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
/**
|
||||
* 限流常量。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface RateLimitConstant {
|
||||
|
||||
/**
|
||||
* 计数 key 前缀, 底层 RateLimiterClient 会再追加 limiter:{应用名}: 前缀
|
||||
*/
|
||||
String KEY_PREFIX = "rateLimit:";
|
||||
|
||||
/**
|
||||
* 匹配全部请求方法的标识
|
||||
*/
|
||||
String METHOD_ALL = "ALL";
|
||||
|
||||
/**
|
||||
* 规则刷新广播频道, 各节点据此热更新本地快照 (须为精确频道, 不含通配符)
|
||||
*/
|
||||
String RULE_REFRESH_CHANNEL = "blade:rate-limit:rule:refresh";
|
||||
|
||||
/**
|
||||
* 加载全部启用规则的 SQL (裸 SQL 天然跨租户全局加载, 按优先级升序)
|
||||
*/
|
||||
String RULE_SELECT_STATEMENT =
|
||||
"SELECT id, limit_name, limit_pattern, request_method, limit_dimension, limit_count, limit_period, time_unit, priority, remark "
|
||||
+ "FROM blade_rate_limit WHERE status = 1 AND is_deleted = 0 ORDER BY priority ASC";
|
||||
|
||||
/**
|
||||
* 插入限流触发日志的 SQL
|
||||
*/
|
||||
String RATE_LIMIT_LOG_INSERT =
|
||||
"INSERT INTO blade_rate_limit_log "
|
||||
+ "(id, tenant_id, service_id, server_ip, server_host, env, limit_id, limit_name, limit_dimension, limit_count, limit_period, time_unit, limit_key, request_uri, method, remote_ip, user_agent, params, create_by, create_time) "
|
||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.ratelimit.enums.RateLimitDimension;
|
||||
import org.springblade.core.redis.ratelimiter.RateLimiterClient;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 限流拦截器。
|
||||
* <p>
|
||||
* 规则按优先级遍历,首条命中 (请求方法 + Ant 路径) 者即按其维度构造计数 key 交 {@link RateLimiterClient} 判定,超限返回 429。
|
||||
* 规则取自 {@link RateLimitRuleManager} 的进程内快照,拦截器不感知其来源 (本地直取 / 远程 Feign) 与同步机制。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RateLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
/**
|
||||
* 滑动窗口限流客户端, 复用 blade-starter-redis 实现
|
||||
*/
|
||||
private final RateLimiterClient rateLimiterClient;
|
||||
|
||||
/**
|
||||
* 触发限流时的提示文案
|
||||
*/
|
||||
private final String rejectMessage;
|
||||
|
||||
/**
|
||||
* 规则匹配前缀, 拼回网关剥离的服务前缀后再与规则比对 (单体为空)
|
||||
*/
|
||||
private final String pathPrefix;
|
||||
|
||||
/**
|
||||
* Ant 风格路径匹配器, 无状态线程安全, 内部对编译后的 pattern 有缓存
|
||||
*/
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private RateLimitRuleManager ruleManager;
|
||||
private IRateLimitLogHandler rateLimitLogHandler;
|
||||
|
||||
private RateLimitRuleManager getRuleManager() {
|
||||
if (ruleManager == null) {
|
||||
ruleManager = SpringUtil.getBean(RateLimitRuleManager.class);
|
||||
}
|
||||
return ruleManager;
|
||||
}
|
||||
|
||||
private IRateLimitLogHandler getRateLimitLogHandler() {
|
||||
if (rateLimitLogHandler == null) {
|
||||
rateLimitLogHandler = SpringUtil.getBean(IRateLimitLogHandler.class);
|
||||
}
|
||||
return rateLimitLogHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
|
||||
List<RateLimitRule> rules = getRuleManager().listEnabledRules();
|
||||
if (rules.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String uri = pathPrefix.concat(request.getRequestURI());
|
||||
String method = request.getMethod();
|
||||
for (RateLimitRule rule : rules) {
|
||||
if (!methodMatches(rule.getRequestMethod(), method) || !pathMatches(rule.getLimitPattern(), uri)) {
|
||||
continue;
|
||||
}
|
||||
String limitKey = RateLimitDimension.of(rule.getLimitDimension()).obtainKey(rule.getId(), request);
|
||||
// 维度值缺失 (如匿名请求命中用户/租户维度规则), 跳过该规则继续匹配后续规则
|
||||
if (limitKey == null) {
|
||||
continue;
|
||||
}
|
||||
boolean allowed = rateLimiterClient.isAllowed(limitKey, rule.getLimitCount(), rule.getLimitPeriod(), TimeUnit.valueOf(rule.getTimeUnit()));
|
||||
if (allowed) {
|
||||
return true;
|
||||
}
|
||||
log.warn("请求触发限流, uri={}, ip={}, rule={}, dimension={}, rate={}/{} {}", uri, WebUtil.getIP(request), rule.getLimitName(),
|
||||
rule.getLimitDimension(), rule.getLimitCount(), rule.getLimitPeriod(), rule.getTimeUnit());
|
||||
getRateLimitLogHandler().saveLog(rule, limitKey, request);
|
||||
reject(response);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方法是否匹配规则。规则方法为空或 ALL 时匹配全部方法。
|
||||
*/
|
||||
private boolean methodMatches(String ruleMethod, String requestMethod) {
|
||||
return StringUtil.isBlank(ruleMethod) || RateLimitConstant.METHOD_ALL.equalsIgnoreCase(ruleMethod) || ruleMethod.equalsIgnoreCase(requestMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 URL 是否匹配规则的匹配模式。
|
||||
* <p>
|
||||
* 匹配模式支持逗号分隔的多个 Ant 风格 pattern (一条规则覆盖多个 URL),命中任意一个即视为匹配;
|
||||
* 这些 URL 共享该规则的同一份配额 (计数 key 仅以 ruleId 与维度区分,与具体命中的 URL 无关)。
|
||||
*/
|
||||
private boolean pathMatches(String limitPattern, String uri) {
|
||||
if (StringUtil.isBlank(limitPattern)) {
|
||||
return false;
|
||||
}
|
||||
for (String pattern : limitPattern.split(StringPool.COMMA)) {
|
||||
String trimmedPattern = pattern.trim();
|
||||
if (StringUtil.isNotBlank(trimmedPattern) && pathMatcher.match(trimmedPattern, uri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出 429 限流响应。
|
||||
*/
|
||||
private void reject(HttpServletResponse response) {
|
||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||
WebUtil.renderJson(response, R.fail(HttpStatus.TOO_MANY_REQUESTS.value(), rejectMessage));
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 限流规则模型。
|
||||
* <p>
|
||||
* 系统级传输/缓存载体,供默认实现(JdbcTemplate)与微服务实现(Feign)共用,与业务层带租户基类的实体相互独立。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class RateLimitRule implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 规则名称
|
||||
*/
|
||||
private String limitName;
|
||||
|
||||
/**
|
||||
* URL匹配模式, 支持 Ant 风格, 多个以逗号分隔 (命中任意一个即匹配, 共享同一份配额)
|
||||
*/
|
||||
private String limitPattern;
|
||||
|
||||
/**
|
||||
* 请求方法, GET/POST/PUT/DELETE/ALL
|
||||
*/
|
||||
private String requestMethod;
|
||||
|
||||
/**
|
||||
* 限流维度, GLOBAL/TENANT/IP/USER
|
||||
*/
|
||||
private String limitDimension;
|
||||
|
||||
/**
|
||||
* 时间窗口内允许的最大请求数
|
||||
*/
|
||||
private Integer limitCount;
|
||||
|
||||
/**
|
||||
* 时间窗口大小
|
||||
*/
|
||||
private Integer limitPeriod;
|
||||
|
||||
/**
|
||||
* 时间单位, SECONDS/MINUTES/HOURS
|
||||
*/
|
||||
private String timeUnit;
|
||||
|
||||
/**
|
||||
* 优先级, 升序匹配
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.redis.pubsub.RPubSubEvent;
|
||||
import org.springblade.core.redis.pubsub.RPubSubListener;
|
||||
import org.springblade.core.redis.pubsub.RPubSubPublisher;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.springblade.core.ratelimit.RateLimitConstant.RULE_REFRESH_CHANNEL;
|
||||
|
||||
/**
|
||||
* 限流规则的进程内快照管理器。
|
||||
* <p>
|
||||
* 读路径无锁读取 volatile 快照,将每请求一次的远程取数收敛为一次内存读取;规则变更在事务提交后于本节点重建,
|
||||
* 并经 Redis 广播驱动其余节点热更新,偶发漏收的广播由刷新间隔兜底收敛。取数方式由 {@link IRateLimitHandler}
|
||||
* 决定,使单体与微服务共用同一套快照与同步逻辑。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RateLimitRuleManager {
|
||||
|
||||
private final IRateLimitHandler ruleHandler;
|
||||
|
||||
private final RPubSubPublisher publisher;
|
||||
|
||||
private final long refreshIntervalMillis;
|
||||
|
||||
/**
|
||||
* 实例标识,用于识别并忽略本节点自身发出的广播
|
||||
*/
|
||||
private final String instanceId = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* 异步重建去重标记,避免快照过期瞬间的并发取数
|
||||
*/
|
||||
private final AtomicBoolean rebuilding = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* 当前规则快照,null 表示尚未加载,空集表示已加载且无启用规则
|
||||
*/
|
||||
private volatile List<RateLimitRule> snapshot;
|
||||
|
||||
/**
|
||||
* 快照最近一次加载的时间戳,用于刷新间隔的过期判定
|
||||
*/
|
||||
private volatile long loadedAt;
|
||||
|
||||
/**
|
||||
* 获取当前启用的限流规则,供拦截器逐请求调用。快照未就绪时返回空集 (本次放行) 并触发异步加载,
|
||||
* 不在请求线程上阻塞远程取数。
|
||||
*
|
||||
* @return 规则快照,不可变且非空
|
||||
*/
|
||||
public List<RateLimitRule> listEnabledRules() {
|
||||
List<RateLimitRule> current = snapshot;
|
||||
if (current == null) {
|
||||
asyncRebuild();
|
||||
return List.of();
|
||||
}
|
||||
if (System.currentTimeMillis() - loadedAt > refreshIntervalMillis) {
|
||||
asyncRebuild();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则增删改后由业务侧调用。处于事务中时延迟至提交后执行,避免其余节点读到未提交数据;
|
||||
* 提交后本节点立即重建并广播全集群。
|
||||
*/
|
||||
public void refresh() {
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
rebuildAndBroadcast();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
rebuildAndBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收集群广播并重建本节点快照,忽略自身消息。
|
||||
*/
|
||||
@RPubSubListener(RULE_REFRESH_CHANNEL)
|
||||
public void onRemoteRefresh(RPubSubEvent<String> event) {
|
||||
if (!instanceId.equals(event.getMsg())) {
|
||||
rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildAndBroadcast() {
|
||||
try {
|
||||
rebuild();
|
||||
publisher.publish(RULE_REFRESH_CHANNEL, instanceId);
|
||||
} catch (Exception broadcastException) {
|
||||
log.warn("限流规则广播失败,将由刷新间隔兜底收敛: {}", broadcastException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuild() {
|
||||
List<RateLimitRule> rules = ruleHandler.listEnabledRules();
|
||||
snapshot = (rules == null) ? List.of() : List.copyOf(rules);
|
||||
loadedAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void asyncRebuild() {
|
||||
if (rebuilding.compareAndSet(false, true)) {
|
||||
CompletableFuture.runAsync(this::rebuild).whenComplete((ignored, error) -> {
|
||||
rebuilding.set(false);
|
||||
if (error != null) {
|
||||
log.warn("限流规则加载失败,下次请求重试: {}", error.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit.config;
|
||||
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springblade.core.launch.server.ServerInfo;
|
||||
import org.springblade.core.ratelimit.BladeRateLimitHandler;
|
||||
import org.springblade.core.ratelimit.BladeRateLimitLogHandler;
|
||||
import org.springblade.core.ratelimit.IRateLimitHandler;
|
||||
import org.springblade.core.ratelimit.IRateLimitLogHandler;
|
||||
import org.springblade.core.ratelimit.RateLimitRuleManager;
|
||||
import org.springblade.core.ratelimit.props.RateLimitProperties;
|
||||
import org.springblade.core.redis.pubsub.RPubSubPublisher;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* 限流默认配置。
|
||||
* <p>
|
||||
* 装配规则取数实现 {@link IRateLimitHandler} (默认本地 JdbcTemplate, 微服务侧由 Feign 实现覆盖) 与统一的规则管理器
|
||||
* {@link RateLimitRuleManager}, 以及触发日志处理器 {@link IRateLimitLogHandler}, 均以 {@link ConditionalOnMissingBean}
|
||||
* 兜底允许下游覆盖。管理器恒定可用 (供规则变更广播), 是否真正拦截由 {@code blade.ratelimit.enabled} 控制。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(RateLimitProperties.class)
|
||||
public class RateLimitAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(IRateLimitHandler.class)
|
||||
public IRateLimitHandler rateLimitHandler(JdbcTemplate jdbcTemplate) {
|
||||
return new BladeRateLimitHandler(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(IRateLimitLogHandler.class)
|
||||
public IRateLimitLogHandler rateLimitLogHandler(JdbcTemplate jdbcTemplate, BladeProperties bladeProperties, ServerInfo serverInfo) {
|
||||
return new BladeRateLimitLogHandler(jdbcTemplate, bladeProperties, serverInfo);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RateLimitRuleManager rateLimitRuleManager(IRateLimitHandler ruleHandler, RPubSubPublisher publisher, RateLimitProperties properties) {
|
||||
return new RateLimitRuleManager(ruleHandler, publisher, properties.getRefreshInterval().toMillis());
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.ratelimit.RateLimitInterceptor;
|
||||
import org.springblade.core.ratelimit.props.RateLimitProperties;
|
||||
import org.springblade.core.redis.ratelimiter.RateLimiterClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流拦截器注册配置。
|
||||
* <p>
|
||||
* 将 {@link RateLimitInterceptor} 注册到 MVC 拦截器链,对全部请求生效。放行路径由内置默认列表与
|
||||
* {@link RateLimitProperties#getExcludePatterns()} 合并而成。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@AllArgsConstructor
|
||||
@EnableConfigurationProperties(RateLimitProperties.class)
|
||||
@ConditionalOnProperty(value = "blade.ratelimit.enabled", havingValue = "true")
|
||||
public class RateLimitWebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 内置默认放行路径 (文档、监控、静态资源、内部 Feign 等)
|
||||
*/
|
||||
private static final List<String> DEFAULT_EXCLUDE_PATTERNS = List.of(
|
||||
"/doc.html",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-resources/**",
|
||||
"/v3/api-docs/**",
|
||||
"/webjars/**",
|
||||
"/static/**",
|
||||
"/druid/**",
|
||||
"/error",
|
||||
"/actuator/**",
|
||||
"/feign/**"
|
||||
);
|
||||
|
||||
private final RateLimiterClient rateLimiterClient;
|
||||
|
||||
private final RateLimitProperties rateLimitProperties;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(@NonNull InterceptorRegistry registry) {
|
||||
List<String> excludePatterns = new ArrayList<>(DEFAULT_EXCLUDE_PATTERNS);
|
||||
excludePatterns.addAll(rateLimitProperties.getExcludePatterns());
|
||||
RateLimitInterceptor interceptor = new RateLimitInterceptor(rateLimiterClient, rateLimitProperties.getRejectMessage(), rateLimitProperties.getPathPrefix());
|
||||
registry.addInterceptor(interceptor)
|
||||
.addPathPatterns(rateLimitProperties.getIncludePatterns())
|
||||
.excludePathPatterns(excludePatterns);
|
||||
}
|
||||
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit.enums;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springblade.core.ratelimit.RateLimitConstant;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
|
||||
/**
|
||||
* 限流维度, 决定限流计数的隔离粒度与 Redis 计数 key 的构造方式。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public enum RateLimitDimension {
|
||||
|
||||
/**
|
||||
* 全局: 同一规则所有请求共享一份配额
|
||||
*/
|
||||
GLOBAL,
|
||||
|
||||
/**
|
||||
* 租户: 同一规则按租户分别计数, 适用于 SaaS 多租户配额
|
||||
*/
|
||||
TENANT,
|
||||
|
||||
/**
|
||||
* IP: 按来源 IP 分别计数, 适用于匿名防刷
|
||||
*/
|
||||
IP,
|
||||
|
||||
/**
|
||||
* 用户: 按登录用户分别计数, 适用于登录用户配额
|
||||
*/
|
||||
USER;
|
||||
|
||||
/**
|
||||
* 解析限流维度, 非法值回退为 GLOBAL, 避免脏数据导致规则失效
|
||||
*
|
||||
* @param dimension 维度编码
|
||||
* @return 限流维度
|
||||
*/
|
||||
public static RateLimitDimension of(String dimension) {
|
||||
for (RateLimitDimension value : values()) {
|
||||
if (value.name().equalsIgnoreCase(dimension)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return GLOBAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造限流计数 key。当维度值无法获取时 (如匿名请求命中 USER/TENANT 维度规则) 返回 null,
|
||||
* 调用方据此跳过该规则、继续匹配后续规则。
|
||||
*
|
||||
* @param ruleId 规则主键, 作为 key 后缀保证短而唯一
|
||||
* @param request 当前请求
|
||||
* @return 计数 key, 维度值缺失时为 null
|
||||
*/
|
||||
public String obtainKey(Long ruleId, HttpServletRequest request) {
|
||||
String base = RateLimitConstant.KEY_PREFIX + ruleId;
|
||||
return switch (this) {
|
||||
case GLOBAL -> base;
|
||||
case IP -> base + ":ip:" + WebUtil.getIP(request);
|
||||
case TENANT -> {
|
||||
String tenantId = AuthUtil.getTenantId();
|
||||
yield StringUtil.isBlank(tenantId) ? null : base + ":t:" + tenantId;
|
||||
}
|
||||
case USER -> {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
yield (userId == null || userId <= 0L) ? null : base + ":u:" + userId;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit.env;
|
||||
|
||||
import org.springblade.core.auto.annotation.AutoEnvPostProcessor;
|
||||
import org.springblade.core.launch.utils.PropsUtil;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* 限流配置初始化。
|
||||
* <p>
|
||||
* 引入本 starter 即开启底层 Redis 限流原语;微服务侧 (存在 blade-core-cloud) 再将 {@code path-prefix}
|
||||
* 落值为服务名,补回网关剥离的前缀,使规则匹配在单体与微服务下对齐。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AutoEnvPostProcessor
|
||||
public class RateLimitEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
private static final String REDIS_RATE_LIMITER_ENABLED_KEY = "blade.redis.rate-limiter.enabled";
|
||||
|
||||
private static final String PATH_PREFIX_KEY = "blade.ratelimit.path-prefix";
|
||||
|
||||
private static final String APPLICATION_NAME_KEY = "spring.application.name";
|
||||
|
||||
/**
|
||||
* 是否微服务部署: 存在 blade-core-cloud 即经网关按服务名路由
|
||||
*/
|
||||
private static final boolean MICROSERVICE = ClassUtils.isPresent(
|
||||
"org.springblade.core.cloud.client.BladeCloudApplication", RateLimitEnvPostProcessor.class.getClassLoader());
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
if (StringUtil.isBlank(environment.getProperty(REDIS_RATE_LIMITER_ENABLED_KEY))) {
|
||||
PropsUtil.setProperty(System.getProperties(), REDIS_RATE_LIMITER_ENABLED_KEY, StringPool.TRUE);
|
||||
}
|
||||
String serviceName = environment.getProperty(APPLICATION_NAME_KEY, StringPool.EMPTY);
|
||||
if (MICROSERVICE && StringUtil.isNotBlank(serviceName) && StringUtil.isBlank(environment.getProperty(PATH_PREFIX_KEY))) {
|
||||
PropsUtil.setProperty(System.getProperties(), PATH_PREFIX_KEY, StringPool.SLASH.concat(serviceName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.ratelimit.props;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流配置。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("blade.ratelimit")
|
||||
public class RateLimitProperties {
|
||||
|
||||
/**
|
||||
* 是否开启限流管理
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 拦截路径, 默认拦截全部请求
|
||||
*/
|
||||
private List<String> includePatterns = new ArrayList<>(Collections.singletonList("/**"));
|
||||
|
||||
/**
|
||||
* 额外的放行路径, 与内置默认排除列表合并后一并放行
|
||||
*/
|
||||
private List<String> excludePatterns = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 规则匹配前缀, 留空则自动推断 (微服务补回网关剥离的 /服务名, 单体为空); 仅当网关前缀异于服务名时才需显式覆盖
|
||||
*/
|
||||
private String pathPrefix = "";
|
||||
|
||||
/**
|
||||
* 触发限流时的提示文案
|
||||
*/
|
||||
private String rejectMessage = "请求过于频繁,请稍后再试";
|
||||
|
||||
/**
|
||||
* 规则快照刷新间隔, 作为广播失效的兜底, 漏收广播的节点至多在此时长后收敛
|
||||
*/
|
||||
private Duration refreshInterval = Duration.ofHours(1);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user