feat:2.4.0

1. 完成任务的新增更新删除
This commit is contained in:
byteblogs168 2023-10-15 23:18:21 +08:00
parent cdbaf8ee75
commit 27fffc4d89
21 changed files with 793 additions and 190 deletions

View File

@ -221,13 +221,13 @@ CREATE TABLE `job` (
`job_name` varchar(64) NOT NULL COMMENT '名称',
`args_str` text NOT NULL COMMENT '执行方法参数',
`args_type` tinyint(4) NOT NULL DEFAULT '' COMMENT '参数类型 ',
`ext_attrs` text NOT NULL COMMENT '扩展字段',
`ext_attrs` text COMMENT '扩展字段',
`next_trigger_at` datetime NOT NULL COMMENT '下次触发时间',
`job_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '重试状态 0、关闭、1、开启',
`task_type` varchar(255) DEFAULT NULL COMMENT '任务类型 1、集群 2、广播 3、切片',
`route_key` tinyint(4) NOT NULL DEFAULT '4' COMMENT '路由策略',
`executor_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '执行器类型',
`executor_name` varchar(255) DEFAULT NULL COMMENT '执行器名称',
`executor_info` varchar(255) DEFAULT NULL COMMENT '执行器名称',
`trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间',
`trigger_interval` varchar(255) NOT NULL COMMENT '间隔时长',
`block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞策略 1、丢弃 2、覆盖 3、并行',
@ -250,7 +250,6 @@ CREATE TABLE `job_log_message` (
`job_id` bigint(20) NOT NULL COMMENT '任务信息id',
`task_batch_id` bigint(20) NOT NULL COMMENT '任务批次id',
`task_id` bigint(20) NOT NULL COMMENT '调度任务id',
`client_address` varchar(255) DEFAULT NULL COMMENT '客户端地址',
`message` text NOT NULL COMMENT '调度信息',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`ext_attrs` varchar(256) NULL default '' COMMENT '扩展字段',
@ -263,9 +262,9 @@ CREATE TABLE `job_task` (
`job_id` bigint(20) NOT NULL COMMENT '任务信息id',
`task_batch_id` bigint(20) NOT NULL COMMENT '调度任务id',
`parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父执行器id',
`execute_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行的状态 0、失败 1、成功',
`task_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行的状态 0、失败 1、成功',
`retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
`client_id` varchar(255) DEFAULT NULL COMMENT '客户端地址',
`client_info` varchar(255) DEFAULT NULL COMMENT '客户端地址 clientId#ip:port',
`result_message` text NOT NULL COMMENT '执行结果',
`args_str` text NOT NULL COMMENT '执行方法参数',
`args_type` varchar(16) NOT NULL DEFAULT '' COMMENT '参数类型 text/json',
@ -279,7 +278,7 @@ CREATE TABLE `job_task_batch` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`job_id` bigint(20) NOT NULL COMMENT '任务id',
`task_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '任务状态 0、失败 1、成功',
`task_batch_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '任务批次状态 0、失败 1、成功',
`operation_reason` tinyint(4) NOT NULL DEFAULT '0' COMMENT '操作原因',
`execution_at` datetime DEFAULT NULL COMMENT '任务执行时间',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

View File

@ -0,0 +1,12 @@
package com.aizuda.easy.retry.template.datasource.persistence.dataobject;
import lombok.Data;
/**
* @author www.byteblogs.com
* @date 2023-10-15 23:03:01
* @since 2.4.0
*/
@Data
public class JobBatchResponseDO {
}

View File

@ -1,10 +1,13 @@
package com.aizuda.easy.retry.template.datasource.persistence.mapper;
import com.aizuda.easy.retry.template.datasource.persistence.dataobject.JobBatchResponseDO;
import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 调度任务 Mapper 接口
@ -16,4 +19,5 @@ import org.apache.ibatis.annotations.Param;
@Mapper
public interface JobTaskBatchMapper extends BaseMapper<JobTaskBatch> {
List<JobBatchResponseDO> selectJobBatchList();
}

View File

@ -12,5 +12,8 @@
<result column="update_dt" property="updateDt" />
<result column="deleted" property="deleted" />
</resultMap>
<select id="selectJobBatchList" resultType="com.aizuda.easy.retry.template.datasource.persistence.dataobject.JobBatchResponseDO">
SELECT a.*, b.job_name, b.task_type, b.block_strategy, b.trigger_type
FROM job_task_batch a join job b on a.job_id = b.id
</select>
</mapper>

View File

@ -34,8 +34,6 @@ import java.util.*;
@Slf4j
public class ShardingTaskGenerator extends AbstractJobTaskGenerator {
@Autowired
private JobMapper jobMapper;
@Autowired
protected ClientNodeAllocateHandler clientNodeAllocateHandler;
@Autowired

View File

@ -4,16 +4,12 @@ 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.JobQueryVO;
import com.aizuda.easy.retry.server.web.model.request.JobRequestVO;
import com.aizuda.easy.retry.server.web.model.request.JobUpdateJobStatusRequestVO;
import com.aizuda.easy.retry.server.web.model.response.JobResponseVO;
import com.aizuda.easy.retry.server.web.service.JobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -43,14 +39,26 @@ public class JobController {
@PostMapping
@LoginRequired
public Boolean saveJob(@RequestBody JobRequestVO jobRequestVO) {
public Boolean saveJob(@RequestBody @Validated JobRequestVO jobRequestVO) {
return jobService.saveJob(jobRequestVO);
}
@PutMapping
@LoginRequired
public Boolean updateJob(@RequestBody JobRequestVO jobRequestVO) {
public Boolean updateJob(@RequestBody @Validated JobRequestVO jobRequestVO) {
return jobService.updateJob(jobRequestVO);
}
@PutMapping("/status")
@LoginRequired
public Boolean updateJobStatus(@RequestBody @Validated JobUpdateJobStatusRequestVO jobRequestVO) {
return jobService.updateJobStatus(jobRequestVO);
}
@DeleteMapping("{id}")
@LoginRequired
public Boolean deleteJobById(@PathVariable("id") Long id) {
return jobService.deleteJobById(id);
}
}

View File

@ -29,6 +29,12 @@ public class JobRequestVO {
@NotBlank(message = "jobName 不能为空")
private String jobName;
/**
* 重试状态 0关闭1开启
*/
@NotNull(message = "jobStatus 不能为空")
private Integer jobStatus;
/**
* 执行方法参数
*/
@ -37,14 +43,14 @@ public class JobRequestVO {
/**
* 参数类型 text/json
*/
@NotNull(message = "argsType 不能为空")
// @NotNull(message = "argsType 不能为空")
private Integer argsType;
/**
* 执行器路由策略
*/
@NotNull(message = "routeKey 不能为空")
private String routeKey;
private Integer routeKey;
/**
* 执行器类型 1Java

View File

@ -0,0 +1,21 @@
package com.aizuda.easy.retry.server.web.model.request;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author www.byteblogs.com
* @date 2023-10-15 16:06:20
* @since 2.4.0
*/
@Data
public class JobUpdateJobStatusRequestVO {
@NotNull(message = "id 不能为空")
private Long id;
@NotNull(message = "jobStatus 不能为空")
private Integer jobStatus;
}

View File

@ -19,6 +19,11 @@ public class JobBatchResponseVO {
*/
private String groupName;
/**
* 名称
*/
private String jobName;
/**
* 任务信息id
*/
@ -43,4 +48,14 @@ public class JobBatchResponseVO {
* 操作原因
*/
private Integer operationReason;
/**
* 执行器类型 1Java
*/
private Integer executorType;
/**
* 执行器名称
*/
private String executorName;
}

View File

@ -3,6 +3,7 @@ package com.aizuda.easy.retry.server.web.service;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.JobQueryVO;
import com.aizuda.easy.retry.server.web.model.request.JobRequestVO;
import com.aizuda.easy.retry.server.web.model.request.JobUpdateJobStatusRequestVO;
import com.aizuda.easy.retry.server.web.model.response.JobResponseVO;
import java.util.List;
@ -21,4 +22,7 @@ public interface JobService {
boolean updateJob(JobRequestVO jobRequestVO);
Boolean updateJobStatus(JobUpdateJobStatusRequestVO jobRequestVO);
Boolean deleteJobById(Long id);
}

View File

@ -1,8 +1,11 @@
package com.aizuda.easy.retry.server.web.service.convert;
import com.aizuda.easy.retry.server.web.model.response.JobBatchResponseVO;
import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@ -19,5 +22,10 @@ public interface JobBatchResponseVOConverter {
List<JobBatchResponseVO> toJobBatchResponseVOs(List<JobTaskBatch> jobBatches);
JobBatchResponseVO toJobBatchResponseVO(JobTaskBatch jobBatch);
@Mappings({
@Mapping(source = "jobBatch.groupName", target = "groupName"),
@Mapping(source = "jobBatch.id", target = "id"),
@Mapping(source = "jobBatch.createDt", target = "createDt")
})
JobBatchResponseVO toJobBatchResponseVO(JobTaskBatch jobBatch, Job job);
}

View File

@ -1,11 +1,14 @@
package com.aizuda.easy.retry.server.web.service.impl;
import com.aizuda.easy.retry.common.core.enums.StatusEnum;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.JobBatchQueryVO;
import com.aizuda.easy.retry.server.web.model.response.JobBatchResponseVO;
import com.aizuda.easy.retry.server.web.service.JobBatchService;
import com.aizuda.easy.retry.server.web.service.convert.JobBatchResponseVOConverter;
import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
@ -25,6 +28,8 @@ public class JobBatchServiceImpl implements JobBatchService {
@Autowired
private JobTaskBatchMapper jobTaskBatchMapper;
@Autowired
private JobMapper jobMapper;
@Override
public PageResult<List<JobBatchResponseVO>> getJobBatchPage(final JobBatchQueryVO queryVO) {
@ -32,6 +37,8 @@ public class JobBatchServiceImpl implements JobBatchService {
PageDTO<JobTaskBatch> pageDTO = new PageDTO<>(queryVO.getPage(), queryVO.getSize());
LambdaQueryWrapper<JobTaskBatch> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(JobTaskBatch::getDeleted, StatusEnum.NO.getStatus());
if (Objects.nonNull(queryVO.getJobId())) {
queryWrapper.eq(JobTaskBatch::getJobId, queryVO.getJobId());
}
@ -47,6 +54,11 @@ public class JobBatchServiceImpl implements JobBatchService {
@Override
public JobBatchResponseVO getJobBatchDetail(final Long id) {
JobTaskBatch jobTaskBatch = jobTaskBatchMapper.selectById(id);
return JobBatchResponseVOConverter.INSTANCE.toJobBatchResponseVO(jobTaskBatch);
if (Objects.isNull(jobTaskBatch)) {
return null;
}
Job job = jobMapper.selectById(jobTaskBatch.getJobId());
return JobBatchResponseVOConverter.INSTANCE.toJobBatchResponseVO(jobTaskBatch, job);
}
}

View File

@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
/**
* @author: www.byteblogs.com
@ -31,6 +32,13 @@ public class JobLogServiceImpl implements JobLogService {
PageDTO<JobLogMessage> pageDTO = new PageDTO<>(queryVO.getPage(), queryVO.getSize());
LambdaQueryWrapper<JobLogMessage> queryWrapper = new LambdaQueryWrapper<>();
if (Objects.nonNull(queryVO.getJobId())) {
queryWrapper.eq(JobLogMessage::getJobId, queryVO.getJobId());
}
if (Objects.nonNull(queryVO.getTaskBatchId())) {
queryWrapper.eq(JobLogMessage::getTaskBatchId, queryVO.getTaskBatchId());
}
PageDTO<JobLogMessage> selectPage = jobLogMessageMapper.selectPage(pageDTO, queryWrapper);

View File

@ -2,10 +2,13 @@ package com.aizuda.easy.retry.server.web.service.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.aizuda.easy.retry.common.core.enums.StatusEnum;
import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.retry.task.support.strategy.WaitStrategies;
import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.JobQueryVO;
import com.aizuda.easy.retry.server.web.model.request.JobRequestVO;
import com.aizuda.easy.retry.server.web.model.request.JobUpdateJobStatusRequestVO;
import com.aizuda.easy.retry.server.web.model.response.JobResponseVO;
import com.aizuda.easy.retry.server.web.service.JobService;
import com.aizuda.easy.retry.server.web.service.convert.JobConverter;
@ -19,6 +22,7 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author www.byteblogs.com
@ -37,6 +41,7 @@ public class JobServiceImpl implements JobService {
PageDTO<Job> pageDTO = new PageDTO<>(queryVO.getPage(), queryVO.getSize());
LambdaQueryWrapper<Job> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Job::getDeleted, StatusEnum.NO.getStatus());
if (StrUtil.isNotBlank(queryVO.getGroupName())) {
queryWrapper.eq(Job::getGroupName, queryVO.getGroupName());
}
@ -66,6 +71,7 @@ public class JobServiceImpl implements JobService {
@Override
public boolean saveJob(JobRequestVO jobRequestVO) {
Job job = JobConverter.INSTANCE.toJob(jobRequestVO);
job.setNextTriggerAt(WaitStrategies.randomWait(1, TimeUnit.SECONDS, 60, TimeUnit.SECONDS).computeRetryTime(null));
return 1 == jobMapper.insert(job);
}
@ -76,4 +82,23 @@ public class JobServiceImpl implements JobService {
Job job = JobConverter.INSTANCE.toJob(jobRequestVO);
return 1 == jobMapper.updateById(job);
}
@Override
public Boolean updateJobStatus(JobUpdateJobStatusRequestVO jobRequestVO) {
Assert.notNull(jobRequestVO.getId(), () -> new EasyRetryServerException("id 不能为空"));
Assert.isTrue(1 == jobMapper.selectCount(new LambdaQueryWrapper<Job>().eq(Job::getId, jobRequestVO.getId())));
Job job = new Job();
job.setId(jobRequestVO.getId());
job.setJobStatus(jobRequestVO.getJobStatus());
return 1 == jobMapper.updateById(job);
}
@Override
public Boolean deleteJobById(Long id) {
Job job = new Job();
job.setId(id);
job.setDeleted(StatusEnum.YES.getStatus());
return 1 == jobMapper.updateById(job);
}
}

View File

@ -5,6 +5,8 @@ const jobApi = {
jobDetail: '/job/',
saveJob: '/job/',
updateJob: '/job/',
updateJobStatus: '/job/status',
delJob: '/job/',
// 任务批次
jobBatchList: '/job/batch/list',
@ -19,6 +21,21 @@ const jobApi = {
export default jobApi
export function delJob (id) {
return request({
url: jobApi.delJob + id,
method: 'delete'
})
}
export function updateJobStatus (data) {
return request({
url: jobApi.updateJobStatus,
method: 'put',
data
})
}
export function jobLogList (parameter) {
return request({
url: jobApi.jobLogList,

View File

@ -141,6 +141,13 @@ export const asyncRouterMap = [
component: () => import('@/views/job/JobInfo'),
meta: { title: '定时任务详情', icon: 'profile', permission: ['retryLog'] }
},
{
path: '/job/config',
name: 'JobFrom',
hidden: true,
component: () => import('@/views/job/from/JobFrom'),
meta: { title: '任务配置', icon: 'profile', permission: ['retryLog'] }
},
{
path: '/job/batch/list',
name: 'JobBatchList',

View File

@ -4,24 +4,37 @@
<div></div>
</page-header-wrapper>
<a-card :bordered="false" v-if="jobBatchInfo !==null ">
<a-descriptions title="" :column="5" bordered>
<a-descriptions title="" :column="3" bordered>
<a-descriptions-item label="组名称">
{{ jobBatchInfo.groupName }}
</a-descriptions-item>
<a-descriptions-item label="任务名称">
{{ jobBatchInfo.jobName }}
</a-descriptions-item>
<a-descriptions-item label="重试状态">
<a-descriptions-item label="状态">
<a-tag :color="taskStatus[jobBatchInfo.taskStatus].color">
{{ taskStatus[jobBatchInfo.taskStatus].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="更新时间">
{{ jobBatchInfo.updateDt }}
<a-descriptions-item label="执行器类型">
<a-tag :color="executorType[jobBatchInfo.executorType].color">
{{ executorType[jobBatchInfo.executorType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="执行器名称" span="3">
<a-descriptions-item label="操作原因">
<a-tag :color="operationReason[jobBatchInfo.operationReason].color">
{{ operationReason[jobBatchInfo.operationReason].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="开始执行时间">
{{ jobBatchInfo.executionAt }}
</a-descriptions-item>
<a-descriptions-item label="执行器名称" span="4">
{{ jobBatchInfo.executorName }}
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ jobBatchInfo.createDt }}
</a-descriptions-item>
</a-descriptions>
</a-card>
<div style="margin: 20px 0; border-left: #f5222d 5px solid; font-size: medium; font-weight: bold">
@ -47,7 +60,11 @@ export default {
return {
jobBatchInfo: null,
taskStatus: enums.taskStatus,
operationReason: enums.operationReason
operationReason: enums.operationReason,
taskType: enums.taskType,
triggerType: enums.triggerType,
blockStrategy: enums.blockStrategy,
executorType: enums.executorType
}
},
created () {

View File

@ -11,47 +11,29 @@
<a-descriptions-item label="任务名称">
{{ jobInfo.jobName }}
</a-descriptions-item>
<a-descriptions-item label="触发类型">
<a-tag :color="triggerType[jobInfo.triggerType].color">
{{ triggerType[jobInfo.triggerType].name }}
<a-descriptions-item label="重试状态">
<a-tag :color="jobStatusEnum[jobInfo.jobStatus].color">
{{ jobStatusEnum[jobInfo.jobStatus].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="间隔时长">
{{ jobInfo.triggerInterval }}
</a-descriptions-item>
<a-descriptions-item label="最大重试次数">
{{ jobInfo.maxRetryTimes }}
</a-descriptions-item>
<a-descriptions-item label="重试间隔">
{{ jobInfo.retryInterval }}()
</a-descriptions-item>
<a-descriptions-item label="并行数">
{{ jobInfo.parallelNum }}
</a-descriptions-item>
<a-descriptions-item label="执行器类型">
<a-descriptions-item label="路由策略">
<a-tag :color="routeKey[jobInfo.routeKey].color">
{{ routeKey[jobInfo.routeKey].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="执行器类型">
<a-tag :color="executorType[jobInfo.executorType].color">
{{ executorType[jobInfo.executorType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="任务类型">
<a-tag :color="taskType[jobInfo.taskType].color">
{{ taskType[jobInfo.taskType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="阻塞策略">
<a-tag :color="blockStrategy[jobInfo.blockStrategy].color">
{{ blockStrategy[jobInfo.blockStrategy].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="重试状态">
<a-tag :color="jobStatusEnum[jobInfo.jobStatus].color">
{{ jobStatusEnum[jobInfo.jobStatus].name }}
</a-tag>
<a-descriptions-item label="并行数">
{{ jobInfo.parallelNum }}
</a-descriptions-item>
<a-descriptions-item label="最大重试次数">
{{ jobInfo.maxRetryTimes }}
</a-descriptions-item>
<a-descriptions-item label="重试间隔">
{{ jobInfo.retryInterval }}()
</a-descriptions-item>
<a-descriptions-item label="超时时间">
{{ jobInfo.executorTimeout }}()
@ -59,16 +41,34 @@
<a-descriptions-item label="下次触发时间">
{{ jobInfo.nextTriggerAt }}
</a-descriptions-item>
<a-descriptions-item label="更新时间">
<a-descriptions-item label="更新时间" span="4">
{{ jobInfo.updateDt }}
</a-descriptions-item>
<a-descriptions-item label="执行器名称" span="3">
<a-descriptions-item label="触发类型" span="1">
<a-tag :color="triggerType[jobInfo.triggerType].color">
{{ triggerType[jobInfo.triggerType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="间隔时长" span="4">
{{ jobInfo.triggerInterval }}
</a-descriptions-item>
<a-descriptions-item label="执行器类型">
<a-tag :color="executorType[jobInfo.executorType].color">
{{ executorType[jobInfo.executorType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="执行器名称" span="4">
{{ jobInfo.executorName }}
</a-descriptions-item>
<a-descriptions-item label="参数" span="3">
<a-descriptions-item label="任务类型">
<a-tag :color="taskType[jobInfo.taskType].color">
{{ taskType[jobInfo.taskType].name }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="参数" span="4">
{{ jobInfo.argsStr }}
</a-descriptions-item>
<a-descriptions-item label="扩展参数" span="3">
<a-descriptions-item label="描述" span="4">
{{ jobInfo.extAttrs }}
</a-descriptions-item>
</a-descriptions>

View File

@ -14,40 +14,40 @@
</a-select>
</a-form-item>
</a-col>
<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>
</a-form-item>
</a-col>
<!-- <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>-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<a-col :md="8" :sm="24">
<a-form-item label="状态">
<a-select v-model="queryParam.jobStatus" placeholder="请选择状态" allowClear>
<a-select-option v-for="(index, value) in jobStatus" :value="value" :key="value">
<a-select-option v-for="(index, value) in jobStatusEnum" :value="value" :key="value">
{{ index.name }}</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-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="幂等id">
<a-input v-model="queryParam.idempotentId" placeholder="请输入幂等id" allowClear />
</a-form-item>
</a-col>
<a-col :md="8" :sm="24">
<a-form-item label="UniqueId">
<a-input v-model="queryParam.uniqueId" placeholder="请输入唯一id" allowClear/>
</a-form-item>
</a-col>
<!-- <a-col :md="8" :sm="24">-->
<!-- <a-form-item label="业务编号">-->
<!-- <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.idempotentId" placeholder="请输入幂等id" allowClear />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- <a-col :md="8" :sm="24">-->
<!-- <a-form-item label="UniqueId">-->
<!-- <a-input v-model="queryParam.uniqueId" placeholder="请输入唯一id" allowClear/>-->
<!-- </a-form-item>-->
<!-- </a-col>-->
</template>
<a-col :md="(!advanced && 8) || 24" :sm="24">
<span
@ -70,10 +70,9 @@
<!-- <a-button type="primary" icon="plus" @click="handleBatchNew()">批量</a-button>-->
<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-item key="1"><a-icon type="delete" />删除</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>
@ -83,8 +82,6 @@
:rowKey="(record) => record.id"
:columns="columns"
:data="loadData"
:alert="options.alert"
:rowSelection="options.rowSelection"
>
<span slot="serial" slot-scope="text, record">
{{ record.id }}
@ -119,40 +116,33 @@
<template>
<a @click="handleInfo(record)">详情</a>
<a-divider type="vertical" />
<a @click="handleEdit(record)">编辑</a>
<a-divider type="vertical" />
<a-popconfirm
title="是否暂停?"
ok-text="恢复"
title="是否关闭?"
ok-text="关闭"
cancel-text="取消"
@confirm="handleSuspend(record)"
@confirm="handleClose(record)"
>
<a href="javascript:;" v-if="record.retryStatus === 0">暂停</a>
<a href="javascript:;" v-if="record.jobStatus === 1">关闭</a>
</a-popconfirm>
<a-divider type="vertical" v-if="record.retryStatus === 0" />
<a-divider type="vertical" v-if="record.jobStatus === 1" />
<a-popconfirm
title="是否恢复?"
ok-text="恢复"
title="是否开启?"
ok-text="开启"
cancel-text="取消"
@confirm="handleRecovery(record)"
@confirm="handleOpen(record)"
>
<a href="javascript:;" v-if="record.retryStatus === 3">恢复</a>
<a href="javascript:;" v-if="record.jobStatus === 0">开启</a>
</a-popconfirm>
<a-divider type="vertical" v-if="record.retryStatus === 3" />
<a-divider type="vertical" v-if="record.jobStatus === 0" />
<a-popconfirm
title="是否完成?"
ok-text="完成"
title="是否删除任务?"
ok-text="删除"
cancel-text="取消"
@confirm="handleFinish(record)"
@confirm="handleDel(record)"
>
<a href="javascript:;" v-if="record.retryStatus !== 1 && record.retryStatus !== 2">完成</a>
</a-popconfirm>
<a-divider type="vertical" v-if="record.retryStatus !== 1 && record.retryStatus !== 2" />
<a-popconfirm
title="是否执行任务?"
ok-text="执行"
cancel-text="取消"
@confirm="handleTrigger(record)"
>
<a href="javascript:;" v-if="record.retryStatus !== 1 && record.retryStatus !== 2">执行</a>
<a href="javascript:;" v-if="record.jobStatus === 0">删除</a>
</a-popconfirm>
</template>
@ -166,7 +156,7 @@
import ATextarea from 'ant-design-vue/es/input/TextArea'
import AInput from 'ant-design-vue/es/input/Input'
import { STable } from '@/components'
import { getJobList } from '@/api/jobApi'
import { delJob, getJobList, updateJobStatus } from '@/api/jobApi'
import { getAllGroupNameList } from '@/api/manage'
import enums from '@/utils/enum'
@ -293,11 +283,11 @@ export default {
})
},
methods: {
handleNew () {
this.$refs.saveRetryTask.isShow(true, null)
handleEdit (record) {
this.$router.push({ path: '/job/config', query: { id: record.id } })
},
handleBatchNew () {
this.$refs.batchSaveRetryTask.isShow(true, null)
handleNew () {
this.$router.push({ path: '/job/config' })
},
handleChange (value) {
},
@ -308,61 +298,38 @@ export default {
this.$router.push({ path: '/job/info', query: { id: record.id, groupName: record.groupName } })
},
handleOk (record) {},
handleSuspend (record) {
// updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 3 }).then((res) => {
// const { status } = res
// if (status === 0) {
// this.$message.error('')
// } else {
// this.$refs.table.refresh(true)
// this.$message.success('')
// }
// })
handleClose (record) {
updateJobStatus({ id: record.id, jobStatus: 0 }).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('关闭失败')
} else {
this.$refs.table.refresh(true)
this.$message.success('关闭成功')
}
})
},
handleRecovery (record) {
// updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 0 }).then((res) => {
// const { status } = res
// if (status === 0) {
// this.$message.error('')
// } else {
// this.$refs.table.refresh(true)
// this.$message.success('')
// }
// })
handleOpen (record) {
updateJobStatus({ id: record.id, jobStatus: 1 }).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('开启失败')
} else {
this.$refs.table.refresh(true)
this.$message.success('开启成功')
}
})
},
handleFinish (record) {
// updateRetryTaskStatus({ id: record.id, groupName: record.groupName, retryStatus: 1 }).then((res) => {
// const { status } = res
// if (status === 0) {
// this.$message.error('')
// } else {
// this.$refs.table.refresh(true)
// this.$message.success('')
// }
// })
},
handleTrigger (record) {
// if (record.taskType === 1) {
// manualTriggerRetryTask({ groupName: record.groupName, uniqueIds: [ record.uniqueId ] }).then(res => {
// const { status } = res
// if (status === 0) {
// this.$message.error('')
// } else {
// this.$refs.table.refresh(true)
// this.$message.success('')
// }
// })
// } else {
// manualTriggerCallbackTask({ groupName: record.groupName, uniqueIds: [ record.uniqueId ] }).then(res => {
// const { status } = res
// if (status === 0) {
// this.$message.error('')
// } else {
// this.$refs.table.refresh(true)
// this.$message.success('')
// }
// })
// }
handleDel (record) {
delJob(record.id).then((res) => {
const { status } = res
if (status === 0) {
this.$message.error('删除失败')
} else {
this.$refs.table.refresh(true)
this.$message.success('删除成功')
}
})
},
refreshTable (v) {
this.$refs.table.refresh(true)
@ -371,23 +338,6 @@ export default {
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)

View File

@ -66,6 +66,19 @@ export default {
total: 0
}
},
created () {
const taskBatchId = this.$route.query.taskBatchId
const jobId = this.$route.query.jobId
if (taskBatchId && jobId) {
this.queryParam = {
taskBatchId: taskBatchId,
jobId: jobId
}
this.$refs.table.refresh(true)
} else {
this.$router.push({ path: '/404' })
}
},
methods: {
refreshTable (v) {
this.queryParam = v

View File

@ -0,0 +1,476 @@
<template>
<div>
<page-header-wrapper content="定时任务配置" @back="() => $router.go(-1)" style="margin: -24px -1px 0">
<div></div>
</page-header-wrapper>
<a-card :body-style="{padding: '24px 32px'}" :bordered="false" :loading="loading">
<a-form @submit="handleSubmit" :form="form" class="form-row" layout="vertical" style="width: 35%;margin: auto;">
<a-row class="form-row" :gutter="16">
<a-col :lg="24" :md="24" :sm="24">
<a-form-item>
<a-input
hidden
v-decorator="['id']" />
</a-form-item>
<a-form-item label="任务名称" >
<a-input
placeholder="请输入任务名称"
:maxLength="64"
v-decorator="[
'jobName',
{rules: [{ required: true, message: '请输入任务名称', whitespace: true},{required: true, max: 64, message: '最多支持64个字符'}]}
]" />
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="18" :md="18" :sm="24">
<a-form-item label="组">
<a-select
placeholder="请选择组"
v-decorator="['groupName', { rules: [{ required: true, message: '请选择组' }] }]"
>
<a-select-option v-for="item in groupNameList" :value="item" :key="item">{{ item }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24">
<a-form-item label="状态">
<a-select
placeholder="请选择状态"
v-decorator="[
'jobStatus',
{
initialValue: '1',
rules: [{ required: true, message: '请选择状态'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in jobStatusEnum" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="12" :md="12" :sm="24">
<a-form-item label="路由策略">
<a-select
placeholder="请选择路由策略"
v-decorator="[
'routeKey',
{
initialValue: '4',
rules: [{ required: true, message: '请选择路由策略'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in routeKey" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :sm="24">
<a-form-item label="阻塞策略">
<a-select
placeholder="请选择阻塞策略"
v-decorator="[
'blockStrategy',
{
initialValue: '1',
rules: [{ required: true, message: '请选择阻塞策略'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in blockStrategy" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="8" :md="12" :sm="12">
<a-form-item label="触发类型">
<a-select
placeholder="请选择触发类型"
v-decorator="[
'triggerType',
{
initialValue: '2',
rules: [{ required: true, message: '请选择触发类型'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in triggerType" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="16" :md="12" :sm="12">
<a-form-item label="间隔时长">
<a-input
placeholder="请输入间隔时长"
v-decorator="[
'triggerInterval',
{rules: [{ required: true, message: '请输入间隔时长', whitespace: true}]}
]" />
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="8" :md="6" :sm="12">
<a-form-item label="执行器类型">
<a-select
placeholder="请选择执行器类型"
v-decorator="[
'executorType',
{
initialValue: '1',
rules: [{ required: true, message: '请选择执行器类型'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in executorType" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="16" :md="24" :sm="24">
<a-form-item label="执行器名称">
<a-input
placeholder="请输入执行器名称"
type="textarea"
:rows="1"
v-decorator="[
'executorName',
{rules: [{ required: true, message: '请输入执行器名称', whitespace: true}]}
]" />
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="8" :md="12" :sm="24">
<a-form-item label="任务类型">
<a-select
placeholder="请选择任务类型"
v-decorator="[
'taskType',
{
initialValue: '1',
rules: [{ required: true, message: '请选择任务类型'}]
}
]" >
<a-select-option :value="index" v-for="(item, index) in taskType" :key="index">{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="16" :md="24" :sm="24">
<a-form-item label="方法参数">
<a-input
placeholder="请输入方法参数"
type="textarea"
:rows="1"
@click="handleBlur"
v-decorator="[
'argsStr',
{rules: [{ required: true, message: '请输入方法参数', whitespace: true}]}
]" />
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="6" :md="12" :sm="12">
<a-form-item label="超时时间(秒)">
<a-input-number
id="inputNumber"
:min="1"
:max="36000"
v-decorator="[
'executorTimeout',
{
initialValue: '60',
rules: [{ required: true, message: '请输入超时时间'}]
}
]" />
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="12">
<a-form-item label="最大重试次数">
<a-input-number
:min="1"
v-decorator="[
'maxRetryTimes',
{
initialValue: '3',
rules: [{ required: true, message: '请输入最大重试次数'}]
}
]" />
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="12">
<a-form-item label="重试间隔(秒)">
<a-input-number
:min="1"
v-decorator="[
'retryInterval',
{
initialValue: '1',
rules: [{ required: true, message: '请输入重试间隔'
}]
}
]" />
</a-form-item>
</a-col>
<a-col :lg="6" :md="6" :sm="12">
<a-form-item label="并行数">
<a-input-number
:min="1"
v-decorator="[
'parallelNum',
{
initialValue: '1',
rules: [{ required: true, message: '请输入并行数'}]
}
]" />
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="16">
</a-row>
<a-row class="form-row" :gutter="16">
<a-col :lg="24" :md="24" :sm="24">
<a-form-item label="描述">
<a-input
placeholder="请输入描述"
type="textarea"
v-decorator="[
'description',
{rules: [{required: false, max: 256, message: '最多支持256个字符'}]}
]" />
</a-form-item>
</a-col>
</a-row>
<a-form-item
:wrapperCol="{ span: 24 }"
style="text-align: center"
>
<a-button htmlType="submit" type="primary">提交</a-button>
<a-button style="margin-left: 8px">重置</a-button>
</a-form-item>
</a-form>
</a-card>
<a-modal :visible="visible" title="添加配置" @ok="handleOk" @cancel="handlerCancel" width="500px">
<a-form :form="dynamicForm" @submit="handleSubmit" :body-style="{padding: '0px 0px'}" v-bind="formItemLayout" >
<a-form-item
v-for="(k, index) in dynamicForm.getFieldValue('keys')"
:key="k"
v-bind="formItemLayoutWithOutLabel"
:label="'分片' + index "
:required="true"
>
<a-input
v-decorator="[
`sharding[${k}]`,
{
validateTrigger: ['change', 'blur'],
rules: [
{
required: true,
whitespace: true,
message: '分片参数必填',
},
],
},
]"
placeholder="请输入参数"
style="width: 60%; margin-right: 8px"
/>
<a-icon
v-if="dynamicForm.getFieldValue('keys').length > 1"
class="dynamic-delete-button"
type="minus-circle-o"
:disabled="dynamicForm.getFieldValue('keys').length === 1"
@click="() => remove(k)"
/>
</a-form-item>
<a-form-item v-bind="formItemLayoutWithOutLabel">
<a-button type="dashed" style="width: 60%" @click="add">
<a-icon type="plus" /> 添加分片
</a-button>
</a-form-item>
<a-form-item
:wrapper-col="{
xs: { span: 24, offset: 0 },
sm: { span: 16, offset: 8 },
lg: { span: 7 }
}">
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { getAllGroupNameList } from '@/api/manage'
import { getJobDetail, saveJob, updateJob } from '@/api/jobApi'
import pick from 'lodash.pick'
const enums = require('@/utils/enum')
let id = 0
export default {
name: 'JobFrom',
props: {},
data () {
return {
form: this.$form.createForm(this),
formItemLayout: {
labelCol: { lg: { span: 7 }, sm: { span: 7 } },
wrapperCol: { lg: { span: 10 }, sm: { span: 17 } }
},
formItemLayoutWithOutLabel: {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 }
}
},
formType: 'create',
groupNameList: [],
jobStatusEnum: enums.jobStatusEnum,
taskType: enums.taskType,
triggerType: enums.triggerType,
blockStrategy: enums.blockStrategy,
executorType: enums.executorType,
routeKey: enums.routeKey,
loading: false,
visible: false
}
},
beforeCreate () {
console.log('beforeCreate')
this.dynamicForm = this.$form.createForm(this, { name: 'dynamic_form_item' })
this.dynamicForm.getFieldDecorator('keys', { initialValue: [], preserve: true })
},
mounted () {
getAllGroupNameList().then((res) => {
this.groupNameList = res.data
})
this.$nextTick(() => {
const id = this.$route.query.id
if (id) {
this.loading = true
getJobDetail(id).then(res => {
this.loadEditInfo(res.data)
this.loading = false
})
}
})
},
methods: {
remove (k) {
const { dynamicForm } = this
// can use data-binding to get
const keys = dynamicForm.getFieldValue('keys')
// We need at least one passenger
if (keys.length === 1) {
return
}
// can use data-binding to set
dynamicForm.setFieldsValue({
keys: keys.filter(key => key !== k)
})
},
add () {
const { dynamicForm } = this
// can use data-binding to get
const keys = dynamicForm.getFieldValue('keys')
console.log(keys)
const nextKeys = keys.concat(id++)
// can use data-binding to set
// important! notify form to detect changes
dynamicForm.setFieldsValue({
keys: nextKeys
})
},
handleBlur () {
const taskType = this.form.getFieldValue('taskType')
if (taskType === '3') {
this.visible = !this.visible
if (this.formType === 'create') {
return
}
const argsStr = this.form.getFieldValue('argsStr')
//
const keyValuePairs = argsStr.split(';')
console.log(keyValuePairs)
const restoredArray = keyValuePairs.map(pair => {
const [index, value] = pair.split('=')
console.log(value)
id++
return Number.parseInt(index)
})
this.dynamicForm.getFieldDecorator('keys', { initialValue: restoredArray, preserve: true })
keyValuePairs.map(pair => {
const [index, value] = pair.split('=')
this.dynamicForm.getFieldDecorator(`sharding[${index}]`, { initialValue: value, preserve: true })
return value
})
}
},
handleOk (e) {
const { form } = this
e.preventDefault()
this.dynamicForm.validateFields((err, values) => {
console.log()
if (!err) {
console.log(values)
const arr = values['sharding']
const formattedString = arr.map((item, index) => `${index}=${item}`).join(';')
form.setFieldsValue({
argsStr: formattedString
})
this.visible = false
}
})
},
handlerCancel () {
this.visible = false
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
if (this.formType === 'create') {
saveJob(values).then(res => {
this.$message.success('任务新增完成')
this.form.resetFields()
this.$router.go(-1)
})
} else {
updateJob(values).then(res => {
this.$message.success('任务更新完成')
this.form.resetFields()
this.$router.go(-1)
})
}
}
})
},
loadEditInfo (data) {
this.formType = 'edit'
const { form } = this
// ajax
new Promise((resolve) => {
setTimeout(resolve, 100)
}).then(() => {
const formData = pick(data, [
'id', 'jobName', 'groupName', 'jobStatus', 'executorName', 'argsStr', 'executorTimeout', 'description',
'maxRetryTimes', 'parallelNum', 'retryInterval', 'triggerType', 'blockStrategy', 'executorType', 'taskType', 'triggerInterval'])
formData.jobStatus = formData.jobStatus.toString()
formData.taskType = formData.taskType.toString()
formData.executorType = formData.executorType.toString()
formData.blockStrategy = formData.blockStrategy.toString()
formData.triggerType = formData.triggerType.toString()
form.setFieldsValue(formData)
})
}
}
}
</script>