feat: 2.4.0

1. 前端新增cron表达式组件
This commit is contained in:
byteblogs168 2023-10-17 21:44:14 +08:00
parent 01eefc2774
commit 69e0fe0a48
17 changed files with 181 additions and 28 deletions

View File

@ -3,8 +3,8 @@ package com.aizuda.easy.retry.client.job.core.client;
import com.aizuda.easy.retry.client.job.core.IJobExecutor;
import com.aizuda.easy.retry.client.job.core.cache.JobExecutorInfoCache;
import com.aizuda.easy.retry.client.job.core.cache.ThreadPoolCache;
import com.aizuda.easy.retry.client.job.core.executor.AbstractJobExecutor;
import com.aizuda.easy.retry.client.job.core.executor.AnnotationJobExecutor;
import com.aizuda.easy.retry.client.job.core.executor.NormalJobExecutor;
import com.aizuda.easy.retry.client.job.core.dto.JobContext;
import com.aizuda.easy.retry.client.job.core.dto.JobExecutorInfo;
import com.aizuda.easy.retry.client.model.request.DispatchJobRequest;
@ -48,7 +48,7 @@ public class JobEndPoint {
IJobExecutor iJobExecutor = SpringContext.getBeanByType(AnnotationJobExecutor.class);
iJobExecutor.jobExecute(jobContext);
} else {
NormalJobExecutor normalJobExecutor = (NormalJobExecutor) jobExecutorInfo.getExecutor();
AbstractJobExecutor normalJobExecutor = (AbstractJobExecutor) jobExecutorInfo.getExecutor();
normalJobExecutor.jobExecute(jobContext);
}

View File

@ -0,0 +1,14 @@
package com.aizuda.easy.retry.client.job.core.dto;
import lombok.Data;
/**
* @author: www.byteblogs.com
* @date : 2023-10-18 16:53
* @since : 2.4.0
*/
@Data
public class JobArgs {
private String executorInfo;
}

View File

@ -0,0 +1,20 @@
package com.aizuda.easy.retry.client.job.core.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author: www.byteblogs.com
* @date : 2023-10-18 16:53
* @since : 2.4.0
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ShardingJobArgs extends JobArgs {
private Integer shardingTotal;
private Integer shardingIndex;
}

View File

