Sentinel 热点规则规则持久化 push模式 Nacos

该博客介绍了如何将Sentinel的热点规则配置持久化到Nacos中,通过修改Sentinel Dashboard的源码实现Push模式,包括配置YAML文件、创建转换器、提供者和发布者类,以及更新相关控制器和视图,从而支持Nacos存储和推送Sentinel的热点限流规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇Sentinel 限流规则规则持久化 push模式 Nacos
准备工作可参考流控规则 https://editor.youkuaiyun.com/md/?articleId=122101156

Sentinel下载地址(已修改)

  1. 修改应用服务的application.yml
    在这里插入图片描述
 param-flow:
          nacos:
            server-addr: localhost:8848 # nacos地址
            dataId: orderservice-param-rules
            groupId: SENTINEL_GROUP
            rule-type: param-flow # 还可以是:degrade、authority、param-flow

以下部分为Sentinel-1.8.1源码包中的sentinel-dashboard修改

  • NacosConfig.java添加代码
  @Bean
    public Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() {
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() {
        return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
    }
  • 新增ParamFlowRuleNacosProvider.java
    在这里插入图片描述
    位置:com.alibaba.csp.sentinel.dashboard.rule.nacos
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * Sentinel 热点规则 持久化  Push 模式 Nacos
 * @Author jianghx
 * @Date 2021/12/24 11:47
 * @Version 1.0
 **/
@Component("paramFlowRuleNacosProvider")
public class ParamFlowRuleNacosProvider implements DynamicRuleProvider<List<ParamFlowRuleEntity>> {

    @Autowired
    private ConfigService configService;

    @Autowired
    private Converter<String, List<ParamFlowRuleEntity>> converter;

    @Override
    public List<ParamFlowRuleEntity> getRules(String appName) throws Exception {
        String rules = configService.getConfig(appName + NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX,
                NacosConfigUtil.GROUP_ID, 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}

  • 新增ParamFlowRuleNacosPublisher.java
    在这里插入图片描述
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
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;
import java.util.concurrent.CompletableFuture;

/**
 * Sentinel 热点规则 持久化  Push 模式 Nacos
 * @Author jianghx
 * @Date 2021/12/27 10:27
 * @Version 1.0
 **/
@Component("paramFlowRuleNacosPublisher")
public class ParamFlowRuleNacosPublisher implements DynamicRulePublisher<List<ParamFlowRuleEntity>>
{

    @Autowired
    private ConfigService configService;
    @Autowired
    private Converter<List<ParamFlowRuleEntity>, String> converter;

    @Override
    public CompletableFuture<Void> publish(String app, List<ParamFlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return null;
        }
        configService.publishConfig(app + NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX,
                NacosConfigUtil.GROUP_ID, converter.convert(rules));
        return null;
    }
}
  • 新增ParamFlowRuleControllerV2.java
    在这里插入图片描述
package com.alibaba.csp.sentinel.dashboard.controller.v2;

import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.controller.ParamFlowRuleController;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.util.VersionUtils;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * Sentinel 热点规则 持久化  Push 模式 Nacos
 * @Author jianghx
 * @Date 2021/12/27 10:30
 * @Version 1.0
 **/
@RestController
@RequestMapping(value = "/v2/paramFlow")
public class ParamFlowRuleControllerV2
{

    private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);

    @Autowired
    private AppManagement appManagement;
    @Autowired
    private RuleRepository<ParamFlowRuleEntity, Long> repository;

    @Autowired
    @Qualifier("paramFlowRuleNacosProvider")
    private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;

    @Autowired
    @Qualifier("paramFlowRuleNacosPublisher")
    private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;

    private boolean checkIfSupported(String app, String ip, int port) {
        try {
            return Optional.ofNullable(appManagement.getDetailApp(app))
                    .flatMap(e -> e.getMachine(ip, port))
                    .flatMap(m -> VersionUtils.parseVersion(m.getVersion())
                            .map(v -> v.greaterOrEqual(version020)))
                    .orElse(true);
            // If error occurred or cannot retrieve machine info, return true.
        } catch (Exception ex) {
            return true;
        }
    }

    @GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
                                                                        @RequestParam String ip,
                                                                        @RequestParam Integer port) {
        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app cannot be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip cannot be null or empty");
        }
        if (port == null || port <= 0) {
            return Result.ofFail(-1, "Invalid parameter: port");
        }
        if (!checkIfSupported(app, ip, port)) {
            return unsupportedVersion();
        }
        try {
            List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
            if (rules != null && !rules.isEmpty()) {
                for (ParamFlowRuleEntity entity : rules) {
                    entity.setApp(app);
                }
            }
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (ExecutionException ex) {
            logger.error("Error when querying parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when querying parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private boolean isNotSupported(Throwable ex) {
        return ex instanceof CommandNotFoundException;
    }

    @PostMapping("/rule")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<ParamFlowRuleEntity> apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) {
        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
            return unsupportedVersion();
        }
        entity.setId(null);
        entity.getRule().setResource(entity.getResource().trim());
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when adding new parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when adding new parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
        if (entity == null) {
            return Result.ofFail(-1, "bad rule body");
        }
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (entity.getPort() == null || entity.getPort() <= 0) {
            return Result.ofFail(-1, "port can't be null");
        }
        if (entity.getRule() == null) {
            return Result.ofFail(-1, "rule can't be null");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1, "resource name cannot be null or empty");
        }
        if (entity.getCount() < 0) {
            return Result.ofFail(-1, "count should be valid");
        }
        if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) {
            return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
        }
        if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
            return Result.ofFail(-1, "paramIdx should be valid");
        }
        if (entity.getDurationInSec() <= 0) {
            return Result.ofFail(-1, "durationInSec should be valid");
        }
        if (entity.getControlBehavior() < 0) {
            return Result.ofFail(-1, "controlBehavior should be valid");
        }
        return null;
    }

    @PutMapping("/rule/{id}")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
                                                              @RequestBody ParamFlowRuleEntity entity) {
        if (id == null || id <= 0) {
            return Result.ofFail(-1, "Invalid id");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofFail(-1, "id " + id + " does not exist");
        }

        Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
            return unsupportedVersion();
        }
        entity.setId(id);
        Date date = new Date();
        entity.setGmtCreate(oldEntity.getGmtCreate());
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (ExecutionException ex) {
            logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when updating parameter flow rules, id=" + id, throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    @DeleteMapping("/rule/{id}")
    @AuthAction(PrivilegeType.DELETE_RULE)
    public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
        if (id == null) {
            return Result.ofFail(-1, "id cannot be null");
        }
        ParamFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
            publishRules(oldEntity.getApp());
            return Result.ofSuccess(id);
        } catch (ExecutionException ex) {
            logger.error("Error when deleting parameter flow rules", ex.getCause());
            if (isNotSupported(ex.getCause())) {
                return unsupportedVersion();
            } else {
                return Result.ofThrowable(-1, ex.getCause());
            }
        } catch (Throwable throwable) {
            logger.error("Error when deleting parameter flow rules", throwable);
            return Result.ofFail(-1, throwable.getMessage());
        }
    }

    private CompletableFuture<Void> publishRules(String app) throws Exception {
        List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
        return rulePublisher.publish(app,rules);
    }

    private <R> Result<R> unsupportedVersion() {
        return Result.ofFail(4041,
                "Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
    }

    private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}


  • 修改sidebar.html

