feat:2.4.0

1. 修复分布式锁锁定失败问题
2. Pods页面显示负责消费的桶信息
This commit is contained in:
byteblogs168 2023-11-05 08:15:55 +08:00
parent 1f3c6e4762
commit 95e594fabb
65 changed files with 197 additions and 357 deletions

View File

@ -13,4 +13,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/ */
public interface DistributedLockMapper extends BaseMapper<DistributedLock> { public interface DistributedLockMapper extends BaseMapper<DistributedLock> {
int updateTest(DistributedLock lock);
} }

View File

@ -18,4 +18,10 @@
id, name, lock_until, locked_at, locked_by, create_dt, update_dt id, name, lock_until, locked_at, locked_by, create_dt, update_dt
</sql> </sql>
<update id="updateTest">
update distributed_lock set locked_by = #{lockedBy},
lock_until = #{lockUntil},
locked_at = #{lockedAt}
where name = #{name} and lock_until <![CDATA[ <= ]]> #{lockedAt}
</update>
</mapper> </mapper>

View File

@ -5,10 +5,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* www.byteblogs.com
* *
* @author: shuguang.zhang * @author: www.byteblogs.com
* @date : 2023-09-21 09:26 * @date : 2023-09-21 09:26
* since: 2.4.0
*/ */
public class DistributeInstance { public class DistributeInstance {
private DistributeInstance() { private DistributeInstance() {

View File

@ -21,8 +21,8 @@ public abstract class AbstractLockProvider implements LockProvider {
String lockName = lockConfig.getLockName(); String lockName = lockConfig.getLockName();
boolean tryToCreateLockRecord = CacheLockRecord.lockRecordRecentlyCreated(lockName); boolean tryToCreateLockRecord = !CacheLockRecord.lockRecordRecentlyCreated(lockName);
if (!tryToCreateLockRecord) { if (tryToCreateLockRecord) {
if (doLock(lockConfig)) { if (doLock(lockConfig)) {
CacheLockRecord.addLockRecord(lockName); CacheLockRecord.addLockRecord(lockName);
return true; return true;

View File

@ -1,11 +1,13 @@
package com.aizuda.easy.retry.server.common.lock; package com.aizuda.easy.retry.server.common.lock;
import com.aizuda.easy.retry.common.core.log.LogUtils; import com.aizuda.easy.retry.common.core.log.LogUtils;
import com.aizuda.easy.retry.common.core.util.JsonUtil;
import com.aizuda.easy.retry.server.common.config.SystemProperties; import com.aizuda.easy.retry.server.common.config.SystemProperties;
import com.aizuda.easy.retry.server.common.dto.LockConfig; import com.aizuda.easy.retry.server.common.dto.LockConfig;
import com.aizuda.easy.retry.server.common.register.ServerRegister; import com.aizuda.easy.retry.server.common.register.ServerRegister;
import com.aizuda.easy.retry.template.datasource.persistence.mapper.DistributedLockMapper; import com.aizuda.easy.retry.template.datasource.persistence.mapper.DistributedLockMapper;
import com.aizuda.easy.retry.template.datasource.persistence.po.DistributedLock; import com.aizuda.easy.retry.template.datasource.persistence.po.DistributedLock;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -48,14 +50,13 @@ public class JdbcLockProvider extends AbstractLockProvider {
LocalDateTime now = lockConfig.getCreateDt(); LocalDateTime now = lockConfig.getCreateDt();
DistributedLock distributedLock = new DistributedLock(); DistributedLock distributedLock = new DistributedLock();
distributedLock.setLockedBy(ServerRegister.CURRENT_CID); distributedLock.setLockedBy(ServerRegister.CURRENT_CID);
distributedLock.setLockedAt(now);
LocalDateTime lockAtLeast = lockConfig.getLockAtLeast(); LocalDateTime lockAtLeast = lockConfig.getLockAtLeast();
distributedLock.setLockUntil(now.isBefore(lockAtLeast) ? lockAtLeast : now); distributedLock.setLockUntil(now.isBefore(lockAtLeast) ? lockAtLeast : now);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
try { try {
return distributedLockMapper.update(distributedLock, new LambdaUpdateWrapper<DistributedLock>() return distributedLockMapper.update(distributedLock, new LambdaUpdateWrapper<DistributedLock>()
.eq(DistributedLock::getName, lockConfig.getLockName())) > 0; .eq(DistributedLock::getName, lockConfig.getLockName())) > 0;
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(log, "unlock error. retrying attempt [{}] ", i, e); LogUtils.error(log, "unlock error. retrying attempt [{}] ", i, e);
} }
@ -78,10 +79,9 @@ public class JdbcLockProvider extends AbstractLockProvider {
distributedLock.setUpdateDt(now); distributedLock.setUpdateDt(now);
return distributedLockMapper.insert(distributedLock) > 0; return distributedLockMapper.insert(distributedLock) > 0;
} catch (DuplicateKeyException | ConcurrencyFailureException | TransactionSystemException e) { } catch (DuplicateKeyException | ConcurrencyFailureException | TransactionSystemException e) {
// LogUtils.warn(log,"Duplicate key. lockName:[{}]", lockConfig.getLockName());
return false; return false;
} catch (DataIntegrityViolationException | BadSqlGrammarException | UncategorizedSQLException e) { } catch (DataIntegrityViolationException | BadSqlGrammarException | UncategorizedSQLException e) {
LogUtils.error(log,"Unexpected exception. lockName:[{}]", lockConfig.getLockName(), e); LogUtils.error(log, "Unexpected exception. lockName:[{}]", lockConfig.getLockName(), e);
return false; return false;
} }
@ -95,11 +95,16 @@ public class JdbcLockProvider extends AbstractLockProvider {
distributedLock.setLockedBy(ServerRegister.CURRENT_CID); distributedLock.setLockedBy(ServerRegister.CURRENT_CID);
distributedLock.setLockedAt(now); distributedLock.setLockedAt(now);
distributedLock.setLockUntil(lockConfig.getLockAtMost()); distributedLock.setLockUntil(lockConfig.getLockAtMost());
return distributedLockMapper.update(distributedLock, new LambdaUpdateWrapper<DistributedLock>() distributedLock.setName(lockConfig.getLockName());
.eq(DistributedLock::getName, lockConfig.getLockName()) try {
.le(DistributedLock::getLockUntil, now)) > 0; return distributedLockMapper.update(distributedLock, new LambdaUpdateWrapper<DistributedLock>()
.eq(DistributedLock::getName, lockConfig.getLockName())
.le(DistributedLock::getLockUntil, now)) > 0;
} catch (ConcurrencyFailureException | DataIntegrityViolationException | TransactionSystemException |
UncategorizedSQLException e) {
return false;
}
} }
} }

View File

@ -45,14 +45,18 @@ public abstract class AbstractSchedule implements Schedule {
LockConfig lockConfig = new LockConfig(LocalDateTime.now(), lockName, Duration.parse(lockAtMost), Duration.parse(lockAtLeast)); LockConfig lockConfig = new LockConfig(LocalDateTime.now(), lockName, Duration.parse(lockAtMost), Duration.parse(lockAtLeast));
LockProvider lockProvider = getLockAccess(); LockProvider lockProvider = getLockAccess();
boolean lock = false;
try { try {
if (lockProvider.lock(lockConfig)) { lock = lockProvider.lock(lockConfig);
if (lock) {
doExecute(); doExecute();
} }
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(log, this.getClass().getName() + " execute error. lockName:[{}]", lockName, e); LogUtils.error(log, this.getClass().getName() + " execute error. lockName:[{}]", lockName, e);
} finally { } finally {
lockProvider.unlock(lockConfig); if (lock) {
lockProvider.unlock(lockConfig);
}
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0aa660"],{"119c":function(t,a,e){"use strict";e.r(a);e("b0c0");var o=function(){var t=this,a=t._self._c;return a("div",[a("page-header-wrapper",{staticStyle:{margin:"-24px -1px 0"},on:{back:function(){return t.$router.go(-1)}}},[a("div")]),null!==t.jobBatchInfo?a("a-card",{attrs:{bordered:!1}},[a("a-descriptions",{attrs:{title:"",column:3,bordered:""}},[a("a-descriptions-item",{attrs:{label:"组名称"}},[t._v(" "+t._s(t.jobBatchInfo.groupName)+" ")]),a("a-descriptions-item",{attrs:{label:"任务名称"}},[t._v(" "+t._s(t.jobBatchInfo.jobName)+" ")]),a("a-descriptions-item",{attrs:{label:"状态"}},[a("a-tag",{attrs:{color:t.taskBatchStatus[t.jobBatchInfo.taskBatchStatus].color}},[t._v(" "+t._s(t.taskBatchStatus[t.jobBatchInfo.taskBatchStatus].name)+" ")])],1),a("a-descriptions-item",{attrs:{label:"执行器类型"}},[a("a-tag",{attrs:{color:t.executorType[t.jobBatchInfo.executorType].color}},[t._v(" "+t._s(t.executorType[t.jobBatchInfo.executorType].name)+" ")])],1),a("a-descriptions-item",{attrs:{label:"操作原因"}},[a("a-tag",{attrs:{color:t.operationReason[t.jobBatchInfo.operationReason].color}},[t._v(" "+t._s(t.operationReason[t.jobBatchInfo.operationReason].name)+" ")])],1),a("a-descriptions-item",{attrs:{label:"开始执行时间"}},[t._v(" "+t._s(t.jobBatchInfo.executionAt)+" ")]),a("a-descriptions-item",{attrs:{label:"执行器名称",span:"4"}},[t._v(" "+t._s(t.jobBatchInfo.executorInfo)+" ")]),a("a-descriptions-item",{attrs:{label:"创建时间"}},[t._v(" "+t._s(t.jobBatchInfo.createDt)+" ")])],1)],1):t._e(),a("div",{staticStyle:{margin:"20px 0","border-left":"#f5222d 5px solid","font-size":"medium","font-weight":"bold"}},[t._v("    任务项列表 ")]),a("JobTaskList",{ref:"JobTaskListRef"})],1)},r=[],s=e("3b7a"),n=e("c1df"),c=e.n(n),i=e("38b7"),u=e.n(i),p=e("36e8"),b={name:"JobInfo",components:{JobTaskList:p["default"]},data:function(){return{jobBatchInfo:null,taskBatchStatus:u.a.taskBatchStatus,operationReason:u.a.operationReason,taskType:u.a.taskType,triggerType:u.a.triggerType,blockStrategy:u.a.blockStrategy,executorType:u.a.executorType}},created:function(){var t=this,a=this.$route.query.id,e=this.$route.query.groupName;a&&e?Object(s["d"])(a).then((function(e){t.jobBatchInfo=e.data,t.queryParam={groupName:t.jobBatchInfo.groupName,taskBatchId:a},t.$refs.JobTaskListRef.refreshTable(t.queryParam)})):this.$router.push({path:"/404"})},methods:{jobTaskList:s["h"],parseDate:function(t){return c()(t).format("YYYY-MM-DD HH:mm:ss")}}},l=b,f=e("2877"),h=Object(f["a"])(l,o,r,!1,null,"1a941578",null);a["default"]=h.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-74bac939"],{"9141d":function(t,e,a){"use strict";a.r(e);var o=function(){var t=this,e=t._self._c;return e("a-card",{attrs:{bordered:!1}},[e("div",{staticClass:"table-page-search-wrapper"},[e("a-form",{attrs:{layout:"inline"}},[e("a-row",{attrs:{gutter:48}},[[e("a-col",{attrs:{md:8,sm:24}},[e("a-form-item",{attrs:{label:"组名称"}},[e("a-input",{attrs:{placeholder:"请输入组名称",allowClear:""},model:{value:t.queryParam.groupName,callback:function(e){t.$set(t.queryParam,"groupName",e)},expression:"queryParam.groupName"}})],1)],1)],e("a-col",{attrs:{md:t.advanced?24:8,sm:24}},[e("span",{staticClass:"table-page-search-submitButtons",style:t.advanced&&{float:"right",overflow:"hidden"}||{}},[e("a-button",{attrs:{type:"primary"},on:{click:function(e){return t.$refs.table.refresh(!0)}}},[t._v("查询")]),e("a-button",{staticStyle:{"margin-left":"8px"},on:{click:function(){return t.queryParam={}}}},[t._v("重置")])],1)])],2)],1)],1),e("s-table",{ref:"table",attrs:{size:"default",rowKey:"key",columns:t.columns,data:t.loadData,alert:t.options.alert,rowSelection:t.options.rowSelection,scroll:{x:1600}},scopedSlots:t._u([{key:"serial",fn:function(a,o,n){return e("span",{},[t._v(" "+t._s(n+1)+" ")])}},{key:"contextPath",fn:function(a,o){return e("span",{},[1===o.nodeType?e("div",[t._v(" Path: "),e("a-tag",{attrs:{color:"#108ee9"}},[t._v(" "+t._s(a)+" ")])],1):e("div",[t._v(" Bucket: "),e("a-popover",{attrs:{placement:"topLeft"}},[e("template",{slot:"content"},t._l(o.consumerBuckets,(function(a){return e("a-tag",{key:a,staticStyle:{"margin-bottom":"16px"},attrs:{color:"pink"}},[t._v(" "+t._s(a)+" ")])})),1),e("template",{slot:"title"},[e("span",[t._v("Bucket列表")])]),t._l(5,(function(a){return e("a-tag",{key:a,staticStyle:{"margin-bottom":"16px"},attrs:{color:"pink"}},[t._v(" "+t._s(o.consumerBuckets[a-1])+" ")])})),o.consumerBuckets.length>5?e("a-tag",{staticStyle:{"margin-bottom":"16px"},attrs:{color:"pink"}},[t._v(" ... ")]):t._e()],2)],1)])}}])})],1)},n=[],r=a("c1df"),s=a.n(r),l=a("0fea"),c=a("2af9"),i={name:"PodList",components:{STable:c["j"]},data:function(){var t=this;return{advanced:!1,queryParam:{},columns:[{title:"#",scopedSlots:{customRender:"serial"},width:"6%"},{title:"类型",dataIndex:"nodeType",customRender:function(e){return t.nodeType[e]},width:"8%"},{title:"组名称",dataIndex:"groupName",width:"10%"},{title:"PodId",dataIndex:"hostId",width:"18%"},{title:"IP",dataIndex:"hostIp",width:"12%"},{title:"Port",dataIndex:"hostPort",width:"8%"},{title:"路径/组",dataIndex:"contextPath",scopedSlots:{customRender:"contextPath"},ellipsis:!0,width:"22%"},{title:"更新时间",dataIndex:"updateDt",sorter:!0,customRender:function(t){return s()(t).format("YYYY-MM-DD HH:mm:ss")}}],loadData:function(e){return Object(l["D"])(Object.assign(e,t.queryParam)).then((function(t){return t}))},selectedRowKeys:[],selectedRows:[],options:{alert:{show:!0,clear:function(){t.selectedRowKeys=[]}},rowSelection:{selectedRowKeys:this.selectedRowKeys,onChange:this.onSelectChange}},nodeType:{1:"客户端",2:"服务端"}}},methods:{}},d=i,u=a("2877"),p=Object(u["a"])(d,o,n,!1,null,"38a41799",null);e["default"]=p.exports}}]);

View File

@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-74bac939"],{"9141d":function(t,e,a){"use strict";a.r(e);var o=function(){var t=this,e=t._self._c;return e("a-card",{attrs:{bordered:!1}},[e("div",{staticClass:"table-page-search-wrapper"},[e("a-form",{attrs:{layout:"inline"}},[e("a-row",{attrs:{gutter:48}},[[e("a-col",{attrs:{md:8,sm:24}},[e("a-form-item",{attrs:{label:"组名称"}},[e("a-input",{attrs:{placeholder:"请输入组名称",allowClear:""},model:{value:t.queryParam.groupName,callback:function(e){t.$set(t.queryParam,"groupName",e)},expression:"queryParam.groupName"}})],1)],1)],e("a-col",{attrs:{md:t.advanced?24:8,sm:24}},[e("span",{staticClass:"table-page-search-submitButtons",style:t.advanced&&{float:"right",overflow:"hidden"}||{}},[e("a-button",{attrs:{type:"primary"},on:{click:function(e){return t.$refs.table.refresh(!0)}}},[t._v("查询")]),e("a-button",{staticStyle:{"margin-left":"8px"},on:{click:function(){return t.queryParam={}}}},[t._v("重置")])],1)])],2)],1)],1),e("s-table",{ref:"table",attrs:{size:"default",rowKey:"key",columns:t.columns,data:t.loadData,alert:t.options.alert,rowSelection:t.options.rowSelection,scroll:{x:1600}},scopedSlots:t._u([{key:"serial",fn:function(a,o,n){return e("span",{},[t._v(" "+t._s(n+1)+" ")])}},{key:"contextPath",fn:function(a,o){return e("span",{},[1===o.nodeType?e("div",[t._v(" 路径: "),e("a-tag",{attrs:{color:"#108ee9"}},[t._v(" "+t._s(a)+" ")])],1):e("div",[t._v(" 组: "),t._l(o.consumerGroup,(function(a){return e("a-tag",{key:a,staticStyle:{"margin-bottom":"16px"},attrs:{color:"pink"}},[t._v(" "+t._s(a)+" ")])}))],2)])}}])})],1)},n=[],r=a("c1df"),s=a.n(r),l=a("0fea"),c=a("2af9"),d={name:"PodList",components:{STable:c["j"]},data:function(){var t=this;return{advanced:!1,queryParam:{},columns:[{title:"#",scopedSlots:{customRender:"serial"},width:"6%"},{title:"类型",dataIndex:"nodeType",customRender:function(e){return t.nodeType[e]},width:"8%"},{title:"组名称",dataIndex:"groupName",width:"10%"},{title:"PodId",dataIndex:"hostId",width:"18%"},{title:"IP",dataIndex:"hostIp",width:"12%"},{title:"Port",dataIndex:"hostPort",width:"8%"},{title:"路径/组",dataIndex:"contextPath",scopedSlots:{customRender:"contextPath"},width:"22%"},{title:"更新时间",dataIndex:"updateDt",sorter:!0,customRender:function(t){return s()(t).format("YYYY-MM-DD HH:mm:ss")}}],loadData:function(e){return Object(l["D"])(Object.assign(e,t.queryParam)).then((function(t){return t}))},selectedRowKeys:[],selectedRows:[],options:{alert:{show:!0,clear:function(){t.selectedRowKeys=[]}},rowSelection:{selectedRowKeys:this.selectedRowKeys,onChange:this.onSelectChange}},nodeType:{1:"客户端",2:"服务端"}}},methods:{}},i=d,u=a("2877"),p=Object(u["a"])(i,o,n,!1,null,"1902f2a8",null);e["default"]=p.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -37,7 +37,6 @@ logging:
config: classpath:logback-boot.xml config: classpath:logback-boot.xml
easy-retry: easy-retry:
last-days: 30 # 拉取重试数据的天数
retry-pull-page-size: 100 # 拉取重试数据的每批次的大小 retry-pull-page-size: 100 # 拉取重试数据的每批次的大小
netty-port: 1788 # 服务端netty端口 netty-port: 1788 # 服务端netty端口
total-partition: 2 # 重试和死信表的分区总数 total-partition: 2 # 重试和死信表的分区总数

View File

@ -1,6 +1,7 @@
package com.aizuda.easy.retry.server.web.controller; package com.aizuda.easy.retry.server.web.controller;
import com.aizuda.easy.retry.server.common.cache.CacheConsumerGroup; import com.aizuda.easy.retry.server.common.cache.CacheConsumerGroup;
import com.aizuda.easy.retry.server.common.dto.DistributeInstance;
import com.aizuda.easy.retry.server.web.model.base.PageResult; import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.ServerNodeQueryVO; import com.aizuda.easy.retry.server.web.model.request.ServerNodeQueryVO;
import com.aizuda.easy.retry.server.web.model.response.ActivePodQuantityResponseVO; import com.aizuda.easy.retry.server.web.model.response.ActivePodQuantityResponseVO;
@ -77,9 +78,9 @@ public class DashBoardController {
return dashBoardService.pods(serverNodeQueryVO); return dashBoardService.pods(serverNodeQueryVO);
} }
@GetMapping("/consumer/group") @GetMapping("/consumer/bucket")
public Set<String> allConsumerGroupName() { public Set<Integer> allConsumerGroupName() {
return CacheConsumerGroup.getAllConsumerGroupName(); return DistributeInstance.INSTANCE.getConsumerBucket();
} }
} }

View File

@ -29,6 +29,8 @@ public class SceneConfigResponseVO {
private Long deadlineRequest; private Long deadlineRequest;
private Integer executorTimeout;
private LocalDateTime createDt; private LocalDateTime createDt;
private LocalDateTime updateDt; private LocalDateTime updateDt;

View File

@ -31,5 +31,5 @@ public class ServerNodeResponseVO {
private String extAttrs; private String extAttrs;
private Set<String> consumerGroup; private Set<Integer> consumerBuckets;
} }

View File

@ -5,6 +5,7 @@ import com.aizuda.easy.retry.common.core.log.LogUtils;
import com.aizuda.easy.retry.common.core.model.Result; import com.aizuda.easy.retry.common.core.model.Result;
import com.aizuda.easy.retry.common.core.util.JsonUtil; import com.aizuda.easy.retry.common.core.util.JsonUtil;
import com.aizuda.easy.retry.server.common.cache.CacheConsumerGroup; import com.aizuda.easy.retry.server.common.cache.CacheConsumerGroup;
import com.aizuda.easy.retry.server.common.dto.DistributeInstance;
import com.aizuda.easy.retry.server.common.dto.ServerNodeExtAttrs; import com.aizuda.easy.retry.server.common.dto.ServerNodeExtAttrs;
import com.aizuda.easy.retry.server.common.register.ServerRegister; import com.aizuda.easy.retry.server.common.register.ServerRegister;
import com.aizuda.easy.retry.server.web.service.convert.DispatchQuantityResponseVOConverter; import com.aizuda.easy.retry.server.web.service.convert.DispatchQuantityResponseVOConverter;
@ -60,7 +61,7 @@ import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
public class DashBoardServiceImpl implements DashBoardService { public class DashBoardServiceImpl implements DashBoardService {
public static final String URL = "http://{0}:{1}/dashboard/consumer/group"; public static final String URL = "http://{0}:{1}/dashboard/consumer/bucket";
@Autowired @Autowired
private RetryTaskLogMapper retryTaskLogMapper; private RetryTaskLogMapper retryTaskLogMapper;
@ -200,7 +201,7 @@ public class DashBoardServiceImpl implements DashBoardService {
// 若是本地节点则直接从缓存中取 // 若是本地节点则直接从缓存中取
if (ServerRegister.CURRENT_CID.equals(serverNodeResponseVO.getHostId())) { if (ServerRegister.CURRENT_CID.equals(serverNodeResponseVO.getHostId())) {
serverNodeResponseVO.setConsumerGroup(CacheConsumerGroup.getAllConsumerGroupName()); serverNodeResponseVO.setConsumerBuckets(DistributeInstance.INSTANCE.getConsumerBucket());
continue; continue;
} }
@ -216,15 +217,14 @@ public class DashBoardServiceImpl implements DashBoardService {
// 从远程节点取 // 从远程节点取
String format = MessageFormat String format = MessageFormat
.format(URL, serverNodeResponseVO.getHostIp(), serverNodeExtAttrs.getWebPort().toString()); .format(URL, serverNodeResponseVO.getHostIp(), serverNodeExtAttrs.getWebPort().toString());
Result<List<String>> result = restTemplate.getForObject(format, Result.class); Result<List<Integer>> result = restTemplate.getForObject(format, Result.class);
List<String> data = result.getData(); List<Integer> data = result.getData();
if (!CollectionUtils.isEmpty(data)) { if (!CollectionUtils.isEmpty(data)) {
serverNodeResponseVO.setConsumerGroup(new HashSet<>(data)); serverNodeResponseVO.setConsumerBuckets(new HashSet<>(data));
} }
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(log, "Failed to retrieve consumer group for node [{}:{}].", serverNodeResponseVO.getHostIp(), serverNodeExtAttrs.getWebPort()); LogUtils.error(log, "Failed to retrieve consumer group for node [{}:{}].", serverNodeResponseVO.getHostIp(), serverNodeExtAttrs.getWebPort());
serverNodeResponseVO.setConsumerGroup(Sets.newHashSet("获取数据异常"));
} }
} }

View File

@ -58,32 +58,6 @@ export const asyncRouterMap = [
redirect: '/retry/list', redirect: '/retry/list',
meta: { title: '重试任务管理', icon: 'schedule', permission: ['retryTask'] }, meta: { title: '重试任务管理', icon: 'schedule', permission: ['retryTask'] },
children: [ children: [
{
path: '/retry/scene/list',
name: 'SceneList',
component: () => import('@/views/task/SceneList'),
meta: { title: '场景列表', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/scene/config',
name: 'SceneFrom',
hidden: true,
component: () => import('@/views/task/form/SceneFrom'),
meta: { title: '场景配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/notify/list',
name: 'NotifyList',
component: () => import('@/views/task/NotifyList'),
meta: { title: '通知配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/notify/config',
name: 'NotifyFrom',
hidden: true,
component: () => import('@/views/task/form/NotifyFrom'),
meta: { title: '通知配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{ {
path: '/retry/list', path: '/retry/list',
name: 'RetryTaskList', name: 'RetryTaskList',
@ -122,6 +96,32 @@ export const asyncRouterMap = [
hidden: true, hidden: true,
component: () => import('@/views/task/RetryLogInfo'), component: () => import('@/views/task/RetryLogInfo'),
meta: { title: '重试日志详情', icon: 'profile', permission: ['retryLog'] } meta: { title: '重试日志详情', icon: 'profile', permission: ['retryLog'] }
},
{
path: '/retry/scene/list',
name: 'SceneList',
component: () => import('@/views/task/SceneList'),
meta: { title: '场景列表', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/scene/config',
name: 'SceneFrom',
hidden: true,
component: () => import('@/views/task/form/SceneFrom'),
meta: { title: '场景配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/notify/list',
name: 'NotifyList',
component: () => import('@/views/task/NotifyList'),
meta: { title: '通知配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
},
{
path: '/retry/notify/config',
name: 'NotifyFrom',
hidden: true,
component: () => import('@/views/task/form/NotifyFrom'),
meta: { title: '通知配置', icon: 'profile', keepAlive: true, permission: ['retryTask'] }
} }
] ]
}, },

View File

@ -36,17 +36,30 @@
</span> </span>
<span slot="contextPath" slot-scope="text, record"> <span slot="contextPath" slot-scope="text, record">
<div v-if="record.nodeType === 1"> <div v-if="record.nodeType === 1">
路径: Path:
<a-tag color="#108ee9" > <a-tag color="#108ee9" >
{{ text }} {{ text }}
</a-tag> </a-tag>
</div> </div>
<div v-else> <div v-else>
: Bucket:
<a-tag color="pink" v-for="item in record.consumerGroup" :key="item" style="margin-bottom: 16px"> <a-popover placement="topLeft">
{{ item }} <template slot="content">
</a-tag> <a-tag color="pink" v-for="item in record.consumerBuckets" :key="item" style="margin-bottom: 16px">
{{ item }}
</a-tag>
</template>
<template slot="title">
<span>Bucket列表</span>
</template>
<a-tag color="pink" v-for="item in 5" :key="item" style="margin-bottom: 16px">
{{ record.consumerBuckets[item-1] }}
</a-tag>
<a-tag color="pink" style="margin-bottom: 16px" v-if="record.consumerBuckets.length > 5">
...
</a-tag>
</a-popover>
</div> </div>
</span> </span>
@ -107,6 +120,7 @@ export default {
title: '路径/组', title: '路径/组',
dataIndex: 'contextPath', dataIndex: 'contextPath',
scopedSlots: { customRender: 'contextPath' }, scopedSlots: { customRender: 'contextPath' },
ellipsis: true,
width: '22%' width: '22%'
}, },
{ {
@ -118,7 +132,6 @@ export default {
], ],
// Promise // Promise
loadData: parameter => { loadData: parameter => {
console.log('loadData.parameter', parameter)
return pods(Object.assign(parameter, this.queryParam)) return pods(Object.assign(parameter, this.queryParam))
.then(res => { .then(res => {
return res return res

View File

@ -48,7 +48,7 @@
:data="loadData" :data="loadData"
:alert="options.alert" :alert="options.alert"
:rowSelection="options.rowSelection" :rowSelection="options.rowSelection"
:scroll="{ x: 1500 }" :scroll="{ x: 2000 }"
> >
<span slot="serial" slot-scope="text, record"> <span slot="serial" slot-scope="text, record">
{{ record.id }} {{ record.id }}
@ -64,7 +64,18 @@
</a-tag> </a-tag>
</span> </span>
<span slot="triggerInterval" slot-scope="text"> <span slot="triggerInterval" slot-scope="text">
{{ text ? text : '10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h' }} <a-tooltip>
<template slot="title">
{{ text ? text : '10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h' }}
</template>
{{ text ? text : '10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h' }}
</a-tooltip>
</span>
<span slot="deadlineRequest" slot-scope="text">
{{ text }}(ms)
</span>
<span slot="executorTimeout" slot-scope="text">
{{ text }}(s)
</span> </span>
<span slot="action" slot-scope="record"> <span slot="action" slot-scope="record">
<template> <template>
@ -92,19 +103,19 @@ export default {
{ {
title: '场景名称', title: '场景名称',
dataIndex: 'sceneName', dataIndex: 'sceneName',
width: '15%' width: '10%'
}, },
{ {
title: '场景状态', title: '场景状态',
dataIndex: 'sceneStatus', dataIndex: 'sceneStatus',
width: '8%', width: '10%',
scopedSlots: { customRender: 'sceneStatus' } scopedSlots: { customRender: 'sceneStatus' }
}, },
{ {
title: '退避策略', title: '退避策略',
dataIndex: 'backOff', dataIndex: 'backOff',
key: 'backOff', key: 'backOff',
width: '12%', width: '10%',
scopedSlots: { customRender: 'backOff' } scopedSlots: { customRender: 'backOff' }
}, },
{ {
@ -114,6 +125,14 @@ export default {
width: '10%', width: '10%',
scopedSlots: { customRender: 'maxRetryCount' } scopedSlots: { customRender: 'maxRetryCount' }
}, },
{
title: '间隔时间',
dataIndex: 'triggerInterval',
key: 'triggerInterval',
ellipsis: true,
width: '10%',
scopedSlots: { customRender: 'triggerInterval' }
},
{ {
title: '调用链超时时间', title: '调用链超时时间',
dataIndex: 'deadlineRequest', dataIndex: 'deadlineRequest',
@ -122,18 +141,29 @@ export default {
scopedSlots: { customRender: 'deadlineRequest' } scopedSlots: { customRender: 'deadlineRequest' }
}, },
{ {
title: '间隔时间', title: '执行超时时间',
dataIndex: 'triggerInterval', dataIndex: 'executorTimeout',
key: 'triggerInterval', key: 'executorTimeout',
ellipsis: true, width: '10%',
width: '15%', scopedSlots: { customRender: 'executorTimeout' }
scopedSlots: { customRender: 'triggerInterval' } },
{
title: '创建时间',
dataIndex: 'createDt',
key: 'createDt',
width: '10%'
},
{
title: '更新时间',
dataIndex: 'updateDt',
key: 'updateDt',
width: '10%'
}, },
{ {
title: '描述', title: '描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
width: '18%', width: '10%',
scopedSlots: { customRender: 'description' } scopedSlots: { customRender: 'description' }
}, },
{ {
@ -148,44 +178,8 @@ export default {
pagination: {}, pagination: {},
backOffLabels: enums.backOffLabels, backOffLabels: enums.backOffLabels,
sceneStatus: enums.sceneStatus, sceneStatus: enums.sceneStatus,
triggerInterval: {
'1': {
placeholder: '',
tooltip: ''
},
'2': {
placeholder: '请输入固定间隔时间',
tooltip: '请输入固定间隔时间'
},
'3': {
placeholder: '请输入CRON表达式',
tooltip: '通过CRON表达式计算执行时间'
},
'4': {
placeholder: '请输入最大间隔时间',
tooltip: '随机生成范围在[0, x]内的延迟时间; 其中x代表最大间隔时间'
}
},
// / // /
advanced: false, advanced: false,
maxRetryCount: {
'1': {
placeholder: '请输入延迟等级(max:26)',
tooltip: '请输入延迟等级max:26)'
},
'2': {
placeholder: '请输入最大重试次数',
tooltip: '请输入最大重试次数'
},
'3': {
placeholder: '请输入最大重试次数',
tooltip: '请输入最大重试次数'
},
'4': {
placeholder: '请输入最大重试次数',
tooltip: '请输入最大重试次数'
}
},
queryParam: {}, queryParam: {},
loadData: (parameter) => { loadData: (parameter) => {
return getScenePage(Object.assign(parameter, this.queryParam)).then((res) => { return getScenePage(Object.assign(parameter, this.queryParam)).then((res) => {
@ -216,16 +210,6 @@ export default {
getAllGroupNameList().then((res) => { getAllGroupNameList().then((res) => {
this.groupNameList = res.data this.groupNameList = res.data
}) })
const groupName = this.$route.query.groupName
if (groupName) {
this.fetch({
groupName: groupName,
size: 6,
page: 1
}
)
}
}, },
methods: { methods: {
handleNew () { handleNew () {
@ -233,179 +217,6 @@ export default {
}, },
handleEdit (record) { handleEdit (record) {
this.$router.push({ path: '/retry/scene/config', query: { id: record.id } }) this.$router.push({ path: '/retry/scene/config', query: { id: record.id } })
},
reset () {
this.formData = []
this.data = []
const groupName = this.$route.query.groupName
if (groupName) {
this.fetch({
groupName: groupName,
size: 6,
page: 1
}
)
}
},
handleTableChange (pagination, filters, sorter) {
console.log(pagination)
const pager = { ...this.pagination }
pager.current = pagination.current
this.pagination = pager
this.fetch({
groupName: this.$route.query.groupName,
size: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters
})
},
queryChange () {
this.fetch({
groupName: this.$route.query.groupName,
size: 6,
page: 1,
sceneName: this.queryParam.sceneName
}
)
},
fetch (params = {}) {
this.loading = true
getScenePage(params).then(res => {
this.data = []
res.data.map(record => {
this.loading = false
const { id, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = record
this.data.push({
key: id,
sceneName: sceneName,
sceneStatus: sceneStatus.toString(),
maxRetryCount: maxRetryCount,
backOff: backOff.toString(),
triggerInterval: triggerInterval,
description: description,
deadlineRequest: deadlineRequest,
editable: false,
isNew: false
})
})
const pagination = { ...this.pagination }
pagination.pageSize = res.size
pagination.current = res.page
pagination.total = res.total
this.pagination = pagination
})
},
remove (delKey) {
const delData = this.data.find(item => item.key === delKey)
const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = delData
this.formData.push({
key: key,
sceneName: sceneName,
sceneStatus: sceneStatus,
maxRetryCount: maxRetryCount,
backOff: backOff,
triggerInterval: triggerInterval,
deadlineRequest: deadlineRequest,
description: description,
isDeleted: 1
})
const newData = this.data.filter(item => item.key !== delKey)
this.data = newData
},
saveRow (record) {
this.memberLoading = true
const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = record
if (!sceneName || !sceneStatus || !maxRetryCount || !backOff || (backOff === '1' ? false : !triggerInterval)) {
this.memberLoading = false
this.$message.error('请填写完整成员信息。')
return
}
const regex = /^[A-Za-z0-9_]{1,64}$/
if (!regex.test(sceneName)) {
this.memberLoading = false
this.$message.error('场景名称: 仅支持长度为:1~64位字符.格式为:数字、字母、下划线。')
return
}
if (description.length > 256) {
this.memberLoading = false
this.$message.error('描述: 仅支持长度为:1~256位字符')
return
}
if ((backOff === '2' || backOff === '4') && triggerInterval < 10) {
this.memberLoading = false
this.$message.error('描述: 间隔时间最小为10秒')
return
}
const target = this.formData.find(item => key === item.key)
if (!target) {
this.formData.push({
key: key,
sceneName: sceneName,
sceneStatus: sceneStatus,
maxRetryCount: maxRetryCount,
backOff: backOff,
triggerInterval: triggerInterval,
description: description,
deadlineRequest: deadlineRequest,
isDeleted: 0
})
}
new Promise((resolve) => {
setTimeout(() => {
resolve({ loop: false })
}, 200)
}).then(() => {
const target = this.data.find(item => item.key === key)
target.editable = false
target.isNew = false
this.memberLoading = false
this.$message.warning('请点击右下角提交按钮以保存所有页面数据')
})
},
toggle (key) {
const target = this.data.find(item => item.key === key)
target._originalData = { ...target }
target.editable = !target.editable
},
getRowByKey (key, newData) {
const data = this.data
return (newData || data).find(item => item.key === key)
},
cancel (key) {
const target = this.data.find(item => item.key === key)
Object.keys(target).forEach(key => { target[key] = target._originalData[key] })
target._originalData = undefined
},
handleChange (value, key, column) {
if (column === 'backOff') {
switch (value) {
case '1':
this.triggerIntervalDisabled = true
this.max = 26
break
default:
this.triggerIntervalDisabled = false
this.max = 99999
}
}
const newData = [...this.data]
const target = newData.find(item => key === item.key)
if (target) {
target[column] = value
this.data = newData
}
} }
} }
} }

View File

@ -118,8 +118,6 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="8" :md="24" :sm="24"> <a-col :lg="8" :md="24" :sm="24">
<a-form-item label="超时时间(秒)"> <a-form-item label="超时时间(秒)">
<a-input-number <a-input-number
@ -145,12 +143,14 @@
v-decorator="[ v-decorator="[
'maxRetryCount', 'maxRetryCount',
{ {
initialValue: '3', initialValue: '16',
rules: [{ required: true, message: '请输入最大重试次数'}] rules: [{ required: true, message: '请输入最大重试次数'}]
} }
]" /> ]" />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="8" :md="24" :sm="24"> <a-col :lg="8" :md="24" :sm="24">
<a-form-item label="调用链超时时间(毫秒)"> <a-form-item label="调用链超时时间(毫秒)">
<a-input-number <a-input-number
@ -296,7 +296,7 @@ export default {
}).then(() => { }).then(() => {
const formData = pick(data, [ const formData = pick(data, [
'id', 'sceneName', 'groupName', 'sceneStatus', 'deadlineRequest', 'maxRetryCount', 'description', 'id', 'sceneName', 'groupName', 'sceneStatus', 'deadlineRequest', 'maxRetryCount', 'description',
'backOff', 'triggerInterval']) 'backOff', 'triggerInterval', 'executorTimeout'])
formData.sceneStatus = formData.sceneStatus.toString() formData.sceneStatus = formData.sceneStatus.toString()
formData.backOff = formData.backOff.toString() formData.backOff = formData.backOff.toString()
this.backOff = formData.backOff this.backOff = formData.backOff