sentinel之dashboard改造

前言

在sentinel控制台设备的规则信息默认都是存储在内存当中,无论是重启了 sentinel 的客户端还是 sentinel 的控制台,所设置的规则都会丢失。如果要线上环境使用,那肯定是不行,解决方法也两种:

  • 一是使用阿里云付费版本,即 AHAS Sentinel ,好处是不用踩坑(==坑很多==)
  • 二是如题所示,自己改造dashboard客户端集成数据源

见官方文档 在生产环境中使用 Sentinel

找过很多关于集成的相关文章,基本都是仿照官网给的限流规则的例子来做的,如果仅仅按照官网案例实现,那是绝对不能用于线上环境去使用的。好了,废话不多说,开始我们的改造之旅吧。

可查看改造源码文件toBearShmily / sentinel-dashboard-nacos-1.8.0

环境版本相关说明

  1. 自定义数据源:nacos-1.4.1
  2. sentinel-dashboard版本:sentinel-1.8.0
  3. 通过推模式持久化规则数据到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包,相关文件如下:

package.png
  • 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下,如图:

1630056096708.png

接下来还是以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来和外部数据源通信,但是仅仅增加这两个类的实现并不够,使用下来发现的问题

  1. 新建RuleEntity的时候,ID是从代码中的AtomicLong变量获取的,每次这个变量都是从0开始计数,也就意味着每次重启之后ID都重新计数,这在使用内存存储rule的时候没有问题,但是一旦有外部数据源,这地方逻辑就不对了。
  2. 新建RuleEntity之后,会将当前所有的RuleEntity发布到外部数据源,如果是从资源列表页(请求链路或簇点链路)直接创建规则,那么这时候还没从外部数据源加载已存在的rule(只有访问对应的规则页面的list接口才会从远程加载),当前rule创建完成之后发布到外部数据源的时候,只会把刚创建的这个发布出去,导致之前存在的rule被覆盖掉。
  3. 在原有的各个Controller的list方法中,在从外部加载rule之后,会调用repository的saveAll方法(就是InMemoryRuleRepositoryAdapter的saveAll方法),在该方法中会清除所有的rule,这相当于内存中同时只能有一个app的rule集合存在。

改动

  1. 不再使用InMemoryRuleRepositoryAdapter的各个实现类作为repository,仅使用外部数据源,即InDataSourceRuleStore
  2. 增加NacosConfigNacosConfigUtil,作为和Nacos通信的基础类
  3. 增加rule > nacos包的以下类(以ProviderPublisher结尾),用于各类Rule和外部数据源交互
  4. 增加InDataSourceRuleStore类,该类提供了findById、list、save、update、delete方法用于和外部数据源交互,提供了format方法,用于格式化从外部数据源获取到的数据,提供了merge方法用于在update时做数据整合
  5. 修改controller类,继承InDataSourceRuleStore类,不再使用原有的repository进行存储,使用InDataSourceRuleStore定义方法操作,同时修改注入的DynamicRuleProviderDynamicRulePublisher实现
  • 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);
     }
 }

注释部分为原有代码

  • 页面部分更改
    1. identity.js 文件中 app.controller 下的 FlowServiceV1 改为 FlowServiceV2
    2. 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>&nbsp;&nbsp;实时监控</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>&nbsp;&nbsp;簇点链路</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>&nbsp;&nbsp;请求链路</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>&nbsp;&nbsp;流控规则-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>&nbsp;&nbsp;&nbsp;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>&nbsp;&nbsp;流控规则</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>&nbsp;&nbsp;流控规则</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>&nbsp;&nbsp;流控规则</a>
            </li>-->
    
            <li ui-sref-active="active">
              <a ui-sref="dashboard.degrade({app: entry.app})">
                <i class="glyphicon glyphicon-flash"></i>&nbsp;&nbsp;降级规则</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>&nbsp;&nbsp;热点规则</a>
            </li>
            <li ui-sref-active="active">
              <a ui-sref="dashboard.system({app: entry.app})">
                <i class="glyphicon glyphicon-lock"></i>&nbsp;&nbsp;系统规则</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>&nbsp;&nbsp;授权规则</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>&nbsp;&nbsp;集群流控</a>
            </li>
    
            <li ui-sref-active="active">
              <a ui-sref="dashboard.machine({app: entry.app})">
                <i class="glyphicon glyphicon-th-list"></i>&nbsp;&nbsp;机器列表</a>
            </li>
          </ul>
    
    1. 接下来是包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下
    1. 修改页面或者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 提供相关帮助

谢谢大家关注,点个赞呗~
如需转载请标明出处,谢谢~~

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351