在这里插入图片描述

 <li ui-sref-active="active" ng-if="entry.appType==0">
            <a ui-sref="dashboard.paramFlow2({app: entry.app})">
              <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;&nbsp;热点规则-Nacos</a>
          </li>

  • 修改resources\app\scripts\app.js
    在这里插入图片描述
 .state('dashboard.paramFlow2', {
          templateUrl: 'app/views/paramFlow_v2.html',
          url: '/v2/paramFlow/:app',
          controller: 'ParamFlowRuleControllerV2',
          resolve: {
              loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
                  return $ocLazyLoad.load({
                      name: 'sentinelDashboardApp',
                      files: [
                          'app/scripts/controllers/paramFlow_v2.js',
                      ]
                  });
              }]
          }
      })
  • 修改resources\dist\js\app.js
    在这里插入图片描述
   .state("dashboard.paramFlow2", {
        templateUrl: "app/views/paramFlow_v2.html",
        url: "/v2/paramFlow/:app",
        controller: "ParamFlowControllerV2",
        resolve: {
            loadMyFiles: ["$ocLazyLoad", function (e) {
                return e.load({name: "sentinelDashboardApp", files: ["app/scripts/controllers/paramFlow_v2.js"]})
            }]
        }
    })

app.js 搜索 ParamFlowService,并在后面追加一下代码
在这里插入图片描述

