前言
在sentinel控制台设备的规则信息默认都是存储在内存当中,无论是重启了 sentinel 的客户端还是 sentinel 的控制台,所设置的规则都会丢失。如果要线上环境使用,那肯定是不行,解决方法也两种:
- 一是使用阿里云付费版本,即 AHAS Sentinel ,好处是不用踩坑(==坑很多==)
- 二是如题所示,自己改造dashboard客户端集成数据源
见官方文档 在生产环境中使用 Sentinel
找过很多关于集成的相关文章,基本都是仿照官网给的限流规则的例子来做的,如果仅仅按照官网案例实现,那是绝对不能用于线上环境去使用的。好了,废话不多说,开始我们的改造之旅吧。
可查看改造源码文件toBearShmily / sentinel-dashboard-nacos-1.8.0
环境版本相关说明
- 自定义数据源:
nacos-1.4.1
- sentinel-dashboard版本:
sentinel-1.8.0
- 通过推模式持久化规则数据到nacos
拉取 sentinel 源码文件
拉取源码,地址:alibaba/Sentinel ,导入idea,注意拉取的版本,我使用的是1.8.0。主要与你自己项目集成的版本,可见官方说明版本说明
修改点
-
pom依赖更改
<!-- for Nacos rule publisher sample --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <!--<scope>test</scope>--> </dependency>
注释掉
scope
-
修改application.properties
server.port=8858 # nacos setting group default SENTINREL_GROUP sentinel.datasource.nacos.server-addr=http://localhost:8848 sentinel.datasource.nacos.namespace=dev
我的
nacos
使用namespace
做环境区分,此处需要指定 rule??橄滦略鰊acos包,相关文件如下:
- NacosConfig 说明 (参考官网test下的相关文件)
@Configuration
public class NacosConfig {
@Value("${sentinel.datasource.nacos.server-addr:localhost:8848}")
private String serverAddr;
@Value("${sentinel.datasource.nacos.namespace:public}")
private String namespace;
@Bean
public ConfigService nacosConfigService() throws Exception {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
properties.put(PropertyKeyConst.NAMESPACE, namespace);
return ConfigFactory.createConfigService(properties);
}
}
此处获取配置中的nacos
配置,初始化nacos中config??椋–onfigService)操作的依赖bean,并注入当前容器
- NacosConfigUtil 说明(参考官网test下的相关文件)
增加如下规则配置项:
/**
* add cc for `degrade,authority,system`
*/
public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";
public static final String AUTHORITY_DATA_ID_POSTFIX = "-authority-rules";
public static final String SYSTEM_DATA_ID_POSTFIX = "-system-rules";
public static final String GETWAY_API_DATA_ID_POSTFIX = "-gateway-api-rules";
public static final String GETWAY_FLOW_DATA_ID_POSTFIX = "-gateway-flow-rules";
- CustomDynamicRule 说明,定义 Nacos数据源 推送,拉取操作
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.util.JSONUtils;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 定义 Nacos数据源 推送,拉取操作
* @Author sisyphus
* @Date 2021/8/25 15:11
* @Version V-1.0
*/
public interface CustomDynamicRule<T> {
/**
*@Author sisyphus
*@Description 远程获取规则-nacos数据源
*@Date 2021/8/25 17:24
*@Param [configService, appName, postfix, clazz-反序列化类]
*@return java.util.List<T>
**/
default List<T> fromNacosRuleEntity(ConfigService configService, String appName, String postfix, Class<T> clazz) throws NacosException {
AssertUtil.notEmpty(appName, "app name cannot be empty");
String rules = configService.getConfig(
genDataId(appName, postfix),
NacosConfigUtil.GROUP_ID,
3000
);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return JSONUtils.parseObject(clazz, rules);
}
/**
* @title setNacosRuleEntityStr
* @description 将规则序列化成JSON文本,存储到Nacos server中
* @author sisyphus
* @param: configService nacos config service
* @param: appName 应用名称
* @param: postfix 规则后缀 eg.NacosConfigUtil.FLOW_DATA_ID_POSTFIX
* @param: rules 规则对象
* @updateTime 2021/8/26 15:47
* @throws NacosException 异常
**/
default void setNacosRuleEntityStr(ConfigService configService, String appName, String postfix, List<T> rules) throws NacosException{
AssertUtil.notEmpty(appName, "app name cannot be empty");
if (rules == null) {
return;
}
String dataId = genDataId(appName, postfix);
//存储,推送远程nacos服务配置中心
boolean publishConfig = configService.publishConfig(
dataId,
NacosConfigUtil.GROUP_ID,
printPrettyJSON(rules)
);
if(!publishConfig){
throw new RuntimeException("publish to nacos fail");
}
}
/**
*@Author sisyphus
*@Description 组装nacos dateId
*@Date 2021/8/25 16:34
*@Param [appName, postfix]
*@return java.lang.String
**/
default String genDataId(String appName, String postfix) {
return appName + postfix;
}
/**
*@Author sisyphus
*@Description 规则对象转换为json字符串,并带有格式化
*@Date 2021/8/25 17:19
*@Param [obj]
*@return java.lang.String
**/
default String printPrettyJSON(Object obj) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
return JSON.toJSONString(obj);
}
}
}
- FlowRuleNacosProvider 和 FlowRuleNacosPublisher 说明(参考官网test下的相关文件)
FlowRuleNacosProvider 代码
package com.alibaba.csp.sentinel.dashboard.rule.nacos.flow;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.nacos.CustomDynamicRule;
import com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfigUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>>, CustomDynamicRule<FlowRuleEntity> {
@Autowired
private ConfigService configService;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
AssertUtil.notEmpty(appName, "app name cannot be empty");
return fromNacosRuleEntity(configService, appName, NacosConfigUtil.FLOW_DATA_ID_POSTFIX, FlowRuleEntity.class);
}
}
FlowRuleNacosPublisher 代码
package com.alibaba.csp.sentinel.dashboard.rule.nacos.flow;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.rule.nacos.CustomDynamicRule;
import com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfigUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>>, CustomDynamicRule<FlowRuleEntity> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
setNacosRuleEntityStr(configService, app, NacosConfigUtil.FLOW_DATA_ID_POSTFIX, rules);
}
}
此处实现与官网案例存在不同,两个类都实现了自定义接口CustomDynamicRule
,将数据源操作的动作抽象为默认接口,后续如果需要其他数据源的操作,可直接在接口中增加操作方法即可, 其余规则对应操作类与以上类似,仅仅泛型对应匹配的规则类型不同(对应AuthorityRuleEntity
, DegradeRuleEntity
, ParamFlowRuleEntity
, SystemRuleEntity
, ApiDefinitionEntity
, GatewayFlowRuleEntity
)
- controller 改造(重点)
我全部使用新建类,并未在原有类做更改,全部在包 v2
下,如图:
接下来还是以flow为例进行相关说明
-
首先增加了抽象类
InDataSourceRuleStore
,先看看代码,如下:public abstract class InDataSourceRuleStore<T extends RuleEntity> { private final Logger logger = LoggerFactory.getLogger(InDataSourceRuleStore.class); /** *@Author sisyphus *@Description 格式化从外部数据源获取到的数据(RuleEntity下的部分数据字段填充等) *@Date 2021/8/25 18:11 *@Param [entity - 远程获取规则且匹配当前需查询规则的实体对象, app - 当前规则标识] *@return void **/ protected abstract void format(T entity, String app); /** *@Author sisyphus *@Description 更新规则时部分数据的整合,字段维护 *@Date 2021/8/26 14:20 *@Param [entity, oldEntity] *@return void **/ protected abstract void merge(T entity, T oldEntity); /** *@Author sisyphus *@Description 根据当前id获取远程匹配的规则实体 * 此处只对普通流控做了转换,会经过format进行,其余规则直接返回远程规则对象,后面根据具体情况自行转换改造 *@Date 2021/8/26 10:33 *@Param [ruleProvider, app, id] *@return T **/ protected T findById(DynamicRuleProvider<List<T>> ruleProvider, String app, Long id) { try { // 远程获取规则(当前种类下(如网关流控,普通流控,系统等)的所有规则数据) List<T> rules = ruleProvider.getRules(app); // 匹配符合当前查询的规则,格式化远端规则数据为sentinel服务端可使用格式(Entity形式) if (rules != null && !rules.isEmpty()) { Optional<T> entity = rules.stream().filter(rule -> (id.equals(rule.getId()))).findFirst(); if (entity.isPresent()){ T t = entity.get(); this.format(t, app); return t; } } } catch (Exception e) { logger.error("服务[{}]规则[{}]匹配远端规则异常:{}", app, id, e.getMessage()); e.printStackTrace(); } return null; } /** *@Author sisyphus *@Description 获取对应??橄碌乃泄嬖?,存在format的进行规则转换 *@Date 2021/8/26 11:27 *@Param [ruleProvider, app] *@return java.util.List<T> **/ protected List<T> list(DynamicRuleProvider<List<T>> ruleProvider, String app) throws Exception { List<T> rules = ruleProvider.getRules(app); if (rules != null && !rules.isEmpty()) { for (T entity : rules) { this.format(entity, app); } // 此处排序是为了确保保存(save)时方便获取id以生成nextId rules.sort((p1,p2) -> (int) (p1.getId() - p2.getId())); } else { rules = new ArrayList<>(); } return rules; } /** *@Author sisyphus *@Description 添加规则至远程数据源 * 添加前先获取远程数据源,再加入本次新增,一起推送到远程数据源(否则存在覆盖的可能) * 修改nextId生成规则,原nextId生成由InMemoryRuleRepositoryAdapter类下nextId()方法实现,内部维护了一个AtomicLong实现自增,每次重启则重新从0开始 *@Date 2021/8/26 11:43 *@Param [rulePublisher, ruleProvider, entity] *@return void **/ protected void save(DynamicRulePublisher<List<T>> rulePublisher, DynamicRuleProvider<List<T>> ruleProvider, T entity) throws Exception { if (null == entity || StringUtils.isEmpty(entity.getApp())) { throw new InvalidParameterException("app is required"); } if (null != entity.getId()) { throw new InvalidParameterException("id must be null"); } // 获取远程规则数据 List<T> rules = this.list(ruleProvider, entity.getApp()); // 增规则添加至集合 long nextId = 1; if (rules.size() > 0) { // 获取集合的最后一个元素,得到id,进行增1操作(集合在)list方法内进行过排序,以保证此处获取到的最后一个元素为当前集合内id最大的元素 nextId = rules.get(rules.size() - 1).getId() + 1; } entity.setId(nextId); rules.add(entity); // 推送远程存储源 rulePublisher.publish(entity.getApp(), rules); } /** * @title update * @description 获取远程所有模式下的规则,匹配id,进行替换 * @author sisyphus * @param: rulePublisher * @param: ruleProvider * @param: entity * @updateTime 2021/8/30 9:35 * @return: com.alibaba.csp.sentinel.dashboard.domain.Result<T> * @throws **/ protected Result<T> update(DynamicRulePublisher<List<T>> rulePublisher, DynamicRuleProvider<List<T>> ruleProvider, T entity) throws Exception { if (null == entity || null == entity.getId() || StringUtils.isEmpty(entity.getApp())) { return Result.ofFail(-1, "id is required"); } // 获取远程规则数据 List<T> rules = this.list(ruleProvider, entity.getApp()); if (null == rules || rules.isEmpty()) { return Result.ofFail(-1, "Failed to save authority rule, no matching authority rule"); } // 远程规则集合与当前规则匹配项,当前规则填充旧的集合中对应规则数据 for (int i = 0; i < rules.size(); i++) { T oldEntity = rules.get(i); if (oldEntity.getId().equals(entity.getId())) { // 新旧值替换填充,字段检查 this.merge(entity, oldEntity); // 写回规则集合 rules.set(i, entity); break; } } // 推送远程存储源 rulePublisher.publish(entity.getApp(), rules); return Result.ofSuccess(entity); } /** * @title delete * @description 获取远程所有模式下的规则,从集合中删除对应规则项 * @author sisyphus * @param: rulePublisher * @param: ruleProvider * @param: id * @param: app * @updateTime 2021/8/30 9:36 * @return: com.alibaba.csp.sentinel.dashboard.domain.Result<java.lang.Long> * @throws **/ protected Result<Long> delete(DynamicRulePublisher<List<T>> rulePublisher, DynamicRuleProvider<List<T>> ruleProvider, long id, String app) throws Exception { List<T> rules = this.list(ruleProvider, app); if (null == rules || rules.isEmpty()) { return Result.ofSuccess(null); } // 匹配删除项,移除集合 boolean removeIf = rules.removeIf(flowRuleEntity -> flowRuleEntity.getId().equals(id)); if (!removeIf){ return Result.ofSuccess(null); } // 推送远程存储源 rulePublisher.publish(app, rules); return Result.ofSuccess(id); } }
说明:该类的作用,摒弃掉所有内存存储操作
问题:
Sentinel Dashboard官方版本中支持创建DynamicRuleProvider和DynamicRulePublisher来和外部数据源通信,但是仅仅增加这两个类的实现并不够,使用下来发现的问题
- 新建RuleEntity的时候,ID是从代码中的AtomicLong变量获取的,每次这个变量都是从0开始计数,也就意味着每次重启之后ID都重新计数,这在使用内存存储rule的时候没有问题,但是一旦有外部数据源,这地方逻辑就不对了。
- 新建RuleEntity之后,会将当前所有的RuleEntity发布到外部数据源,如果是从资源列表页(请求链路或簇点链路)直接创建规则,那么这时候还没从外部数据源加载已存在的rule(只有访问对应的规则页面的list接口才会从远程加载),当前rule创建完成之后发布到外部数据源的时候,只会把刚创建的这个发布出去,导致之前存在的rule被覆盖掉。
- 在原有的各个Controller的list方法中,在从外部加载rule之后,会调用repository的saveAll方法(就是InMemoryRuleRepositoryAdapter的saveAll方法),在该方法中会清除所有的rule,这相当于内存中同时只能有一个app的rule集合存在。
改动:
- 不再使用InMemoryRuleRepositoryAdapter的各个实现类作为repository,仅使用外部数据源,即
InDataSourceRuleStore
- 增加
NacosConfig
和NacosConfigUtil
,作为和Nacos
通信的基础类- 增加
rule > nacos
包的以下类(以Provider
或Publisher
结尾),用于各类Rule和外部数据源交互- 增加
InDataSourceRuleStore
类,该类提供了findById、list、save、update、delete
方法用于和外部数据源交互,提供了format
方法,用于格式化从外部数据源获取到的数据,提供了merge
方法用于在update
时做数据整合- 修改
controller
类,继承InDataSourceRuleStore
类,不再使用原有的repository
进行存储,使用InDataSourceRuleStore
定义方法操作,同时修改注入的DynamicRuleProvider
和DynamicRulePublisher
实现
- controller类
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 extends InDataSourceRuleStore<FlowRuleEntity>{
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
// @Qualifier("flowRuleDefaultProvider")
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
// @Qualifier("flowRuleDefaultPublisher")
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
try {
/*List<FlowRuleEntity> rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
rules = repository.saveAll(rules);*/
List<FlowRuleEntity> rules = this.list(ruleProvider, app);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
@AuthAction(value = AuthService.PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
/*entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());*/
try {
/*entity = repository.save(entity);
publishRules(entity.getApp());*/
this.save(rulePublisher, ruleProvider, entity);
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiUpdateFlowRule(@PathVariable("id") Long id,
@RequestBody FlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
/* FlowRuleEntity oldEntity = repository.findById(id);*/
FlowRuleEntity oldEntity = this.findById(ruleProvider, entity.getApp(), id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
/*entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
*/
entity.setId(id);
/*Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);*/
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
try {
/*entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}*/
return this.update(rulePublisher, ruleProvider, entity);
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id, @RequestParam("app") String app) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
if (StringUtils.isEmpty(app)) {
return Result.ofFail(-1, "Invalid app");
}
/*FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}*/
try {
/*repository.delete(id);*/
return this.delete(rulePublisher, ruleProvider, id, app);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
}
@Override
protected void format(FlowRuleEntity entity, String app) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
}
@Override
protected void merge(FlowRuleEntity entity, FlowRuleEntity oldEntity) {
entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
}
}
注释部分为原有代码
- 页面部分更改
-
identity.js
文件中app.controller
下的FlowServiceV1
改为FlowServiceV2
-
sidebar.html
修改如下:
<ul class="nav nav-second-level" ng-show="entry.active"> <li ui-sref-active="active"> <a ui-sref="dashboard.metric({app: entry.app})"> <i class="fa fa-bar-chart"></i> 实时监控</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.identity({app: entry.app})"> <i class="glyphicon glyphicon-list-alt"></i> 簇点链路</a> </li> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayIdentity({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 请求链路</a> </li> <!--<li ui-sref-active="active" ng-if="entry.appType==0"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则-Nacos</a> </li>--> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayApi({app: entry.app})"> <i class="glyphicon glyphicon-tags"></i> API 管理</a> </li> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayFlow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li> <!--<li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>--> <li ui-sref-active="active"> <a ui-sref="dashboard.degrade({app: entry.app})"> <i class="glyphicon glyphicon-flash"></i> 降级规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.paramFlow({app: entry.app})"> <i class="glyphicon glyphicon-fire"></i> 热点规则</a> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.system({app: entry.app})"> <i class="glyphicon glyphicon-lock"></i> 系统规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.authority({app: entry.app})"> <i class="glyphicon glyphicon-check"></i> 授权规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.clusterAppServerList({app: entry.app})"> <i class="glyphicon glyphicon-cloud"></i> 集群流控</a> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.machine({app: entry.app})"> <i class="glyphicon glyphicon-th-list"></i> 机器列表</a> </li> </ul>
- 接下来是包
webapp-resources-app-scripts-service
下对应控制规则的js做修改,修改部分主要为delete方
法,因为我们对删除方法做过更改,删除逻辑变为从远程获取所有规则,删除当前匹配规则,推送远程,修改
部分为参数增加参数app
依次修改this.deleteRule = function (rule) { var param = { app: rule.app }; return $http({ url: '/v2/flow/rule/' + rule.id, params: param, method: 'DELETE' }); };
flow_service_v2.js
,authority_service.js
,degrade_service.js
,param_flow_service.js
,
systemservice.js
,api_service.js
,flow_service.js
, 后两个js在gateway下- 修改页面或者JS后不生效的话,可能需要重新编译angular
- 找到
package.json
文件,鼠标右键Run 'npm install'
,待依赖安装完成,如果直接在webapp/resource
下执行npm run build
,会报错缺失gulp
- 执行
npm run build
,完成编译,文件就会更新了
-
最后可能有人需要这个util
package com.alibaba.csp.sentinel.dashboard.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JSONUtils {
public static <T> String toJSONString(Object object) {
try {
return new ObjectMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return new ObjectMapper()
.getTypeFactory()
.constructParametricType(collectionClass, elementClasses);
}
public static <T> List<T> parseObject(Class<T> clazz, String string) {
JavaType javaType = getCollectionType(ArrayList.class, clazz);
try {
return (List<T>) new ObjectMapper().readValue(string, javaType);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}
改造过程碰到问题颇多,感谢 github-franciszhao 和官方文档 在生产环境中使用 Sentinel 提供相关帮助
谢谢大家关注,点个赞呗~
如需转载请标明出处,谢谢~~