上一篇Sentinel 限流规则规则持久化 push模式 Nacos
准备工作可参考流控规则 https://editor.youkuaiyun.com/md/?articleId=122101156
- 修改应用服务的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> 热点规则-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> 新增热点限流规则</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',