feat: 1.1.0

1. 新增批量删除重试任务数据
2. 新增批量修改重试任务数据
3. 删除重试retry_task_x的biz_id唯一键索引
This commit is contained in:
byteblogs168 2023-04-30 22:59:47 +08:00
parent 22ea53b477
commit 2fe0753ce4
12 changed files with 271 additions and 72 deletions

View File

@ -42,8 +42,7 @@ CREATE TABLE `retry_dead_letter_0`
`ext_attrs` text NOT NULL COMMENT '扩展字段',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_group_name` (`group_name`) USING BTREE,
KEY `idx_scene_name` (`scene_name`) USING BTREE,
KEY `idx_group_name_scene_name` (`group_name`, `scene_name`),
KEY `idx_biz_id` (`biz_id`),
KEY `idx_biz_no` (`biz_no`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='重试死信队列'
@ -65,7 +64,9 @@ CREATE TABLE `retry_task_0`
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_id` (`biz_id`)
KEY `idx_group_name_scene_name` (`group_name`, `scene_name`),
KEY `idx_retry_status` (`retry_status`),
KEY `idx_biz_id` (`biz_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='重试表'
;
@ -84,8 +85,7 @@ CREATE TABLE `retry_task_log`
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`error_message` text NOT NULL COMMENT '异常信息',
PRIMARY KEY (`id`),
KEY `idx_group_name` (`group_name`),
KEY `idx_scene_name` (`scene_name`),
KEY `idx_group_name_scene_name` (`group_name`, `scene_name`),
KEY `idx_retry_status` (`retry_status`),
KEY `idx_biz_id` (`biz_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='重试日志表'

View File

@ -1,6 +1,7 @@
package com.aizuda.easy.retry.server.service;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.BatchDeleteRetryTaskVO;
import com.aizuda.easy.retry.server.web.model.request.GenerateRetryBizIdVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskQueryVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskUpdateStatusRequestVO;
@ -59,4 +60,13 @@ public interface RetryTaskService {
* @return 更新条数
*/
int updateRetryTaskExecutorName(RetryTaskUpdateExecutorNameRequestVO requestVO);
/**
* 批量删除重试数据
*
* @param requestVO 批量删除重试数据
* @return
*/
Integer deleteRetryTask(BatchDeleteRetryTaskVO requestVO);
}

View File

@ -15,6 +15,7 @@ import com.aizuda.easy.retry.server.service.convert.RetryTaskResponseVOConverter
import com.aizuda.easy.retry.server.support.handler.ClientNodeAllocateHandler;
import com.aizuda.easy.retry.server.support.strategy.WaitStrategies;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.BatchDeleteRetryTaskVO;
import com.aizuda.easy.retry.server.web.model.request.GenerateRetryBizIdVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskQueryVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskUpdateStatusRequestVO;
@ -177,21 +178,18 @@ public class RetryTaskServiceImpl implements RetryTaskService {
RetryTask retryTask = new RetryTask();
retryTask.setExecutorName(requestVO.getExecutorName());
retryTask.setRetryStatus(requestVO.getRetryStatus());
retryTask.setUpdateDt(LocalDateTime.now());
if (!CollectionUtils.isEmpty(requestVO.getIds())) {
// 根据重试数据id更新执行器名称
RequestDataHelper.setPartition(requestVO.getGroupName());
return retryTaskMapper
.update(retryTask, new LambdaUpdateWrapper<RetryTask>().in(RetryTask::getId, requestVO.getIds()));
}
// 更新组下面的场景对应的执行器名称
// 根据重试数据id更新执行器名称
RequestDataHelper.setPartition(requestVO.getGroupName());
return retryTaskMapper
.update(retryTask, new LambdaUpdateWrapper<RetryTask>()
.eq(RetryTask::getGroupName, requestVO.getGroupName())
.eq(RetryTask::getSceneName, requestVO.getSceneName())
);
.update(retryTask, new LambdaUpdateWrapper<RetryTask>().in(RetryTask::getId, requestVO.getIds()));
}
@Override
public Integer deleteRetryTask(final BatchDeleteRetryTaskVO requestVO) {
RequestDataHelper.setPartition(requestVO.getGroupName());
return retryTaskMapper.deleteBatchIds(requestVO.getIds());
}
}