, angular.module("sentinelDashboardApp").service("ParamFlowService2", ["$http", function (a) {
        function o(e) {
            return !("int" !== (r = e.classType) && "double" !== r && "float" !== r && "long" !== r && "short" !== r || void 0 !== (t = e.object) && "" !== t && !isNaN(t)) || (!!("byte" === e.classType && (a = e.object, o = -128, l = 127, void 0 === a || "" === a || isNaN(a) || a < o || l < a)) || (void 0 === e.object || void 0 === e.classType || (void 0 === (n = e.count) || "" === n || isNaN(n) || n < 0)));
            var t, r, a, o, l, n
        }

        this.queryMachineRules = function (e, t, r) {
            return a({url: "v2/paramFlow/rules", params: {app: e, ip: t, port: r}, method: "GET"})
        }, this.addNewRule = function (e) {
            return a({url: "/v2/paramFlow/rule", data: e, method: "POST"})
        }, this.saveRule = function (e) {
            return a({url: "/v2/paramFlow/rule/" + e.id, data: e, method: "PUT"})
        }, this.deleteRule = function (e) {
            return a({url: "/v2/paramFlow/rule/" + e.id, method: "DELETE"})
        }, this.checkRuleValid = function (e) {
            if (!e.resource || "" === e.resource) return alert("资源名称不能为空"), !1;
            if (1 != e.grade) return alert("未知的限流模式"), !1;
            if (e.count < 0) return alert("限流阈值必须大于等于 0"), !1;
            if (void 0 === e.paramIdx || "" === e.paramIdx || isNaN(e.paramIdx) || e.paramIdx < 0) return alert("热点参数索引必须大于等于 0"), !1;
            if (void 0 !== e.paramFlowItemList) for (var t = 0; t < e.paramFlowItemList.length; t++) {
                var r = e.paramFlowItemList[t];
                if (o(r)) return alert("热点参数例外项不合法,请检查值和类型是否正确:参数为 " + r.object + ", 类型为 " + r.classType + ", 限流阈值为 " + r.count), !1
            }
            return !0
        }
    }
])
  • 新增resources\app\views\paramFlow_v2.html
    在这里插入图片描述
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
    <div class="col-md-6" style="margin-bottom: 10px;">
      <span style="font-size: 30px;font-weight: bold;">{{app}}</span>
    </div>
    <div class="col-md-6" ng-if="!loadError">
      <button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
        <i class="fa fa-plus"></i>&nbsp;&nbsp;新增热点限流规则</button>
    </div>
  </div>
  
  <div class="separator"></div>
  
  <div class="container-fluid">
    <div class="row" style="margin-top: 20px; margin-bottom: 20px;">
      <div class="col-md-12">
        <div class="card">
          <div class="inputs-header">
            <span class="brand" style="font-size: 13px;">热点参数限流规则</span>
            <button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getMachineRules()">刷新</button>
            <input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
            <div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
              <selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"
                placeholder="机器"></selectize>
            </div>
          </div>

            <!-- error panel -->
            <div class="row clearfix" ng-if="loadError">
                <div class="col-md-6 col-md-offset-3">
                    <div class="panel panel-default">
                        <div class="panel-body">
                            <center>
                                <p>{{loadError.message}}</p>
                            </center>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Table and pagination start -->
                <!--.tools-header -->
                <div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
                    <table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
                        <thead>
                        <tr style="background: #F3F5F7;">
                            <td style="width: 40%">
                                资源名
                            </td>
                            <td style="width: 10%;">
                                参数索引
                            </td>
                            <td style="width: 10%;">
                                流控模式
                            </td>
                            <td style="width: 10%;">
                                阈值
                            </td>
                            <td style="width: 8%;">
                                是否集群
                            </td>
                            <td style="width: 10%;">
                                例外项数目
                            </td>
                            <td style="width: 12%;">
                                操作
                            </td>
                        </tr>
                        </thead>
                        <tbody>
                        <tr dir-paginate="ruleEntity in rules | filter: searchKey | itemsPerPage: rulesPageConfig.pageSize " current-page="rulesPageConfig.currentPageIndex"
                            pagination-id="entriesPagination">
                            <td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.resource}}</td>
                            <td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.paramIdx}}</td>
                            <td>
                                {{ruleEntity.rule.grade == 1 ? 'QPS' : '未知'}}
                            </td>
                            <td style="word-wrap:break-word;word-break:break-all;">
                                {{ruleEntity.rule.count}}
                            </td>
                            <td>
                                <span ng-if="ruleEntity.rule.clusterMode"></span>
                                <span ng-if="!ruleEntity.rule.clusterMode"></span>
                            </td>
                            <td>
                                {{ruleEntity.rule.paramFlowItemList == undefined ? 0 : ruleEntity.rule.paramFlowItemList.length}}
                            </td>
                            <td>
                                <button class="btn btn-xs btn-default" type="button" ng-click="editRule(ruleEntity)" style="font-size: 12px; height:25px;">编辑</button>
                                <button class="btn btn-xs btn-default" type="button" ng-click="deleteRule(ruleEntity)" style="font-size: 12px; height:25px;">删除</button>
                            </td>
                        </tr>
                        </tbody>
                    </table>
                </div>
                <!-- .card-body -->
                <div class="pagination-footer" ng-if="!loadError">
                    <dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
                                             on-page-change="">
                    </dir-pagination-controls>
                    <div class="tools" style="">
                        <span>{{rulesPageConfig.totalCount}} 条记录, </span>
                        <span>每页 <input class="form-control" ng-model="rulesPageConfig.pageSize"> 条记录</span>
                        <!--<span>{{rulesPageConfig.currentPageIndex}} / {{rulesPageConfig.totalPage}}</span>-->
                    </div>
                    <!-- .tools -->
                </div>
                <!-- pagination-footer -->
            <!-- Table and pagination end -->

        </div>
        <!-- .card -->
      </div>
      <!-- .col-md-12 -->
    </div>
    <!-- -->
  </div>
  <!-- .container-fluid -->
  
  • 新增resources\app\scripts\controllers\paramFlow_v2.js
    在这里插入图片描述