@ -3,6 +3,7 @@ package com.aizuda.easy.retry.client.job.core.executor;
import com.aizuda.easy.retry.client.job.core.IJobExecutor;
import com.aizuda.easy.retry.client.job.core.cache.FutureCache;
import com.aizuda.easy.retry.client.job.core.cache.ThreadPoolCache;
import com.aizuda.easy.retry.client.job.core.dto.JobArgs;
import com.aizuda.easy.retry.client.job.core.dto.JobContext;
import com.aizuda.easy.retry.client.job.core.timer.StopTaskTimerTask;
import com.aizuda.easy.retry.client.job.core.timer.TimerManager;
@ -23,7 +24,7 @@ import java.util.concurrent.TimeUnit;
* @date : 2023-09-27 09:48
* @since 2.4.0
*/
abstract class AbstractJobExecutor implements IJobExecutor {
public abstract class AbstractJobExecutor implements IJobExecutor {
@Override
public void jobExecute(JobContext jobContext) {
@ -43,6 +44,6 @@ abstract class AbstractJobExecutor implements IJobExecutor {
}
protected abstract ExecuteResult doJobExecute(JobContext jobContext);
protected abstract ExecuteResult doJobExecute(JobArgs jobArgs);
}

View File

@ -1,6 +1,7 @@
package com.aizuda.easy.retry.client.job.core.executor;
import com.aizuda.easy.retry.client.job.core.cache.JobExecutorInfoCache;
import com.aizuda.easy.retry.client.job.core.dto.JobArgs;
import com.aizuda.easy.retry.client.job.core.dto.JobContext;
import com.aizuda.easy.retry.client.job.core.dto.JobExecutorInfo;
import com.aizuda.easy.retry.client.model.ExecuteResult;
@ -18,8 +19,8 @@ import org.springframework.util.ReflectionUtils;
public class AnnotationJobExecutor extends AbstractJobExecutor {
@Override
protected ExecuteResult doJobExecute(final JobContext jobContext) {
JobExecutorInfo jobExecutorInfo = JobExecutorInfoCache.get(jobContext.getExecutorInfo());
return (ExecuteResult) ReflectionUtils.invokeMethod(jobExecutorInfo.getMethod(), jobExecutorInfo.getExecutor(), jobContext);
protected ExecuteResult doJobExecute(final JobArgs jobArgs) {
JobExecutorInfo jobExecutorInfo = JobExecutorInfoCache.get(jobArgs.getExecutorInfo());
return (ExecuteResult) ReflectionUtils.invokeMethod(jobExecutorInfo.getMethod(), jobExecutorInfo.getExecutor(), jobArgs);
}
}

View File

@ -1,10 +0,0 @@
package com.aizuda.easy.retry.client.job.core.executor;
/**
* @author: www.byteblogs.com
* @date : 2023-10-08 17:02
* @since : 2.4.0
*/
public abstract class NormalJobExecutor extends AbstractJobExecutor {
}

View File

@ -61,4 +61,10 @@ public class JobController {
return jobService.deleteJobById(id);
}
@GetMapping("/cron")
@LoginRequired
public List<String> getTimeByCron(@RequestParam("cron") String cron) {
return jobService.getTimeByCron(cron);
}
}

View File

@ -62,7 +62,7 @@ public class JobResponseVO {
/**
* 执行器名称
*/
private String executorName;
private String executorInfo;
/**
* 触发类型 1.CRON 表达式 2. 固定时间

View File

@ -25,4 +25,6 @@ public interface JobService {
Boolean updateJobStatus(JobUpdateJobStatusRequestVO jobRequestVO);
Boolean deleteJobById(Long id);
List<String> getTimeByCron(String cron);
}

View File

@ -3,6 +3,7 @@ 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.common.core.util.CronExpression;
import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.job.task.support.WaitStrategy;
import com.aizuda.easy.retry.server.job.task.support.strategy.WaitStrategies.WaitStrategyContext;
@ -22,10 +23,15 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author www.byteblogs.com
@ -35,6 +41,8 @@ import java.util.concurrent.TimeUnit;
@Service
public class JobServiceImpl implements JobService {
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired
private JobMapper jobMapper;
@ -72,6 +80,26 @@ public class JobServiceImpl implements JobService {
return JobResponseVOConverter.INSTANCE.toJobResponseVO(job);
}
@Override
public List<String> getTimeByCron(String cron) {
List<String> list = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
for (int i = 0; i < 5; i++) {
Date nextValidTime;
try {
ZonedDateTime zdt = now.atZone(ZoneOffset.ofHours(8));
nextValidTime = new CronExpression(cron).getNextValidTimeAfter(Date.from(zdt.toInstant()));
now = LocalDateTime.ofEpochSecond( nextValidTime.getTime() / 1000,0, ZoneOffset.ofHours(8));
list.add(dateTimeFormatter.format(now));
} catch (ParseException ignored) {
}
}
return list;
}
@Override
public boolean saveJob(JobRequestVO jobRequestVO) {
Job job = JobConverter.INSTANCE.toJob(jobRequestVO);

View File

@ -14,8 +14,9 @@
"@antv/data-set": "^0.10.2",
"@antv/g2": "^3.5.1",
"ant-design-vue": "^1.7.6",
"antv-cron": "^1.0.9",
"axios": ">=0.21.1",
"core-js": "^3.1.2",
"core-js": "^3.8.3",
"enquire.js": "^2.1.6",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",

View File

@ -7,6 +7,7 @@ const jobApi = {
updateJob: '/job/',
updateJobStatus: '/job/status',
delJob: '/job/',
timeByCron: '/job/cron',
// 任务批次
jobBatchList: '/job/batch/list',
@ -21,6 +22,14 @@ const jobApi = {
export default jobApi
export function timeByCron (parameter) {
return request({
url: jobApi.timeByCron,
method: 'get',
params: parameter
})
}
export function delJob (id) {
return request({
url: jobApi.delJob + id,

View File

@ -20,7 +20,8 @@ import './core/lazy_use' // use lazy load components
import './permission' // permission control
import './utils/filter' // global filter
import './global.less' // global style
import CronInput from 'antv-cron/packages/index'
import 'antv-cron/lib/antv-cron.css'
Vue.config.productionTip = false
// mount axios to `Vue.$http` and `this.$http`
@ -29,6 +30,7 @@ Vue.use(VueAxios)
Vue.component('pro-layout', ProLayout)
Vue.component('page-container', PageHeaderWrapper)
Vue.component('page-header-wrapper', PageHeaderWrapper)
Vue.use(CronInput)
window.umi_plugin_ant_themeVar = themePluginConfig.theme

View File

@ -58,7 +58,7 @@
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="执行器名称" span="4">
{{ jobInfo.executorName }}
{{ jobInfo.executorInfo }}
</a-descriptions-item>
<a-descriptions-item label="任务类型">
<a-tag :color="taskType[jobInfo.taskType].color">

View File

@ -107,7 +107,7 @@
</a-tag>
</span>
<span slot="triggerInterval" slot-scope="text">
<span>{{ text }}()</span>
<span>{{ text }}</span>
</span>
<span slot="executorTimeout" slot-scope="text">
<span>{{ text }}()</span>
@ -199,7 +199,8 @@ export default {
},
{
title: '触发时间',
dataIndex: 'nextTriggerAt'
dataIndex: 'nextTriggerAt',
ellipsis: true
},
{
title: '状态',
@ -219,7 +220,8 @@ export default {
{
title: '间隔时长',
dataIndex: 'triggerInterval',
scopedSlots: { customRender: 'triggerInterval' }
scopedSlots: { customRender: 'triggerInterval' },
ellipsis: true
},
{
title: '阻塞策略',

View File

@ -0,0 +1,54 @@
<template>
<div>
<a-modal :visible="visible" title="Cron表达式" @ok="handleOk" @cancel="handlerCancel" width="650px">
<cron-input v-model="cron" :item="cronItem" @change="showFive"/>
<a-input placeholder="请输入cron表达式" v-model="cron"/>
<div style="margin: 20px 0; border-left: #f5222d 5px solid; font-size: medium; font-weight: bold">
&nbsp;&nbsp; 近5次的运行时间:
</div>
<div v-for="(item, index) in list" :key="item" style="margin-top: 10px"> {{ index + 1 }}: {{ item }}</div>
</a-modal>
</div>
</template>
<script>
import { timeByCron } from '@/api/jobApi'
export default {
name: 'CronModal',
data () {
return {
visible: false,
cronItem: ['second', 'minute', 'hour', 'day', 'month', 'week', 'year'],
cron: '',
list: []
}
},
methods: {
handleOk () {
this.visible = false
this.$emit('getCron', this.cron)
},
handlerCancel () {
this.visible = false
},
isShow (cron) {
this.cron = cron
this.visible = true
},
showFive (cron) {
this.cron = cron
timeByCron({ 'cron': cron }).then(res => {
this.list = res.data
})
}
}
}
</script>
<style>
.ant-cron-result{
display: none;
}
</style>

View File

@ -101,6 +101,7 @@
<a-col :lg="16" :md="12" :sm="12">
<a-form-item label="间隔时长">
<a-input
@click="handlerCron"
placeholder="请输入间隔时长"
v-decorator="[
'triggerInterval',
@ -250,7 +251,7 @@
</a-form>
</a-card>
<a-modal :visible="visible" title="添加配置" @ok="handleOk" @cancel="handlerCancel" width="500px">
<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')"
@ -298,6 +299,7 @@
</a-form-item>
</a-form>
</a-modal>
<cron-modal ref="cronModalRef" @getCron="getCron"/>
</div>
</template>
@ -305,11 +307,18 @@
import { getAllGroupNameList } from '@/api/manage'
import { getJobDetail, saveJob, updateJob } from '@/api/jobApi'
import pick from 'lodash.pick'
import CronModal from '@/views/job/from/CronModal'
const enums = require('@/utils/enum')
let id = 0
export default {
name: 'JobFrom',
components: { CronModal },
props: {},
comments: {
CronModal
},
data () {
return {
form: this.$form.createForm(this),
@ -336,7 +345,6 @@ export default {
}
},
beforeCreate () {
console.log('beforeCreate')
this.dynamicForm = this.$form.createForm(this, { name: 'dynamic_form_item' })
this.dynamicForm.getFieldDecorator('keys', { initialValue: [], preserve: true })
},
@ -357,6 +365,16 @@ export default {
})
},
methods: {
handlerCron () {
const triggerType = this.form.getFieldValue('triggerType')
if (triggerType === '1') {
let triggerInterval = this.form.getFieldValue('triggerInterval')
if (triggerInterval === null || triggerInterval === '') {
triggerInterval = '* * * * * ?'
}
this.$refs.cronModalRef.isShow(triggerInterval)
}
},
remove (k) {
const { dynamicForm } = this
// can use data-binding to get
@ -414,6 +432,11 @@ export default {
})
}
},
getCron (cron) {
this.form.setFieldsValue({
triggerInterval: cron
})
},
handleOk (e) {
const { form } = this
e.preventDefault()
@ -461,7 +484,7 @@ export default {
setTimeout(resolve, 100)
}).then(() => {
const formData = pick(data, [
'id', 'jobName', 'groupName', 'jobStatus', 'executorName', 'argsStr', 'executorTimeout', 'description',
'id', 'jobName', 'groupName', 'jobStatus', 'executorInfo', 'argsStr', 'executorTimeout', 'description',
'maxRetryTimes', 'parallelNum', 'retryInterval', 'triggerType', 'blockStrategy', 'executorType', 'taskType', 'triggerInterval'])
formData.jobStatus = formData.jobStatus.toString()
formData.taskType = formData.taskType.toString()