View File

@ -4,6 +4,7 @@ import com.aizuda.easy.retry.common.core.model.Result;
import com.aizuda.easy.retry.server.service.RetryTaskService;
import com.aizuda.easy.retry.server.web.annotation.LoginRequired;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.BatchDeleteRetryTaskVO;
import com.aizuda.easy.retry.server.web.model.request.GenerateRetryBizIdVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskQueryVO;
import com.aizuda.easy.retry.server.web.model.request.RetryTaskUpdateStatusRequestVO;
@ -12,6 +13,7 @@ import com.aizuda.easy.retry.server.web.model.request.RetryTaskUpdateExecutorNam
import com.aizuda.easy.retry.server.web.model.response.RetryTaskResponseVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -68,8 +70,14 @@ public class RetryTaskController {
}
@LoginRequired
@PutMapping("/executor-name/batch")
@PutMapping("/batch")
public Integer updateRetryTaskExecutorName(@RequestBody @Validated RetryTaskUpdateExecutorNameRequestVO requestVO) {
return retryTaskService.updateRetryTaskExecutorName(requestVO);
}
@LoginRequired
@DeleteMapping("/batch")
public Integer deleteRetryTask(@RequestBody @Validated BatchDeleteRetryTaskVO requestVO) {
return retryTaskService.deleteRetryTask(requestVO);
}
}

View File

@ -0,0 +1,30 @@
package com.aizuda.easy.retry.server.web.model.request;
import com.aizuda.easy.retry.common.core.enums.RetryStatusEnum;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.List;
/**
* 批量删除重试数据
*
* @author: shuguang.zhang
* @date : 2023-04-30 22:30
*/
@Data
public class BatchDeleteRetryTaskVO {
/**
* 组名称
*/
@NotBlank(message = "groupName 不能为空")
private String groupName;
/**
* 重试表id
*/
@NotEmpty(message = "至少选择一项")
private List<Long> ids;
}

View File

@ -1,7 +1,9 @@
package com.aizuda.easy.retry.server.web.model.request;
import com.aizuda.easy.retry.common.core.enums.RetryStatusEnum;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.List;
@ -20,15 +22,20 @@ public class RetryTaskUpdateExecutorNameRequestVO {
@NotBlank(message = "groupName 不能为空")
private String groupName;
@NotBlank(message = "scene 不能为空")
private String sceneName;
@NotBlank(message = "executorName 不能为空")
/**
* 执行器名称
*/
private String executorName;
/**
* 重试状态 {@link RetryStatusEnum}
*/
private Integer retryStatus;
/**
* 重试表id
*/
@NotEmpty(message = "至少选择一项")
private List<Long> ids;
}

View File

@ -23,9 +23,10 @@
<fileNamePattern>log/sys.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小默认值是10MB,本篇设置为1KB只是为了演示 -->
<maxFileSize>1KB</maxFileSize>
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
@ -47,4 +48,4 @@
<logger name="com" level="DEBUG">
<appender-ref ref="syslog" />
</logger>
</configuration>
</configuration>

View File