/**
 * Parameter flow control controller.
 * 
 * @author Eric Zhao
 */
angular.module('sentinelDashboardApp').controller('ParamFlowRuleControllerV2', ['$scope', '$stateParams', 'ParamFlowService2', 'ngDialog',
  'MachineService',
  function ($scope, $stateParams, ParamFlowService, ngDialog,
    MachineService) {
    const UNSUPPORTED_CODE = 4041;
    $scope.app = $stateParams.app;
    $scope.curExItem = {};

    $scope.paramItemClassTypeList = [
      'int', 'double', 'java.lang.String', 'long', 'float', 'char', 'byte'
    ];

    $scope.rulesPageConfig = {
      pageSize: 10,
      currentPageIndex: 1,
      totalPage: 1,
      totalCount: 0,
    };
    $scope.macsInputConfig = {
      searchField: ['text', 'value'],
      persist: true,
      create: false,
      maxItems: 1,
      render: {
        item: function (data, escape) {
          return '<div>' + escape(data.text) + '</div>';
        }
      },
      onChange: function (value, oldValue) {
        $scope.macInputModel = value;
      }
    };

      function updateSingleParamItem(arr, v, t, c) {
          for (let i = 0; i < arr.length; i++) {
              if (arr[i].object === v && arr[i].classType === t) {
                  arr[i].count = c;
                  return;
              }
          }
          arr.push({object: v, classType: t, count: c});
      }

      function removeSingleParamItem(arr, v, t) {
          for (let i = 0; i < arr.length; i++) {
              if (arr[i].object === v && arr[i].classType === t) {
                  arr.splice(i, 1);
                  break;
              }
          }
      }

      function isNumberClass(classType) {
        return classType === 'int' || classType === 'double' ||
            classType === 'float' || classType === 'long' || classType === 'short';
      }

      function isByteClass(classType) {
          return classType === 'byte';
      }

      function notNumberAtLeastZero(num) {
        return num === undefined || num === '' || isNaN(num) || num < 0;
      }

      function notGoodNumber(num) {
          return num === undefined || num === '' || isNaN(num);
      }

      function notGoodNumberBetweenExclusive(num, l ,r) {
          return num === undefined || num === '' || isNaN(num) || num < l || num > r;
      }

      $scope.notValidParamItem = (curExItem) => {
        if (isNumberClass(curExItem.classType) && notGoodNumber(curExItem.object)) {
          return true;
        }
        if (isByteClass(curExItem.classType) && notGoodNumberBetweenExclusive(curExItem.object, -128, 127)) {
          return true;
        }
        return curExItem.object === undefined || curExItem.classType === undefined ||
            notNumberAtLeastZero(curExItem.count);
      };

      $scope.addParamItem = () => {
          updateSingleParamItem($scope.currentRule.rule.paramFlowItemList,
              $scope.curExItem.object, $scope.curExItem.classType, $scope.curExItem.count);
          let oldItem = $scope.curExItem;
          $scope.curExItem = {classType: oldItem.classType};
      };

      $scope.removeParamItem = (v, t) => {
          removeSingleParamItem($scope.currentRule.rule.paramFlowItemList, v, t);
      };

    function getMachineRules() {
      if (!$scope.macInputModel) {
        return;
      }
      let mac = $scope.macInputModel.split(':');
      ParamFlowService.queryMachineRules($scope.app, mac[0], mac[1])
        .success(function (data) {
          if (data.code === 0 && data.data) {
            $scope.loadError = undefined;
            $scope.rules = data.data;
            $scope.rulesPageConfig.totalCount = $scope.rules.length;
          } else {
            $scope.rules = [];
            $scope.rulesPageConfig.totalCount = 0;
            if (data.code === UNSUPPORTED_CODE) {
              $scope.loadError = {message: "机器 " + mac[0] + ":" + mac[1] + " 的 Sentinel 客户端版本不支持热点参数限流功能,请升级至 0.2.0 以上版本并引入 sentinel-parameter-flow-control 依赖。"}
            } else {
              $scope.loadError = {message: data.msg}
            }
          }
        })
        .error((data, header, config, status) => {
          $scope.loadError = {message: "未知错误"}
        });
    }
    $scope.getMachineRules = getMachineRules;
    getMachineRules();

    var paramFlowRuleDialog;

    $scope.editRule = function (rule) {
      $scope.currentRule = angular.copy(rule);
      if ($scope.currentRule.rule && $scope.currentRule.rule.durationInSec === undefined) {
        $scope.currentRule.rule.durationInSec = 1;
      }
      $scope.paramFlowRuleDialog = {
        title: '编辑热点规则',
        type: 'edit',
        confirmBtnText: '保存',
        supportAdvanced: true,
        showAdvanceButton: rule.rule.paramFlowItemList === undefined || rule.rule.paramFlowItemList.length <= 0
      };
      paramFlowRuleDialog = ngDialog.open({
        template: '/app/views/dialog/param-flow-rule-dialog.html',
        width: 680,
        overlay: true,
        scope: $scope
      });
      $scope.curExItem = {};
    };

    $scope.addNewRule = function () {
      var mac = $scope.macInputModel.split(':');
      $scope.currentRule = {
        app: $scope.app,
        ip: mac[0],
        port: mac[1],
        rule: {
          grade: 1,
          paramFlowItemList: [],
          count: 0,
          limitApp: 'default',
          controlBehavior: 0,
          durationInSec: 1,
          burstCount: 0,
          maxQueueingTimeMs: 0,
          clusterMode: false,
          clusterConfig: {
            thresholdType: 0,
            fallbackToLocalWhenFail: true,
          }
        }
      };
      $scope.paramFlowRuleDialog = {
        title: '新增热点规则',
        type: 'add',
        confirmBtnText: '新增',
        supportAdvanced: true,
        showAdvanceButton: true,
      };
      paramFlowRuleDialog = ngDialog.open({
        template: '/app/views/dialog/param-flow-rule-dialog.html',
        width: 680,
        overlay: true,
        scope: $scope
      });
      $scope.curExItem = {};
    };

      $scope.onOpenAdvanceClick = function () {
          $scope.paramFlowRuleDialog.showAdvanceButton = false;
      };
      $scope.onCloseAdvanceClick = function () {
          $scope.paramFlowRuleDialog.showAdvanceButton = true;
      };

    $scope.saveRule = function () {
      if (!ParamFlowService.checkRuleValid($scope.currentRule.rule)) {
        return;
      }
      if ($scope.paramFlowRuleDialog.type === 'add') {
        addNewRuleAndPush($scope.currentRule);
      } else if ($scope.paramFlowRuleDialog.type === 'edit') {
        saveRuleAndPush($scope.currentRule, true);
      }
    };

    function addNewRuleAndPush(rule) {
      ParamFlowService.addNewRule(rule).success((data) => {
        if (data.success) {
          getMachineRules();
          paramFlowRuleDialog.close();
        } else {
          alert('添加规则失败:' + data.msg);
        }
      }).error((data) => {
        if (data) {
          alert('添加规则失败:' + data.msg);
        } else {
          alert("添加规则失败:未知错误");
        }
      });
    }

    function saveRuleAndPush(rule, edit) {
      ParamFlowService.saveRule(rule).success(function (data) {
        if (data.success) {
          alert("修改规则成功");
          getMachineRules();
          if (edit) {
            paramFlowRuleDialog.close();
          } else {
            confirmDialog.close();
          }
        } else {
          alert('修改规则失败:' + data.msg);
        }
      }).error((data) => {
        if (data) {
          alert('修改规则失败:' + data.msg);
        } else {
          alert("修改规则失败:未知错误");
        }
      });
    }

    function deleteRuleAndPush(entity) {
      if (entity.id === undefined || isNaN(entity.id)) {
        alert('规则 ID 不合法!');
        return;
      }
      ParamFlowService.deleteRule(entity).success((data) => {
        if (data.code == 0) {
          getMachineRules();
          confirmDialog.close();
        } else {
          alert('删除规则失败:' + data.msg);
        }
      }).error((data) => {
        if (data) {
          alert('删除规则失败:' + data.msg);
        } else {
          alert("删除规则失败:未知错误");
        }
      });
    };

    var confirmDialog;
    $scope.deleteRule = function (ruleEntity) {
      $scope.currentRule = ruleEntity;
      console.log('deleting: ' + ruleEntity);
      $scope.confirmDialog = {
        title: '删除热点规则',
        type: 'delete_rule',
        attentionTitle: '请确认是否删除如下热点参数限流规则',
        attention: '资源名: ' + ruleEntity.rule.resource + ', 热点参数索引: ' + ruleEntity.rule.paramIdx +
            ', 限流模式: ' + (ruleEntity.rule.grade === 1 ? 'QPS' : '未知') + ', 限流阈值: ' + ruleEntity.rule.count,
        confirmBtnText: '删除',
      };
      confirmDialog = ngDialog.open({
        template: '/app/views/dialog/confirm-dialog.html',
        scope: $scope,
        overlay: true
      });
    };

    $scope.confirm = function () {
      if ($scope.confirmDialog.type === 'delete_rule') {
        deleteRuleAndPush($scope.currentRule);
      } else {
        console.error('error');
      }
    };

    queryAppMachines();

    function queryAppMachines() {
      MachineService.getAppMachines($scope.app).success(
        function (data) {
          if (data.code == 0) {
            // $scope.machines = data.data;
            if (data.data) {
              $scope.machines = [];
              $scope.macsInputOptions = [];
              data.data.forEach(function (item) {
                if (item.healthy) {
                  $scope.macsInputOptions.push({
                    text: item.ip + ':' + item.port,
                    value: item.ip + ':' + item.port
                  });
                }
              });
            }
            if ($scope.macsInputOptions.length > 0) {
              $scope.macInputModel = $scope.macsInputOptions[0].value;
            }
          } else {
            $scope.macsInputOptions = [];
          }
        }
      );
    };
    $scope.$watch('macInputModel', function () {
      if ($scope.macInputModel) {
        getMachineRules();
      }
    });
  }]);
  • 新增resources\app\scripts\services\paramFlow_service_v2.js
    在这里插入图片描述
