feat: 2.2.0

1. 死信队列支持批量回滚和批量删除
This commit is contained in:
byteblogs168 2023-08-10 09:11:29 +08:00
parent 2bc581fbfe
commit afc10140cc
25 changed files with 97 additions and 36 deletions

View File

@ -2,17 +2,14 @@ package com.aizuda.easy.retry.template.datasource.access.task;
import com.aizuda.easy.retry.template.datasource.enums.DbTypeEnum;
import com.aizuda.easy.retry.template.datasource.enums.OperationTypeEnum;
import com.aizuda.easy.retry.template.datasource.exception.EasyRetryDatasourceException;
import com.aizuda.easy.retry.template.datasource.persistence.mapper.RetryDeadLetterMapper;
import com.aizuda.easy.retry.template.datasource.persistence.po.RetryDeadLetter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -29,7 +26,7 @@ public class RetryDeadLetterTaskAccess extends AbstractTaskAccess<RetryDeadLett
@Override
public boolean supports(String operationType) {
DbTypeEnum dbType = getDbType();
return OperationTypeEnum.RETRY_TASK.name().equals(operationType)
return OperationTypeEnum.RETRY_DEAD_LETTER.name().equals(operationType)
&& ALLOW_DB.contains(dbType.getDb());
}

View File

@ -18,7 +18,7 @@ public interface RetryDeadLetterService {
RetryDeadLetterResponseVO getRetryDeadLetterById(String groupName, Long id);
boolean rollback(BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO);
int rollback(BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO);
boolean batchDelete(BatchDeleteRetryDeadLetterVO deadLetterVO);
int batchDelete(BatchDeleteRetryDeadLetterVO deadLetterVO);
}

View File