@ -15,7 +15,8 @@ const api = {
retryTaskById: '/retry-task/',
saveRetryTask: '/retry-task',
bizIdGenerate: '/retry-task/generate/biz-id',
updateRetryTaskExecutorName: '/retry-task/executor-name/batch',
batchUpdate: '/retry-task/batch',
deleteRetryTask: '/retry-task/batch',
updateRetryTaskStatus: '/retry-task/status',
retryTaskLogPage: '/retry-task-log/list',
retryTaskLogById: '/retry-task-log/',
@ -39,6 +40,22 @@ const api = {
export default api
export function batchDelete (data) {
return request({
url: api.deleteRetryTask,
method: 'delete',
data
})
}
export function batchUpdate (data) {
return request({
url: api.batchUpdate,
method: 'put',
data
})
}
export function bizIdGenerate (data) {
return request({
url: api.bizIdGenerate,

View File

@ -53,8 +53,8 @@
:rowSelection="options.rowSelection"
:scroll="{ x: 2000 }"
>
<span slot="serial" slot-scope="text, record, index">
{{ index + 1 }}
<span slot="serial" slot-scope="text, record">
{{ record.id }}
</span>
<span slot="action" slot-scope="text, record">
<template>

View File

@ -5,7 +5,11 @@
<a-row :gutter="48">
<a-col :md="8" :sm="24">
<a-form-item label="组名称">
<a-select v-model="queryParam.groupName" placeholder="请输入组名称" @change="value => handleChange(value)">
<a-select
v-model="queryParam.groupName"
placeholder="请输入组名称"
@change="(value) => handleChange(value)"
>
<a-select-option v-for="item in groupNameList" :value="item" :key="item">{{ item }}</a-select-option>
</a-select>
</a-form-item>
@ -13,36 +17,43 @@
<a-col :md="8" :sm="24">
<a-form-item label="场景名称">
<a-select v-model="queryParam.sceneName" placeholder="请选择场景名称" allowClear>
<a-select-option v-for="item in sceneList" :value="item.sceneName" :key="item.sceneName"> {{ item.sceneName }}</a-select-option>
<a-select-option v-for="item in sceneList" :value="item.sceneName" :key="item.sceneName">
{{ item.sceneName }}</a-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="重试状态">
<a-select v-model="queryParam.retryStatus" placeholder="请选择状态" allowClear>
<a-select-option v-for="(value, key) in retryStatus" :value="key" :key="key"> {{ value }}</a-select-option>
<a-select-option v-for="(value, key) in retryStatus" :value="key" :key="key">
{{ value }}</a-select-option
>
</a-select>
</a-form-item>
</a-col>
<template v-if="advanced">
<a-col :md="8" :sm="24">
<a-form-item label="业务编号">
<a-input v-model="queryParam.bizNo" placeholder="请输入业务编号" allowClear/>
<a-input v-model="queryParam.bizNo" placeholder="请输入业务编号" allowClear />
</a-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="业务id">
<a-input v-model="queryParam.bizId" placeholder="请输入业务id" allowClear/>
<a-input v-model="queryParam.bizId" placeholder="请输入业务id" allowClear />
</a-form-item>
</a-col>
</template>
<a-col :md="!advanced && 8 || 24" :sm="24">
<span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} ">
<a-col :md="(!advanced && 8) || 24" :sm="24">
<span
class="table-page-search-submitButtons"
:style="(advanced && { float: 'right', overflow: 'hidden' }) || {}"
>
<a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button>
<a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
<a-button style="margin-left: 8px" @click="() => (queryParam = {})">重置</a-button>
<a @click="toggleAdvanced" style="margin-left: 8px">
{{ advanced ? '收起' : '展开' }}
<a-icon :type="advanced ? 'up' : 'down'"/>
<a-icon :type="advanced ? 'up' : 'down'" />
</a>
</span>
</a-col>
@ -51,15 +62,12 @@
</div>
<div class="table-operator">
<a-button type="primary" icon="plus" @click="handleNew()">新增</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<a-menu slot="overlay">
<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>
<!-- lock | unlock -->
<a-menu-item key="2"><a-icon type="lock" />锁定</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-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /> </a-button>
</a-dropdown>
</div>
@ -81,29 +89,30 @@
<a @click="handleInfo(record)">详情</a>
<a-divider type="vertical" />
<a @click="handleSuspend(record)" v-if="record.retryStatus === 0">暂停</a>
<a-divider type="vertical" v-if="record.retryStatus === 0"/>
<a-divider type="vertical" v-if="record.retryStatus === 0" />
<a @click="handleRecovery(record)" v-if="record.retryStatus === 3">恢复</a>
<a-divider type="vertical" v-if="record.retryStatus === 3"/>
<a-divider type="vertical" v-if="record.retryStatus === 3" />
<a @click="handleFinish(record)" v-if="record.retryStatus !== 1">完成</a>
<a-divider type="vertical" v-if="record.retryStatus !== 1"/>
<a-divider type="vertical" v-if="record.retryStatus !== 1" />
</template>
</span>
</s-table>
<SaveRetryTask ref="saveRetryTask"/>
<SaveRetryTask ref="saveRetryTask" @refreshTable="refreshTable"/>
<BatchUpdateRetryTaskInfo ref="batchUpdateRetryTaskInfo" @refreshTable="refreshTable"/>
</a-card>
</template>
<script>
import ATextarea from 'ant-design-vue/es/input/TextArea'
import AInput from 'ant-design-vue/es/input/Input'
//
import Edit from '@/views/list/table/Edit'
import { getAllGroupNameList, getRetryTaskPage, getSceneList, updateRetryTaskStatus } from '@/api/manage'
import { getAllGroupNameList, getRetryTaskPage, getSceneList, updateRetryTaskStatus, batchDelete } from '@/api/manage'
import { STable } from '@/components'
import moment from 'moment'
import SaveRetryTask from './form/SaveRetryTask'
import BatchUpdateRetryTaskInfo from './form/BatchUpdateRetryTaskInfo'
export default {
name: 'RetryTask',
@ -112,7 +121,8 @@ export default {
ATextarea,
Edit,
STable,
SaveRetryTask
SaveRetryTask,
BatchUpdateRetryTaskInfo
},
data () {
return {
@ -125,10 +135,10 @@ export default {
//
queryParam: {},
retryStatus: {
'0': '重试中',
'1': '重试完成',
'2': '最大次数',
'3': '暂停'
0: '重试中',
1: '重试完成',
2: '最大次数',
3: '暂停'
},
//
columns: [
@ -185,18 +195,22 @@ export default {
}
],
// Promise
loadData: parameter => {
return getRetryTaskPage(Object.assign(parameter, this.queryParam))
.then(res => {
return res
})
loadData: (parameter) => {
return getRetryTaskPage(Object.assign(parameter, this.queryParam)).then((res) => {
return res
})
},
selectedRowKeys: [],
selectedRows: [],
// custom table alert & rowSelection
options: {
alert: { show: true, clear: () => { this.selectedRowKeys = [] } },
alert: {
show: true,
clear: () => {
this.selectedRowKeys = []
}
},
rowSelection: {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
@ -208,11 +222,12 @@ export default {
}
},
created () {
getAllGroupNameList().then(res => {
getAllGroupNameList().then((res) => {
this.groupNameList = res.data
if (this.groupNameList !== null && this.groupNameList.length > 0) {
this.queryParam['groupName'] = this.groupNameList[0]
this.$refs.table.refresh(true)
this.handleChange(this.groupNameList[0])
}
})
},
@ -221,7 +236,7 @@ export default {
this.$refs.saveRetryTask.isShow(true, null)
},
handleChange (value) {
getSceneList({ 'groupName': value }).then(res => {
getSceneList({ groupName: value }).then((res) => {
this.sceneList = res.data
})
},
@ -231,11 +246,9 @@ export default {
handleInfo (record) {
this.$router.push({ path: '/retry-task/info', query: { id: record.id, groupName: record.groupName } })
},
handleOk (record) {
},
handleOk (record) {},
handleSuspend (record) {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 3 }).then(res => {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 3 }).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('暂停失败')
@ -246,7 +259,7 @@ export default {
})
},
handleRecovery (record) {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 0 }).then(res => {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 0 }).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('恢复失败')
@ -257,7 +270,7 @@ export default {
})
},
handleFinish (record) {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 1 }).then(res => {
updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 1 }).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('重试完成失败')
@ -267,8 +280,39 @@ export default {
}
})
},
refresh (v) {
refreshTable (v) {
this.$refs.table.refresh(true)
},
onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows
},
handlerDel () {
var that = this
this.$confirm({
title: '您要删除这些数据吗?',
content: h => <div style="color:red;">删除后数据不可恢复请确认!</div>,
onOk () {
batchDelete({ 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'
})
},
onClick ({ key }) {
if (key === '2') {
this.$refs.batchUpdateRetryTaskInfo.isShow(true, this.selectedRows, this.selectedRowKeys)
return
}
if (key === '1') {
this.handlerDel()
}
}
}
}

View File

@ -0,0 +1,84 @@
<template>
<div>
<a-modal :visible="visible" title="批量更新" @ok="handleOk" @cancel="visible = false" width="800px">
<a-form @submit="handleOk" :form="form" v-bind="formItemLayout">
<a-alert message="批量更新只根据选择的数据进行更新, 请操作前确认您的选择的数据是否正确?" banner />
<a-form-item label="执行器名称">
<a-input
v-decorator="['executorName', { rules: [{ required: false, message: '请输入执行器名称' }] }]"
name="executorName"
placeholder="请输入执行器名称"
/>
</a-form-item>
<a-form-item label="重试状态">
<a-select
placeholder="请选择重试状态"
v-decorator="['retryStatus', { rules: [{ required: false, message: '请选择重试状态' }] }]"
>
<a-select-option v-for="(value, key) in retryStatus" :value="key" :key="key"> {{ value }}</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { batchUpdate } from '@/api/manage'
export default {
name: 'BatchUpdateRetryTaskInfo',
props: {},
data () {
return {
visible: false,
form: this.$form.createForm(this),
formItemLayout: {
labelCol: { lg: { span: 6 }, sm: { span: 7 } },
wrapperCol: { lg: { span: 14 }, sm: { span: 17 } }
},
groupNameList: [],
sceneList: [],
retryStatus: {
0: '重试中',
1: '重试完成',
2: '最大次数',
3: '暂停'
},
groupName: '',
ids: []
}
},
methods: {
handleOk (e) {
console.log(e)
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
console.log(values)
if (values['executorName'] === undefined && values['retryStatus'] === undefined) {
this.$message.error('无需要更新的内容, 请填写任意一项')
return
}
values['groupName'] = this.groupName
values['ids'] = this.ids
batchUpdate(values).then((res) => {
this.$emit('refreshTable', 1)
this.form.resetFields()
this.$message.success('更新任务成功')
this.visible = false
})
}
})
},
isShow (visible, data, ids) {
this.visible = visible
this.groupName = data[0].groupName
this.ids = ids
}
}
}
</script>
<style scoped>
</style>

View File

@ -14,7 +14,7 @@
<a-form-item label="场景名称">
<a-select
placeholder="请选择场景名称"
v-decorator="['sceneName', { rules: [{ required: true, message: '请选择场景名称' }] }]" >
v-decorator="['sceneName', { rules: [{ required: true, message: '请选择场景名称' }] }]" >
<a-select-option v-for="item in sceneList" :value="item.sceneName" :key="item.sceneName">
{{ item.sceneName }}</a-select-option
>
@ -56,7 +56,7 @@
v-decorator="['retryStatus', { rules: [{ required: true, message: '请选择重试状态' }] }]"
>
<a-select-option v-for="(value, key) in retryStatus" :value="key" :key="key"> {{ value }}</a-select-option>
</a-select>
</a-select>
</a-form-item>
<a-form-item label="参数">
<a-textarea
@ -105,7 +105,7 @@ export default {
this.form.resetFields()
this.$message.success('新增任务成功')
this.visible = false
this.$emit('refresh', 1)
this.$emit('refreshTable', 1)
})
}
})