/**
 * Parameter flow control service.
 * 
 * @author Eric Zhao
 */
angular.module('sentinelDashboardApp').service('ParamFlowService2', ['$http', function ($http) {
  this.queryMachineRules = function(app, ip, port) {
    var param = {
      app: app,
      ip: ip,
      port: port
    };
    return $http({
      url: '/v2/paramFlow/rules',
      params: param,
      method: 'GET'
    });
  };

  this.addNewRule = function(rule) {
    return $http({
      url: '/v2/paramFlow/rule',
      data: rule,
      method: 'POST'
    });
  };

  this.saveRule = function (entity) {
    return $http({
      url: '/v2/paramFlow/rule/' + entity.id,
      data: entity,
      method: 'PUT'
    });
  };

  this.deleteRule = function (entity) {
    return $http({
      url: '/v2/paramFlow/rule/' + entity.id,
      method: 'DELETE'
    });
  };

    function isNumberClass(classType) {
        return classType === 'int' || classType === 'double' ||
            classType === 'float' || classType === 'long' || classType === 'short';
    }

    function isByteClass(classType) {
        return classType === 'byte';
    }

    function notNumberAtLeastZero(num) {
        return num === undefined || num === '' || isNaN(num) || num < 0;
    }

    function notGoodNumber(num) {
        return num === undefined || num === '' || isNaN(num);
    }

    function notGoodNumberBetweenExclusive(num, l ,r) {
        return num === undefined || num === '' || isNaN(num) || num < l || num > r;
    }

    function notValidParamItem(curExItem) {
        if (isNumberClass(curExItem.classType) && notGoodNumber(curExItem.object)) {
            return true;
        }
        if (isByteClass(curExItem.classType) && notGoodNumberBetweenExclusive(curExItem.object, -128, 127)) {
            return true;
        }
        return curExItem.object === undefined || curExItem.classType === undefined ||
            notNumberAtLeastZero(curExItem.count);
    }

  this.checkRuleValid = function (rule) {
      if (!rule.resource || rule.resource === '') {
          alert('资源名称不能为空');
          return false;
      }
      if (rule.grade != 1) {
          alert('未知的限流模式');
          return false;
      }
      if (rule.count < 0) {
          alert('限流阈值必须大于等于 0');
          return false;
      }
      if (rule.paramIdx === undefined || rule.paramIdx === '' || isNaN(rule.paramIdx) || rule.paramIdx < 0) {
          alert('热点参数索引必须大于等于 0');
          return false;
      }
      if (rule.paramFlowItemList !== undefined) {
          for (var i = 0; i < rule.paramFlowItemList.length; i++) {
              var item = rule.paramFlowItemList[i];
              if (notValidParamItem(item)) {
                  alert('热点参数例外项不合法,请检查值和类型是否正确:参数为 ' + item.object + ', 类型为 ' +
                      item.classType + ', 限流阈值为 ' + item.count);
                  return false;
              }
          }
      }
      return true;
  };
}]);

  • 修改resources\gulpfile.js
    在这里插入图片描述
 'app/scripts/services/paramFlow_service_v2.js',
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值