一、前言
上篇文章已经讲了怎么在docker中部署sentinel,创建一个测试demo怎样去实现限流,这篇文章的话是怎么去使用后端接口动态上传限流规则到nacos,sentinel去动态加载限流规则去进行限流,和使用openfegin进行一个跨服务的一个限流
二、实现流程
2.1、创建json-service
2.2、创建一个nacos的config
package com.ruoyi.vehicle.config;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class NacosConfigServiceConfig {
private final NacosConfigProperties properties;
public NacosConfigServiceConfig(NacosConfigProperties properties) {
this.properties = properties;
}
@Bean
public ConfigService nacosConfigService() throws NacosException {
Properties props = new Properties();
props.put("serverAddr", this.properties.getServerAddr());
// 只有当用户名和密码非空时才设置
if (this.properties.getUsername() != null && !this.properties.getUsername().isEmpty()) {
props.put("username", this.properties.getUsername());
}
if (this.properties.getPassword() != null && !this.properties.getPassword().isEmpty()) {
props.put("password", this.properties.getPassword());
}
return NacosFactory.createConfigService(props);
}
}
2.3、redis config
package com.ruoyi.vehicle.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用StringRedisSerializer序列化key
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 使用Jackson2JsonRedisSerializer序列化value
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.afterPropertiesSet();
return template;
}
}
2.4、创建controller实现创建api分组和获取分组
package com.ruoyi.vehicle.controller;
import com.alibaba.nacos.api.exception.NacosException;
import com.ruoyi.vehicle.DTO.ApiGroupRequest;
import com.ruoyi.vehicle.DTO.ApiResponse;
import com.ruoyi.vehicle.service.ApiGroupService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api-groups")
public class ApiGroupController {
private final ApiGroupService apiGroupService;
@Autowired
public ApiGroupController(ApiGroupService apiGroupService) {
this.apiGroupService = apiGroupService;
}
/**
* 创建API分组
*/
@PostMapping("/create")
public ApiResponse<String> createApiGroup(@Validated @RequestBody ApiGroupRequest request) {
try {
String dataId = apiGroupService.createApiGroup(request);
return ApiResponse.success(dataId);
} catch (NacosException e) {
log.error("Nacos配置失败", e);
return ApiResponse.error(500, "Nacos配置失败: " + e.getMessage());
} catch (Exception e) {
log.error("创建API分组失败", e);
return ApiResponse.error(500, "创建API分组失败: " + e.getMessage());
}
}
/**
* 获取API分组详情
*/
@GetMapping("/{groupCode}")
public ApiResponse<String> getApiGroup(@PathVariable String groupCode) {
try {
String dataId = "api-groups-" + groupCode;
String content = apiGroupService.getConfigContent(dataId);
return ApiResponse.success(content);
} catch (Exception e) {
log.error("获取API分组失败", e);
return ApiResponse.error(500, "获取API分组失败: " + e.getMessage());
}
}
}
2.5、创建json的controller
package com.ruoyi.vehicle.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/json")
public class JsonController {
@PostMapping("/parse")
public ResponseEntity<Map<String, Object>> parseJson(@RequestBody String jsonString) {
// 实际项目中会调用JsonService,这里简化演示
Map<String, Object> result = Map.of("parsed", jsonString, "status", "success");
return ResponseEntity.ok(result);
}
@PostMapping("/batch-parse")
public ResponseEntity<List<Map<String, Object>>> batchParseJson(@RequestBody List<String> jsonList) {
// 实际项目中会调用JsonService,这里简化演示
List<Map<String, Object>> results = jsonList.stream()
.map(json -> {
Map<String, Object> result = new HashMap<>();
result.put("parsed", json);
result.put("status", "success");
return result;
})
.toList();
return ResponseEntity.ok(results);
}
}
2.6、创建普通添加策略、批量添加策略、获取策略
package com.ruoyi.vehicle.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.exception.NacosException;
import com.ruoyi.vehicle.DTO.ApiResponse;
import com.ruoyi.vehicle.DTO.StrategyDetail;
import com.ruoyi.vehicle.DTO.StrategyRequest;
import com.ruoyi.vehicle.Sentinel.GatewayFlowRule;
import com.ruoyi.vehicle.service.StrategyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/strategies")
public class StrategyController {
private final StrategyService strategyService;
@Autowired
public StrategyController(StrategyService strategyService) {
this.strategyService = strategyService;
}
/**
* 创建监控策略
*/
@PostMapping("/create")
public ApiResponse<String> createStrategy(@Validated @RequestBody StrategyRequest request) {
try {
String dataId = strategyService.createStrategy(request);
return ApiResponse.success(dataId);
} catch (NacosException e) {
log.error("Nacos配置失败", e);
return ApiResponse.error(500, "Nacos配置失败: " + e.getMessage());
} catch (Exception e) {
log.error("创建策略失败", e);
return ApiResponse.error(500, "创建策略失败: " + e.getMessage());
}
}
/**
* 批量创建策略
*/
@PostMapping("/create/batch")
public ApiResponse<String> createMultipleStrategies(@Validated @RequestBody List<StrategyRequest> requests) {
try {
strategyService.createMultipleStrategies(requests);
return ApiResponse.success("批量策略创建成功");
} catch (NacosException e) {
log.error("Nacos配置失败", e);
return ApiResponse.error( 500, "Nacos配置失败:" + e.getMessage());
} catch (Exception e) {
log.error("批量创建策略失败", e);
return ApiResponse.error( 500, "批量创建策略失败:" + e.getMessage());
}
}
/**
* 获取策略详情
*/
@GetMapping("/{configCode}")
public ApiResponse<String> getStrategy(@PathVariable String configCode) {
try {
String dataId = "gateway-flow-rules-" + configCode;
String content = strategyService.getConfigContent(dataId);
// 解析 JSON 为对象
List<GatewayFlowRule> rules = JSON.parseArray(content, GatewayFlowRule.class);
StrategyDetail detail = new StrategyDetail();
detail.setConfigCode(configCode);
detail.setRules(rules);
detail.setUpdateTime(new Date());
return ApiResponse.success(content);
} catch (Exception e) {
log.error("获取策略失败", e);
return ApiResponse.error(500, "获取策略失败: " + e.getMessage());
}
}
}
2.7、创建限流规则
package com.ruoyi.vehicle.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.exception.NacosException;
import com.ruoyi.vehicle.DTO.ApiResponse;
import com.ruoyi.vehicle.DTO.StrategyRequest;
import com.ruoyi.vehicle.DTO.Thresholds;
import com.ruoyi.vehicle.service.StrategyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
private final StrategyService strategyService;
@Autowired
public TestController(StrategyService strategyService) {
this.strategyService = strategyService;
}
/**
* 创建针对具体API路径的流控规则示例
*/
@PostMapping("/createApiRule")
public ApiResponse<String> createApiRule() {
try {
StrategyRequest request = new StrategyRequest();
request.setStrategyName("API路径流控策略");
request.setMonitoringType("FLOW_CONTROL");
request.setControlMeasure("限流");
request.setConfigCode("api-test-rule");
request.setRoutePath("/test/flow-control"); // 具体API路径
request.setResourceMode("API"); // 指定为API模式
Thresholds thresholds = new Thresholds();
thresholds.setQps(2); // 每秒最多2个请求
request.setThresholds(thresholds);
String dataId = strategyService.createStrategy(request);
return ApiResponse.success("API流控规则创建成功,dataId: " + dataId);
} catch (Exception e) {
log.error("创建API流控规则失败", e);
return ApiResponse.error(500, "创建API流控规则失败: " + e.getMessage());
}
}
/**
* 创建针对API分组的流控规则示例
*/
@PostMapping("/createGroupRule")
public ApiResponse<String> createGroupRule() {
try {
StrategyRequest request = new StrategyRequest();
request.setStrategyName("API分组流控策略");
request.setMonitoringType("FLOW_CONTROL");
request.setControlMeasure("限流");
request.setConfigCode("group-order-rule");
request.setApiGroup("order_group"); // 分组标识
request.setResourceMode("GROUP"); // 指定为分组模式
Thresholds thresholds = new Thresholds();
thresholds.setQps(3); // 每秒最多3个请求
request.setThresholds(thresholds);
String dataId = strategyService.createStrategy(request);
return ApiResponse.success("API分组流控规则创建成功,dataId: " + dataId);
} catch (Exception e) {
log.error("创建API分组流控规则失败", e);
return ApiResponse.error(500, "创建API分组流控规则失败: " + e.getMessage());
}
}
/**
* 创建针对路由的流控规则示例
*/
@PostMapping("/createRouteRule")
public ApiResponse<String> createRouteRule() {
try {
StrategyRequest request = new StrategyRequest();
request.setStrategyName("路由级别流控策略");
request.setMonitoringType("FLOW_CONTROL");
request.setControlMeasure("限流");
request.setConfigCode("route-business-rule");
request.setBusinessType("business");
request.setRoutePath("/api/**"); // 路由路径
request.setResourceMode("ROUTE"); // 指定为路由模式
Thresholds thresholds = new Thresholds();
thresholds.setQps(5); // 每秒最多5个请求
request.setThresholds(thresholds);
String dataId = strategyService.createStrategy(request);
return ApiResponse.success("路由级别流控规则创建成功,dataId: " + dataId);
} catch (Exception e) {
log.error("创建路由级别流控规则失败", e);
return ApiResponse.error(500, "创建路由级别流控规则失败: " + e.getMessage());
}
}
}
2.8、创建api分组的实体类
package com.ruoyi.vehicle.DTO;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ApiGroupRequest {
@NotBlank(message = "分组名称不能为空")
private String groupName;
@NotBlank(message = "描述不能为空")
private String description;
@NotNull(message = "排序不能为空")
private Integer sort;
private String businessType;
private String parentCode; // 父级分组编码
private Boolean enabled = true; // 是否启用
}
2.9、创建返回
package com.ruoyi.vehicle.DTO;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private LocalDateTime timestamp = LocalDateTime.now();
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("成功");
response.setData(data);
return response;
}
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}
}
2.10、创建限流规则
package com.ruoyi.vehicle.DTO;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
public class FlowRuleDto {
private String resource;
private String limitApp;
private Integer grade;
private Double count;
private Integer strategy;
private Integer controlBehavior;
private Boolean isClusterMode;
private Integer intervalSec;
private Integer warmUpPeriodSec;
private Integer burstCount; // 可能不存在
private Integer maxQueueingTimeMs; // 可能不存在
}
2.11、创建StrategyDetail
package com.ruoyi.vehicle.DTO;
import com.ruoyi.vehicle.Sentinel.GatewayFlowRule;
import lombok.Data;
import java.util.List;
import java.util.Date;
@Data
public class StrategyDetail {
private String configCode;
private List<GatewayFlowRule> rules;
private Date updateTime;
}
2.12、创建策略的实体类
package com.ruoyi.vehicle.DTO;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class StrategyRequest {
@NotBlank(message = "策略名称不能为空")
private String strategyName;
private List<FlowRule> flowRules;
@NotBlank(message = "监控类型不能为空")
private String monitoringType;
@NotNull(message = "阈值配置不能为空")
private Thresholds thresholds;
private String alertLevel = "INFO";
private List<String> notifyUsers;
@NotBlank(message = "控制措施不能为空")
private String controlMeasure;
@NotBlank(message = "配置编码不能为空")
private String configCode;
// 额外信息
private String businessType;
private String routePath = "/api/**";
private String apiGroup; // API分组标识
private String description;
// 添加按API路径进行流控的字段
private String resourceMode = "ROUTE"; // 资源模式: ROUTE-路由, API-具体API路径, GROUP-分组
}
2.13、创建指定阈值实体类
package com.ruoyi.vehicle.DTO;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
@Data
public class Thresholds {
@Min(value = 1, message = "响应时长阈值必须大于0")
private Integer responseTime = 10; // 秒
@Min(value = 0, message = "错误率阈值不能小于0")
@Max(value = 100, message = "错误率阈值不能大于100")
private Integer errorRate = 20; // %
@Min(value = 1, message = "QPS阈值必须大于0")
private Integer qps = 100; // 次/秒
@Min(value = 0, message = "队列阈值不能小于0")
private Integer queueSize = 20;
@Min(value = 0, message = "并发阈值不能小于0")
private Integer concurrency = 20; // 次/秒
}
2.14、关于seninel
package com.ruoyi.vehicle.Sentinel;
import lombok.Data;
@Data
public class GatewayFlowRule {
private String resource; // 资源名称
private int count; // 阈值
private int intervalSec = 1; // 统计时间窗口
private int controlBehavior; // 流控效果
private int maxQueueingTimeoutMs = 0; // 最大排队等待时长
private int burstCount = 0; // 令牌桶容量
private boolean clusterMode = false; // 是否集群模式
private GatewayParamFlowItem paramItem; // 参数流控项
// 静态方法:创建限流规则
public static GatewayFlowRule createLimitRule(String resource, int qps) {
GatewayFlowRule rule = new GatewayFlowRule();
rule.setResource(resource);
rule.setCount(qps);
rule.setControlBehavior(0); // 0-快速失败, 1-匀速排队, 2-预热
return rule;
}
}
@Data
class GatewayParamFlowItem {
private int parseStrategy; // 参数解析策略
private String fieldName; // 参数名称
private int threshold; // 阈值
private int pattern; // 匹配模式
private String matchStrategy; // 匹配策略
}
package com.ruoyi.vehicle.Sentinel;
import lombok.Data;
@Data
public class GatewayFlowRule {
private String resource; // 资源名称
private int count; // 阈值
private int intervalSec = 1; // 统计时间窗口
private int controlBehavior; // 流控效果
private int maxQueueingTimeoutMs = 0; // 最大排队等待时长
private int burstCount = 0; // 令牌桶容量
private boolean clusterMode = false; // 是否集群模式
private GatewayParamFlowItem paramItem; // 参数流控项
// 静态方法:创建限流规则
public static GatewayFlowRule createLimitRule(String resource, int qps) {
GatewayFlowRule rule = new GatewayFlowRule();
rule.setResource(resource);
rule.setCount(qps);
rule.setControlBehavior(0); // 0-快速失败, 1-匀速排队, 2-预热
return rule;
}
}
@Data
class GatewayParamFlowItem {
private int parseStrategy; // 参数解析策略
private String fieldName; // 参数名称
private int threshold; // 阈值
private int pattern; // 匹配模式
private String matchStrategy; // 匹配策略
}
2.15、关于创建api分组
package com.ruoyi.vehicle.service;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.ruoyi.vehicle.DTO.ApiGroupRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class ApiGroupService {
private final ConfigService configService;
private final NacosConfigProperties properties;
@Autowired
public ApiGroupService(ConfigService configService, NacosConfigProperties properties) {
this.configService = configService;
this.properties = properties;
}
/**
* 创建API分组
*/
public String createApiGroup(ApiGroupRequest request) throws NacosException {
log.info("创建API分组: {}", request.getGroupName());
// 1. 生成数据ID
String dataId = generateDataId(request);
// 2. 构建分组配置
Map<String, Object> groupConfig = buildGroupConfig(request);
// 3. 写入Nacos
publishToNacos(dataId, groupConfig);
log.info("API分组创建成功,Nacos配置ID: {}", dataId);
return dataId;
}
/**
* 生成Nacos配置的Data ID
*/
private String generateDataId(ApiGroupRequest request) {
return "api-groups-" + request.getGroupName();
}
/**
* 构建API分组配置
*/
private Map<String, Object> buildGroupConfig(ApiGroupRequest request) {
Map<String, Object> config = new HashMap<>();
config.put("groupName", request.getGroupName());
config.put("description", request.getDescription());
config.put("enabled", request.getEnabled());
config.put("createTime", System.currentTimeMillis());
return config;
}
/**
* 发布配置到Nacos
*/
private void publishToNacos(String dataId, Map<String, Object> config) throws NacosException {
String content = JSON.toJSONString(config);
boolean success = configService.publishConfig(
dataId,
properties.getGroup(),
content
);
if (!success) {
throw new RuntimeException("发布Nacos配置失败: " + dataId);
}
log.debug("Nacos配置发布成功 - DataId: {}, Content: {}", dataId, content);
}
/**
* 获取配置内容
*/
public String getConfigContent(String dataId) throws NacosException {
return configService.getConfig(
dataId,
properties.getGroup(),
properties.getTimeout()
);
}
}
2.16、关于json处理
package com.ruoyi.vehicle.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Service
@Slf4j
public class JsonService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* JSON解析接口 - 需要流控
*/
@SentinelResource(
value = "parseJson",
blockHandler = "parseJsonBlockHandler",
fallback = "parseJsonFallback"
)
public Map<String, Object> parseJson(String jsonString) {
log.info("Parsing JSON: {}", jsonString);
// 模拟JSON解析
try {
Thread.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(jsonString, Map.class);
} catch (Exception e) {
throw new RuntimeException("Invalid JSON format", e);
}
}
/**
* 批量JSON处理接口 - 需要流控
*/
@SentinelResource(
value = "batchParseJson",
blockHandler = "batchParseJsonBlockHandler",
fallback = "batchParseJsonFallback"
)
public List<Map<String, Object>> batchParseJson(List<String> jsonStrings) {
log.info("Batch parsing JSON: {} items", jsonStrings.size());
// 模拟批量处理
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
List<Map<String, Object>> results = new ArrayList<>();
for (String jsonString : jsonStrings) {
try {
ObjectMapper mapper = new ObjectMapper();
results.add(mapper.readValue(jsonString, Map.class));
} catch (Exception e) {
results.add(Collections.singletonMap("error", "Invalid JSON"));
}
}
return results;
}
/**
* 流控处理方法
*/
public Map<String, Object> parseJsonBlockHandler(String jsonString, BlockException ex) {
log.warn("Parse JSON blocked: {}, exception: {}", jsonString, ex.getClass().getSimpleName());
return Collections.singletonMap("error", "flow_control");
}
public List<Map<String, Object>> batchParseJsonBlockHandler(List<String> jsonStrings, BlockException ex) {
log.warn("Batch parse JSON blocked: {}, exception: {}", jsonStrings.size(), ex.getClass().getSimpleName());
return Collections.singletonList(Collections.singletonMap("error", "flow_control"));
}
/**
* 降级处理方法
*/
public Map<String, Object> parseJsonFallback(String jsonString, Throwable throwable) {
log.warn("Parse JSON fallback: {}", throwable.getMessage());
return Collections.singletonMap("error", "fallback");
}
public List<Map<String, Object>> batchParseJsonFallback(List<String> jsonStrings, Throwable throwable) {
log.warn("Batch parse JSON fallback: {}", throwable.getMessage());
return Collections.singletonList(Collections.singletonMap("error", "fallback"));
}
}
2.17、发布到nacos中格式转换
package com.ruoyi.vehicle.service;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.exception.NacosException;
import com.ruoyi.vehicle.DTO.FlowRuleDto;
import com.ruoyi.vehicle.DTO.StrategyRequest;
import com.ruoyi.vehicle.DTO.Thresholds;
import com.ruoyi.vehicle.Sentinel.GatewayFlowRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.stream.Stream;
@Slf4j
@Service
public class StrategyService {
private final ConfigService configService;
private final NacosConfigProperties properties;
@Autowired
public StrategyService(ConfigService configService, NacosConfigProperties properties) {
this.configService = configService;
this.properties = properties;
}
// 在类级别添加版本检测
private static final boolean IS_SENTINEL_2_X =
Stream.of(FlowRule.class.getDeclaredMethods())
.anyMatch(m -> m.getName().equals("getBurstCount"));
/**
* 创建监控策略
*/
public String createStrategy(StrategyRequest request) throws NacosException {
log.info("创建监控策略: {}", request.getStrategyName());
// 1. 生成数据ID
String dataId = generateDataId(request);
// 2. 构建规则
String ruleJson = buildRuleJson(request);
// 3. 写入Nacos
publishToNacos(dataId, ruleJson);
log.info("策略创建成功,Nacos配置ID: {}", dataId);
return dataId;
}
/**
* 将 FlowRule 对象转换为 Map 结构,便于 JSON 序列化
*/
private Map<String, Object> buildFlowRuleMap(FlowRule rule) {
Map<String, Object> ruleMap = new HashMap<>();
// 基本必填字段
ruleMap.put("resource", rule.getResource() != null ? rule.getResource() : "");
ruleMap.put("limitApp", rule.getLimitApp() != null ? rule.getLimitApp() : "default");
ruleMap.put("grade", rule.getGrade()); // 1: QPS, 0: 线程数
ruleMap.put("count", rule.getCount()); // 阈值
ruleMap.put("strategy", rule.getStrategy()); // 0: 直接, 1: 关联, 2: 链路
ruleMap.put("controlBehavior", rule.getControlBehavior()); // 0: 快速失败, 1: Warm Up, 2: 排队等待
ruleMap.put("clusterMode", rule.isClusterMode());
// 可选字段 - 使用 try-catch 避免 NoSuchMethodError
try {
// Sentinel 1.8+ 版本
ruleMap.put("maxQueueingTimeMs", rule.getMaxQueueingTimeMs());
} catch (NoSuchMethodError | Exception e) {
log.debug("无法获取 maxQueueingTimeMs 字段,使用默认值 500");
ruleMap.put("maxQueueingTimeMs", 500);
}
try {
// Sentinel 1.8+ 版本
ruleMap.put("warmUpPeriodSec", rule.getWarmUpPeriodSec());
} catch (NoSuchMethodError | Exception e) {
log.debug("无法获取 warmUpPeriodSec 字段,使用默认值 30");
ruleMap.put("warmUpPeriodSec", 30);
}
return ruleMap;
}
/**
* 批量创建策略 - 支持在同一个配置文件中存储多个规则
*/
public void createMultipleStrategies(List<StrategyRequest> requests) throws NacosException {
// if (requests == null || requests.isEmpty()) {
// log.warn("请求列表为空,无需处理");
// return;
// }
//
// try {
// StrategyRequest firstRequest = requests.get(0);
// String baseDataId = generateDataId(firstRequest);
//
// List<List<Map<String, Object>>> allRules = new ArrayList<>();
//
// for (StrategyRequest request : requests) {
// List<FlowRule> flowRules = request.getFlowRules();
//
// if (flowRules == null || flowRules.isEmpty()) {
// log.warn("策略 {} 没有配置规则,跳过", request.getStrategyName());
// continue;
// }
//
// List<Map<String, Object>> ruleList = new ArrayList<>();
// for (FlowRule rule : flowRules) {
// Map<String, Object> ruleMap = buildFlowRuleMap(rule);
// ruleList.add(ruleMap);
// }
//
// allRules.add(ruleList);
// }
//
// // 使用 PrettyFormat 格式化
// String combinedRulesJson = JSON.toJSONString(allRules, SerializerFeature.PrettyFormat);
//
// log.info("生成的 Nacos 配置内容:\n{}", combinedRulesJson);
//
// // 发布到 Nacos
// publishToNacos(baseDataId, combinedRulesJson);
//
// log.info("批量策略创建成功,Nacos 配置 ID: {}", baseDataId);
//
// } catch (Exception e) {
// log.error("批量创建策略失败", e);
// throw new RuntimeException("批量创建策略失败: " + e.getMessage(), e);
// }
if (requests == null || requests.isEmpty()) {
log.warn("请求列表为空,无需处理");
return;
}
try {
StrategyRequest firstRequest = requests.get(0);
String baseDataId = generateDataId(firstRequest);
// 创建扁平化的规则列表
List<Map<String, Object>> flatRules = new ArrayList<>();
for (StrategyRequest request : requests) {
List<FlowRule> flowRules = request.getFlowRules();
if (flowRules == null || flowRules.isEmpty()) {
log.warn("策略 {} 没有配置规则,跳过", request.getStrategyName());
continue;
}
// 将每个规则直接添加到扁平化列表中
for (FlowRule rule : flowRules) {
Map<String, Object> ruleMap = buildFlowRuleMap(rule);
flatRules.add(ruleMap);
}
}
// 使用 PrettyFormat 格式化
String combinedRulesJson = JSON.toJSONString(flatRules, SerializerFeature.PrettyFormat);
log.info("生成的 Nacos 配置内容:\n{}", combinedRulesJson);
// 发布到 Nacos
publishToNacos(baseDataId, combinedRulesJson);
log.info("批量策略创建成功,Nacos 配置 ID: {}", baseDataId);
} catch (Exception e) {
log.error("批量创建策略失败", e);
throw new RuntimeException("批量创建策略失败: " + e.getMessage(), e);
}
}
/**
* 生成Nacos配置的Data ID
*/
private String generateDataId(StrategyRequest request) {
if (!StringUtils.hasText(request.getConfigCode())) {
request.setConfigCode(UUID.randomUUID().toString().replace("-", "").substring(0, 12));
}
return "gateway-flow-rules-" + request.getConfigCode();
}
/**
* 构建Sentinel规则JSON
*/
// private String buildRuleJson(StrategyRequest request) {
// // 创建一个包含所有必要字段的DTO
// List<FlowRuleDto> ruleDtos = new ArrayList<>();
// for (FlowRule rule : rules) {
// FlowRuleDto dto = new FlowRuleDto();
// dto.setResource(rule.getResource());
// dto.setLimitApp(rule.getLimitApp());
// dto.setGrade(rule.getGrade());
// dto.setCount(rule.getCount());
// dto.setStrategy(rule.getStrategy());
// dto.setControlBehavior(rule.getControlBehavior());
// dto.setClusterMode(rule.isClusterMode());
// dto.setIntervalSec(rule.getIntervalSec());
// dto.setWarmUpPeriodSec(rule.getWarmUpPeriodSec());
//
// // 只有在存在时才设置
// try {
// dto.setBurstCount(rule.getBurstCount());
// } catch (NoSuchMethodError e) {
// // 忽略
// }
//
// ruleDtos.add(dto);
// }
//
// return JSON.toJSONString(ruleDtos);
// }
/**
* 根据请求构建单个规则的JSON
*/
private String buildRuleJson(StrategyRequest request) {
List<FlowRule> rules = request.getFlowRules();
if (rules == null || rules.isEmpty()) {
return "[]";
}
// 创建规则列表
List<Map<String, Object>> ruleList = new ArrayList<>();
for (FlowRule rule : rules) {
Map<String, Object> ruleMap = new HashMap<>();
ruleMap.put("resource", rule.getResource());
ruleMap.put("limitApp", rule.getLimitApp());
ruleMap.put("grade", rule.getGrade());
ruleMap.put("count", rule.getCount());
ruleMap.put("strategy", rule.getStrategy());
ruleMap.put("controlBehavior", rule.getControlBehavior());
ruleMap.put("clusterMode", rule.isClusterMode());
ruleMap.put("warmUpPeriodSec", rule.getWarmUpPeriodSec());
ruleMap.put("maxQueueingTimeMs", rule.getMaxQueueingTimeMs());
ruleList.add(ruleMap);
}
// 转换为JSON字符串
return JSON.toJSONString(ruleList);
}
/**
* 构建资源名称
*/
private String buildResourceName(StrategyRequest request) {
// 根据资源模式构建不同的资源名称
switch (request.getResourceMode()) {
case "API":
// 针对具体API路径的流控
return request.getRoutePath();
case "GROUP":
// 针对API分组的流控
return "group@" + request.getApiGroup();
case "ROUTE":
default:
// 默认基于路由的流控
String businessType = StringUtils.hasText(request.getBusinessType()) ?
request.getBusinessType() : "default";
return String.format("gateway_api::%s::%s", businessType, request.getRoutePath());
}
}
/**
* 获取控制行为类型
*/
private int getControlBehavior(String measure) {
if (measure == null) return 0;
switch (measure) {
case "熔断": return 0; // 快速失败
case "排队": return 1; // 匀速排队
case "禁止访问": return 2; // 直接拒绝
case "限流": return 0; // 限流
case "降级": return 0; // 降级
default: return 0;
}
}
/**
* 发布配置到Nacos
*/
private void publishToNacos(String dataId, String content) throws NacosException {
try {
boolean success = configService.publishConfig(
dataId,
properties.getGroup(),
content,
ConfigType.JSON.getType()
);
if (!success) {
log.error("发布Nacos配置失败 - DataId: {}, Content: {}", dataId, content);
throw new RuntimeException("发布Nacos配置失败:" + dataId);
}
log.debug("Nacos配置发布成功 - DataId: {}, Content: {}", dataId, content);
} catch (NacosException e) {
log.error("Nacos配置发布异常 - DataId: {}, Exception: {}", dataId, e.getMessage());
throw new RuntimeException("发布Nacos配置失败:" + dataId + ",错误详情:" + e.getMessage(), e);
}
}
/**
* 获取配置内容
*/
public String getConfigContent(String dataId) throws NacosException {
return configService.getConfig(
dataId,
properties.getGroup(),
properties.getTimeout()
);
}
}
如图所示:
启动类:
package com.ruoyi.vehicle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigManagementApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigManagementApplication.class, args);
System.out.println("┌───────────────────────────────────────────────────────────────┐");
System.out.println("│ 配置管理服务启动成功!监听端口: 9001 │");
System.out.println("│ 策略配置API: http://localhost:9001/config/strategies │");
System.out.println("└───────────────────────────────────────────────────────────────┘");
}
}
application.yml:
server:
port: 9001
servlet:
context-path: /config
spring:
application:
name: config-management-service
redis:
host: 你的ip
port: 6379
password:
config:
import:
- optional:nacos:config-management-service.yaml
cloud:
nacos:
server-addr: 你的ip:8848
config:
group: DEFAULT_GROUP
file-extension: yaml
auto-refresh: true
timeout: 3000
discovery:
enabled: true
group: DEFAULT_GROUP
weight: 1.0
metadata:
version: 1.0
sentinel:
transport:
dashboard: http://你的ip:8858
port: 8721
logging:
level:
com.ruoyi.vehicle: DEBUG
com.alibaba.nacos: DEBUG # 调整为 DEBUG 方便排查
management:
endpoints:
web:
exposure:
include: health,info # 必须包含health
endpoint:
health:
show-details: always
pom文件:
<?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>
<groupId>com.ruoyi.vehicle</groupId>
<artifactId>test-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>json-service</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos Config Client -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version> <!-- 与 Spring Boot 2.7.15 兼容 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Sentinel Core (for rule definitions) -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
三、测试接口test-service
3.1、创建限流异常处理器
package com.ruoyi.vehicle.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Configuration
public class SentinelConfig {
/**
* 只定义自定义的BlockExceptionHandler
* 不要定义sentinelWebMvcConfig Bean
*/
@Bean
public BlockExceptionHandler blockExceptionHandler() {
return new CustomBlockExceptionHandler();
}
/**
* 自定义限流异常处理器
*/
public static class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
String msg = "{\"status\":\"blocked\",\"resource\":\"" + e.getRule().getResource()
+ "\",\"reason\":\"" + e.getClass().getSimpleName()
+ "\",\"message\":\"流量控制触发,请求被限流\",\"timestamp\":" + System.currentTimeMillis() + "}";
response.getWriter().print(msg);
}
}
}
3.2、测试接口controller
package com.ruoyi.vehicle.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class TestController {
// 测试接口1 - 模拟业务接口
@GetMapping("/business")
@SentinelResource(value = "api/business", blockHandler = "handleBusinessBlock")
public Map<String, Object> business() {
Map<String, Object> result = new HashMap<>();
result.put("status", "success");
result.put("message", "Business API called successfully");
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 测试接口2 - 模拟订单接口
@GetMapping("/order")
@SentinelResource(value = "api/order", blockHandler = "handleOrderBlock")
public Map<String, Object> order() {
Map<String, Object> result = new HashMap<>();
result.put("status", "success");
result.put("message", "Order API called successfully");
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 测试接口3 - 模拟支付接口
@GetMapping("/payment")
@SentinelResource(value = "api/payment", blockHandler = "handlePaymentBlock")
public Map<String, Object> payment() {
Map<String, Object> result = new HashMap<>();
result.put("status", "success");
result.put("message", "Payment API called successfully");
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 限流处理方法 - 业务接口
public Map<String, Object> handleBusinessBlock(BlockException ex) {
Map<String, Object> result = new HashMap<>();
result.put("status", "blocked");
result.put("message", "API business is blocked by Sentinel: " + ex.getClass().getSimpleName());
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 限流处理方法 - 订单接口
public Map<String, Object> handleOrderBlock(BlockException ex) {
Map<String, Object> result = new HashMap<>();
result.put("status", "blocked");
result.put("message", "API order is blocked by Sentinel: " + ex.getClass().getSimpleName());
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 限流处理方法 - 支付接口
public Map<String, Object> handlePaymentBlock(BlockException ex) {
Map<String, Object> result = new HashMap<>();
result.put("status", "blocked");
result.put("message", "API payment is blocked by Sentinel: " + ex.getClass().getSimpleName());
result.put("timestamp", System.currentTimeMillis());
return result;
}
// 健康检查接口
@GetMapping("/health")
public String health() {
return "Service is running";
}
// 获取当前规则信息
@GetMapping("/rules")
public String getRules() {
return "Current rules are loaded from Nacos: gateway-flow-rules-test004";
}
}
3.3、启动类:
package com.ruoyi.vehicle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TextApplication {
public static void main(String[] args) {
SpringApplication.run(TextApplication.class, args);
}
}
3.4、创建application.yml
server:
port: 8082
servlet:
context-path: "" # 关键!避免路径变长
spring:
main:
allow-circular-references: true
application:
name: test-service
cloud:
sentinel:
transport:
dashboard: 自己Ip:8858
port: 8722
eager: true # 急切加载规则,启动时立即初始化
datasource:
ds1:
nacos:
server-addr: 自己ip:8848
dataId: gateway-flow-rules-test004
groupId: DEFAULT_GROUP
rule-type: flow
# 添加自动刷新配置
auto-refresh: true
data-type: json
# 修复:启用过滤器
filter:
enabled: true # 必须启用才能对HTTP请求进行限流
url-patterns: /** # 拦截所有URL
order: -2147483648 # 设置高优先级
# 添加Web上下文配置
web-context-unify: false # 防止链路被聚合
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
3.5、pom文件:
<?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>
<groupId>com.ruoyi</groupId>
<artifactId>test-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>test-service</name>
<description>Test service for Sentinel flow rules</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version> <!-- 关键修复:必须与maven.compiler一致 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<sentinel.version>1.8.5</sentinel.version>
<!-- 关键:必须使用Spring Cloud Alibaba 2021.0.4.0配套的Sentinel版本 -->
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencies>
<!-- Alibaba Sentinel Core (必须与Spring Cloud Alibaba版本配套) -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!-- Alibaba Sentinel Web MVC Adapter (关键!) -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.8.5</version> <!-- 必须与您的 Sentinel 版本一致 -->
</dependency>
<!-- Spring Boot Starter AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Boot Starter Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Starter Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Alibaba Sentinel Datasource for Nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!-- Spring Cloud Alibaba Sentinel Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.4.0</version>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Alibaba Sentinel Datasource for Nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>${sentinel.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>aliyun</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<!-- 重要:统一依赖版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
四、调用openfegin调用进行限流
4.1、创建sentinel的处理类,使用CGLIB代理
package com.ruoyi.vehicle.config;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
/**
* Sentinel 配置类 - 关键修复
* 1. 使用@Order确保最先加载
* 2. 强制CGLIB代理
* 3. 显式创建SentinelResourceAspect bean
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) // 关键:强制CGLIB代理 + 暴露代理
@Order(0) // 确保最先加载
public class SentinelConfig {
/**
* 显式创建SentinelResourceAspect bean - 解决"Wrong state"问题的核心
*/
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
/**
* 添加初始化验证
*/
@Bean
public SentinelInitVerifier sentinelInitVerifier() {
return new SentinelInitVerifier();
}
/**
* 初始化验证器 - 检查Sentinel AOP是否正确初始化
*/
public static class SentinelInitVerifier {
public SentinelInitVerifier() {
System.out.println("✅ [SentinelInitVerifier] SentinelResourceAspect 已注册到Spring容器");
System.out.println("✅ [SentinelInitVerifier] CGLIB代理已启用: proxyTargetClass=true");
System.out.println("✅ [SentinelInitVerifier] AOP切面初始化完成");
}
}
}
4.2、创建测试返回
package com.ruoyi.vehicle.config;
import com.ruoyi.vehicle.service.TestServiceFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component // 必须是 Spring Bean
public class TestServiceFeignClientFallback implements TestServiceFeignClient {
@Override
public Map<String, Object> business() {
Map<String, Object> result = new HashMap<>();
result.put("status", "fallback");
result.put("message", "【降级】业务服务不可用");
result.put("timestamp", System.currentTimeMillis());
return result;
}
@Override
public Map<String, Object> submitData() {
Map<String, Object> result = new HashMap<>();
result.put("status", "fallback");
result.put("message", "【降级】数据提交服务不可用");
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
4.3、openfegin 测试controller
package com.ruoyi.vehicle.controller;
import com.ruoyi.vehicle.service.TestServiceFeignClient;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/client")
@RequiredArgsConstructor
public class FeignClientController {
private final TestServiceFeignClient testServiceFeignClient;
// 3. 通过 Feign 调用 test-service
@GetMapping("/business")
public String callBusinessService() {
System.out.println("🔵 收到请求: /api/client/business");
// 这里会触发 Sentinel 限流规则
Map<String,Object> result = testServiceFeignClient.business();
System.out.println("✅ Feign 调用成功: " + result);
return "Feign Result: " + result;
}
@PostMapping("/submit")
public String submitToBusiness(@RequestBody String data) {
System.out.println("🔵 收到请求: /api/client/submit | Data: " + data);
Map<String,Object> result = testServiceFeignClient.submitData();
return "Feign Submit Result: " + result;
}
}
4.4、创建fegin客户端
package com.ruoyi.vehicle.service;
import com.ruoyi.vehicle.config.TestServiceFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.Map;
// 1. 定义 Feign 客户端
@FeignClient(
name = "test-service", // 目标服务名(必须与 test-service 的 spring.application.name 一致)
path = "/api/business", // 基础路径
fallback = TestServiceFeignClientFallback.class // 降级实现类
)
public interface TestServiceFeignClient {
@GetMapping("/business")
Map<String, Object> business();
@PostMapping("/submit")
Map<String, Object> submitData();
}
4.5、启动类,开启openfegin
package com.ruoyi.vehicle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableFeignClients // 启用 Feign 客户端
@EnableDiscoveryClient
@ServletComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = true) // 双重保险,确保AOP生效
public class TestOpenFeignServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TestOpenFeignServiceApplication.class, args);
System.out.println("✅ test-openfeign-service 启动成功!");
System.out.println("✅ Feign 调用接口: http://localhost:8082/api/client/business");
System.out.println("✅ Sentinel 资源名: GET:http://test-service/api/business");
// 启动后验证Sentinel初始化
System.out.println("✅ 应用启动成功,等待Sentinel初始化...");
try {
Thread.sleep(2000); // 等待AOP初始化
System.out.println("✅ Sentinel应该已初始化完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.6、application.yml
server:
port: 9002 # test-openfeign-service 端口
spring:
application:
name: test-openfeign-service # 调用方服务名
cloud:
nacos:
discovery:
server-addr: 自己ip:8848
config:
server-addr: 自己ip:8848
file-extension: yaml
sentinel:
transport:
dashboard: 自己ip:8858 # Sentinel Dashboard 地址
port: 8719
eager: true # 急切加载规则
datasource:
# Feign 调用限流规则(关键配置!)
feign-flow:
nacos:
server-addr: 自己ip:8848
data-id: test-openfeign-service-feign-rules
group-id: DEFAULT_GROUP
rule-type: flow
auto-refresh: true
data-type: json
# 启用 Feign 支持(必须!)
feign:
enabled: true
web-context-unify: false # 重要!防止 URL 路径被聚合
# Feign 超时配置(必须设置!)
feign:
client:
config:
default:
connectTimeout: 3000 # 连接超时3秒
readTimeout: 5000 # 读取超时5秒
# 启用 Sentinel 熔断
sentinel:
enabled: true
management:
endpoints:
web:
exposure:
include: "*"
4.7、bootstrap.yml
server:
port: 9002
spring:
application:
name: test-openfeign-service
cloud:
nacos:
discovery:
server-addr: 自己Ip:8848
config:
server-addr: 自己ip:8848
file-extension: yaml
group: DEFAULT_GROUP
4.8、pom文件:
<?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>
<groupId>com.ruoyi.vehicle</groupId>
<artifactId>test-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>test-openfeign-service</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.5</version> <!-- 与您项目中其他sentinel组件版本保持一致 -->
</dependency>
<!-- Sentinel Annotation AspectJ -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.5</version>
</dependency>
<!-- Spring Boot AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 1. OpenFeign (无需版本号) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 2. Sentinel 核心 (无需版本号) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 3. Nacos 服务发现 (无需版本号) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 4. Nacos 配置中心 (无需版本号) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 5. Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/repository/public</url>
</repository>
<repository>
<id>central</id>
<url>http://repo1.maven.org/maven2</url>
</repository>
</repositories>
</project>