@ -2,6 +2,7 @@ package com.aizuda.easy.retry.server.service.impl;
import cn.hutool.core.lang.Assert;
import com.aizuda.easy.retry.common.core.enums.RetryStatusEnum;
import com.aizuda.easy.retry.server.enums.TaskTypeEnum;
import com.aizuda.easy.retry.server.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.service.RetryDeadLetterService;
import com.aizuda.easy.retry.server.service.convert.RetryDeadLetterResponseVOConverter;
@ -90,7 +91,7 @@ public class RetryDeadLetterServiceImpl implements RetryDeadLetterService {
@Override
@Transactional
public boolean rollback(BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO) {
public int rollback(BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO) {
String groupName = rollBackRetryDeadLetterVO.getGroupName();
List<Long> ids = rollBackRetryDeadLetterVO.getIds();
@ -103,9 +104,10 @@ public class RetryDeadLetterServiceImpl implements RetryDeadLetterService {
List<RetryTask> waitRollbackList = new ArrayList<>();
for (RetryDeadLetter retryDeadLetter : retryDeadLetterList) {
RetryTask retryTask = RetryTaskConverter.INSTANCE.toRetryTask(retryDeadLetter);
retryTask.setRetryStatus(RetryStatusEnum.RUNNING.getStatus());
retryTask.setTaskType(TaskTypeEnum.RETRY.getType());
retryTask.setNextTriggerAt(WaitStrategies.randomWait(1, TimeUnit.SECONDS, 60, TimeUnit.SECONDS).computeRetryTime(null));
retryTask.setCreateDt(LocalDateTime.now());
retryTask.setUpdateDt(LocalDateTime.now());
waitRollbackList.add(retryTask);
}
@ -118,7 +120,7 @@ public class RetryDeadLetterServiceImpl implements RetryDeadLetterService {
.in(RetryDeadLetter::getId, waitDelRetryDeadLetterIdSet)), () -> new EasyRetryServerException("删除死信队列数据失败"))
;
// 日志的状态
// 日志的状态
RetryTaskLog retryTaskLog = new RetryTaskLog();
retryTaskLog.setRetryStatus(RetryStatusEnum.RUNNING.getStatus());
@ -126,15 +128,15 @@ public class RetryDeadLetterServiceImpl implements RetryDeadLetterService {
int update = retryTaskLogMapper.update(retryTaskLog, new LambdaUpdateWrapper<RetryTaskLog>()
.in(RetryTaskLog::getUniqueId, uniqueIdSet)
.eq(RetryTaskLog::getGroupName, groupName));
Assert.isTrue(update == uniqueIdSet.size(), () -> new EasyRetryServerException("回滚日志状态失败, 可能原因: 日志信息缺失"));
Assert.isTrue(update == uniqueIdSet.size(), () -> new EasyRetryServerException("回滚日志状态失败, 可能原因: 日志信息缺失或存在多个相同uniqueId"));
return true;
return update;
}
@Override
public boolean batchDelete(BatchDeleteRetryDeadLetterVO deadLetterVO) {
public int batchDelete(BatchDeleteRetryDeadLetterVO deadLetterVO) {
TaskAccess<RetryDeadLetter> retryDeadLetterAccess = accessTemplate.getRetryDeadLetterAccess();
return 1 == retryDeadLetterAccess.delete(deadLetterVO.getGroupName(),
return retryDeadLetterAccess.delete(deadLetterVO.getGroupName(),
new LambdaQueryWrapper<RetryDeadLetter>()
.eq(RetryDeadLetter::getGroupName, deadLetterVO.getGroupName())
.in(RetryDeadLetter::getId, deadLetterVO.getIds()));

View File

@ -40,14 +40,14 @@ public class RetryDeadLetterController {
}
@LoginRequired
@GetMapping("/batch/rollback")
public boolean rollback(@RequestBody @Validated BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO) {
@PostMapping("/batch/rollback")
public int rollback(@RequestBody @Validated BatchRollBackRetryDeadLetterVO rollBackRetryDeadLetterVO) {
return retryDeadLetterService.rollback(rollBackRetryDeadLetterVO);
}
@LoginRequired
@DeleteMapping("/batch")
public boolean deleteById(@RequestBody @Validated BatchDeleteRetryDeadLetterVO deadLetterVO) {
public int batchDelete(@RequestBody @Validated BatchDeleteRetryDeadLetterVO deadLetterVO) {
return retryDeadLetterService.batchDelete(deadLetterVO);
}
}

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="zh-cmn-Hans"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Easy-Retry</title><style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style><link href="/css/chunk-758b2aa4.1c9c785f.css" rel="prefetch"><link href="/css/chunk-ec9b3564.28cc4171.css" rel="prefetch"><link href="/css/chunk-ff9025ec.cd4961ff.css" rel="prefetch"><link href="/css/user.6ccd4506.css" rel="prefetch"><link href="/js/chunk-251479d0.76f3aeae.js" rel="prefetch"><link href="/js/chunk-2d0a4079.87c5b64e.js" rel="prefetch"><link href="/js/chunk-2d0b7230.9f49048b.js" rel="prefetch"><link href="/js/chunk-2d0c8f97.6e54ba8d.js" rel="prefetch"><link href="/js/chunk-2d0f085f.510926e6.js" rel="prefetch"><link href="/js/chunk-2d21a08f.21a838f2.js" rel="prefetch"><link href="/js/chunk-2d228eef.36e37160.js" rel="prefetch"><link href="/js/chunk-35f76107.25d7b4e6.js" rel="prefetch"><link href="/js/chunk-40597980.d8d9899e.js" rel="prefetch"><link href="/js/chunk-74bac939.044908f4.js" rel="prefetch"><link href="/js/chunk-758b2aa4.fa618117.js" rel="prefetch"><link href="/js/chunk-ec9b3564.18257885.js" rel="prefetch"><link href="/js/chunk-ff9025ec.da9ef935.js" rel="prefetch"><link href="/js/fail.da4ec1f3.js" rel="prefetch"><link href="/js/lang-zh-CN-account-settings.f8f25eaf.js" rel="prefetch"><link href="/js/lang-zh-CN-account.c724e71d.js" rel="prefetch"><link href="/js/lang-zh-CN-dashboard-analysis.98c2997a.js" rel="prefetch"><link href="/js/lang-zh-CN-dashboard.6a9e378b.js" rel="prefetch"><link href="/js/lang-zh-CN-form-basicForm.ff3088ac.js" rel="prefetch"><link href="/js/lang-zh-CN-form.cc39e450.js" rel="prefetch"><link href="/js/lang-zh-CN-global.bf0df5c8.js" rel="prefetch"><link href="/js/lang-zh-CN-menu.25425a62.js" rel="prefetch"><link href="/js/lang-zh-CN-result-fail.232762aa.js" rel="prefetch"><link href="/js/lang-zh-CN-result-success.3519c60c.js" rel="prefetch"><link href="/js/lang-zh-CN-result.32c5cf1c.js" rel="prefetch"><link href="/js/lang-zh-CN-setting.8c2ce690.js" rel="prefetch"><link href="/js/lang-zh-CN-user.81513cba.js" rel="prefetch"><link href="/js/lang-zh-CN.41562a6f.js" rel="prefetch"><link href="/js/user.ba1d9f2e.js" rel="prefetch"><link href="/css/app.b0a9877f.css" rel="preload" as="style"><link href="/css/chunk-vendors.5be6e05a.css" rel="preload" as="style"><link href="/js/app.a633babd.js" rel="preload" as="script"><link href="/js/chunk-vendors.87c1e94d.js" rel="preload" as="script"><link href="/css/chunk-vendors.5be6e05a.css" rel="stylesheet"><link href="/css/app.b0a9877f.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h2>Easy-Retry</h2><div class="loading-wrp"><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div><div style="display: flex; justify-content: center; align-items: center;">分布式重试服务平台</div></div></div><script src="//cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script><script src="//cdn.jsdelivr.net/npm/vue-router@3.5.1/dist/vue-router.min.js"></script><script src="//cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js"></script><script src="//cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script><script src="/js/chunk-vendors.87c1e94d.js"></script><script src="/js/app.a633babd.js"></script></body></html>
<!DOCTYPE html><html lang="zh-cmn-Hans"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Easy-Retry</title><style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style><link href="/css/chunk-758b2aa4.1c9c785f.css" rel="prefetch"><link href="/css/chunk-ec9b3564.28cc4171.css" rel="prefetch"><link href="/css/chunk-ff9025ec.cd4961ff.css" rel="prefetch"><link href="/css/user.6ccd4506.css" rel="prefetch"><link href="/js/chunk-251479d0.aeecc2be.js" rel="prefetch"><link href="/js/chunk-2d0a4079.c7758a6c.js" rel="prefetch"><link href="/js/chunk-2d0b7230.f12f5eb5.js" rel="prefetch"><link href="/js/chunk-2d0c8f97.ba54ec5e.js" rel="prefetch"><link href="/js/chunk-2d0f085f.b23ef12b.js" rel="prefetch"><link href="/js/chunk-2d21a08f.cfecde70.js" rel="prefetch"><link href="/js/chunk-2d228eef.882d082b.js" rel="prefetch"><link href="/js/chunk-35f76107.daaca925.js" rel="prefetch"><link href="/js/chunk-40597980.248c5471.js" rel="prefetch"><link href="/js/chunk-74bac939.e066e595.js" rel="prefetch"><link href="/js/chunk-758b2aa4.4fd41f21.js" rel="prefetch"><link href="/js/chunk-ec9b3564.be26cfb0.js" rel="prefetch"><link href="/js/chunk-ff9025ec.817a1d60.js" rel="prefetch"><link href="/js/fail.2f205d28.js" rel="prefetch"><link href="/js/lang-zh-CN-account-settings.f8f25eaf.js" rel="prefetch"><link href="/js/lang-zh-CN-account.c724e71d.js" rel="prefetch"><link href="/js/lang-zh-CN-dashboard-analysis.98c2997a.js" rel="prefetch"><link href="/js/lang-zh-CN-dashboard.6a9e378b.js" rel="prefetch"><link href="/js/lang-zh-CN-form-basicForm.ff3088ac.js" rel="prefetch"><link href="/js/lang-zh-CN-form.cc39e450.js" rel="prefetch"><link href="/js/lang-zh-CN-global.bf0df5c8.js" rel="prefetch"><link href="/js/lang-zh-CN-menu.25425a62.js" rel="prefetch"><link href="/js/lang-zh-CN-result-fail.232762aa.js" rel="prefetch"><link href="/js/lang-zh-CN-result-success.3519c60c.js" rel="prefetch"><link href="/js/lang-zh-CN-result.32c5cf1c.js" rel="prefetch"><link href="/js/lang-zh-CN-setting.8c2ce690.js" rel="prefetch"><link href="/js/lang-zh-CN-user.81513cba.js" rel="prefetch"><link href="/js/lang-zh-CN.41562a6f.js" rel="prefetch"><link href="/js/user.a4500874.js" rel="prefetch"><link href="/css/app.b0a9877f.css" rel="preload" as="style"><link href="/css/chunk-vendors.5be6e05a.css" rel="preload" as="style"><link href="/js/app.81a74a99.js" rel="preload" as="script"><link href="/js/chunk-vendors.87c1e94d.js" rel="preload" as="script"><link href="/css/chunk-vendors.5be6e05a.css" rel="stylesheet"><link href="/css/app.b0a9877f.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h2>Easy-Retry</h2><div class="loading-wrp"><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div><div style="display: flex; justify-content: center; align-items: center;">分布式重试服务平台</div></div></div><script src="//cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script><script src="//cdn.jsdelivr.net/npm/vue-router@3.5.1/dist/vue-router.min.js"></script><script src="//cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js"></script><script src="//cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script><script src="/js/chunk-vendors.87c1e94d.js"></script><script src="/js/app.81a74a99.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,11 +5,11 @@ spring:
active: dev
datasource:
name: easy_retry
url: jdbc:postgresql://localhost:5432/easy_retry
username: postgres
url: jdbc:mysql://localhost:3306/easy_retry?useSSL=false&characterEncoding=utf8&useUnicode=true
username: root
password: root
driver-class-name: org.postgresql.Driver
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
minimum-idle: 5
@ -47,7 +47,7 @@ easy-retry:
callback: # 回调配置
max-count: 288 #回调最大执行次数
trigger-interval: 900 #间隔时间
db-type: postgres
db-type: mysql #当前使用的数据库

View File

@ -24,8 +24,8 @@ const api = {
retryTaskLogById: '/retry-task-log/',
retryDeadLetterPage: '/retry-dead-letter/list',
retryDeadLetterById: '/retry-dead-letter/',
retryDeadLetterRollback: '/retry-dead-letter/rollback/',
deleteRetryDeadLetter: '/retry-dead-letter/',
retryDeadLetterRollback: '/retry-dead-letter/batch/rollback',
deleteRetryDeadLetter: '/retry-dead-letter/batch',
scenePageList: '/scene-config/page/list',
sceneList: '/scene-config/list',
notifyConfigList: '/notify-config/list',
@ -255,19 +255,19 @@ export function getRetryDeadLetterById (id, parameter) {
})
}
export function rollbackRetryDeadLetter (id, parameter) {
export function rollbackRetryDeadLetter (data) {
return request({
url: api.retryDeadLetterRollback + id,
method: 'get',
params: parameter
url: api.retryDeadLetterRollback,
method: 'post',
data
})
}
export function deleteRetryDeadLetter (id, parameter) {
export function deleteRetryDeadLetter (data) {
return request({
url: api.deleteRetryDeadLetter + id,
url: api.deleteRetryDeadLetter,
method: 'delete',
params: parameter
data
})
}

View File

@ -48,10 +48,20 @@
</a-form>
</div>
<div class="table-operator">
<a-dropdown v-action:edit v-if="selectedRowKeys.length > 0">
<a-menu slot="overlay" @click="onClick">
<a-menu-item key="1"><a-icon type="delete" />回滚</a-menu-item>
<a-menu-item key="2"><a-icon type="edit" />删除</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /> </a-button>
</a-dropdown>
</div>
<s-table
ref="table"
size="default"
rowKey="key"
:rowKey="(record) => record.id"
:columns="columns"
:data="loadData"
:alert="options.alert"
@ -107,7 +117,13 @@
import ATextarea from 'ant-design-vue/es/input/TextArea'
import AInput from 'ant-design-vue/es/input/Input'
import { getAllGroupNameList, getRetryDeadLetterPage, getSceneList, rollbackRetryDeadLetter, deleteRetryDeadLetter } from '@/api/manage'
import {
getAllGroupNameList,
getRetryDeadLetterPage,
getSceneList,
rollbackRetryDeadLetter,
deleteRetryDeadLetter
} from '@/api/manage'
import { STable } from '@/components'
import moment from 'moment'
@ -234,12 +250,12 @@ export default {
})
},
handleRollback (record) {
rollbackRetryDeadLetter(record.id, { groupName: record.groupName }).then(res => {
rollbackRetryDeadLetter({ groupName: record.groupName, ids: [ record.id ] }).then(res => {
this.$refs.table.refresh(true)
})
},
handleDelete (record) {
deleteRetryDeadLetter(record.id, { groupName: record.groupName }).then(res => {
deleteRetryDeadLetter({ groupName: record.groupName, ids: [ record.id ] }).then(res => {
this.$refs.table.refresh(true)
})
},
@ -248,6 +264,52 @@ export default {
},
handleInfo (record) {
this.$router.push({ path: '/retry-dead-letter/info', query: { id: record.id, groupName: record.groupName } })
},
onClick ({ key }) {
if (key === '1') {
this.handlerRollback()
return
}
if (key === '2') {
this.handlerDel()
}
},
handlerRollback () {
var that = this
this.$confirm({
title: '您要回滚这些数据吗?',
content: h => <div style="color:red;">请确认是否回滚!</div>,
onOk () {
rollbackRetryDeadLetter({ groupName: that.selectedRows[0].groupName, ids: that.selectedRowKeys }).then(res => {
that.$refs.table.refresh(true)
that.$message.success(`成功删除${res.data}条数据`)
that.selectedRowKeys = []
})
},
onCancel () {},
class: 'test'
})
},
handlerDel () {
var that = this
this.$confirm({
title: '您要删除这些数据吗?',
content: h => <div style="color:red;">删除后数据不可恢复请确认!</div>,
onOk () {
deleteRetryDeadLetter({ groupName: that.selectedRows[0].groupName, ids: that.selectedRowKeys }).then(res => {
that.$refs.table.refresh(true)
that.$message.success(`成功删除${res.data}条数据`)
that.selectedRowKeys = []
})
},
onCancel () {},
class: 'test'
})
},
onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows
}
}
}