增加定时任务管理
This commit is contained in:
parent
03d09ca7d7
commit
327f0df6a9
|
|
@ -36,10 +36,16 @@
|
||||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SaToken-Redis -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.dev33</groupId>
|
<groupId>cn.dev33</groupId>
|
||||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||||
<version>1.32.0</version>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Quartz-Scheduler -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
|
<artifactId>quartz</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.mdd.admin.config.quartz;
|
||||||
|
|
||||||
|
import org.quartz.CronExpression;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表达式工具
|
||||||
|
*/
|
||||||
|
public class CronUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表达式是否有效
|
||||||
|
*
|
||||||
|
* @param cronExpression 表达式
|
||||||
|
* @return true=有效,false=无效
|
||||||
|
*/
|
||||||
|
public static boolean isValid(String cronExpression) {
|
||||||
|
return CronExpression.isValidExpression(cronExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表达式消息无效给出有效性
|
||||||
|
*
|
||||||
|
* @param cronExpression 表达式
|
||||||
|
* @return null=有效, 其它=无效时的错误描述
|
||||||
|
*/
|
||||||
|
public static String invalidMessage(String cronExpression) {
|
||||||
|
try {
|
||||||
|
new CronExpression(cronExpression);
|
||||||
|
return null;
|
||||||
|
} catch (ParseException pe) {
|
||||||
|
return pe.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下一个执行时间点
|
||||||
|
*
|
||||||
|
* @param cronExpression n表达式
|
||||||
|
* @return Date下次表达式执行时间
|
||||||
|
*/
|
||||||
|
public static Date nextExecution(String cronExpression)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CronExpression cron = new CronExpression(cronExpression);
|
||||||
|
return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
package com.mdd.admin.config.quartz;
|
||||||
|
|
||||||
|
import com.mdd.common.entity.Crontab;
|
||||||
|
import com.mdd.common.utils.SpringUtil;
|
||||||
|
import com.mdd.common.utils.StringUtil;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行工具
|
||||||
|
*/
|
||||||
|
public class InvokeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行方法
|
||||||
|
*
|
||||||
|
* @param crontab 系统任务
|
||||||
|
*/
|
||||||
|
public static void invokeMethod(Crontab crontab) throws Exception {
|
||||||
|
String invokeTarget = crontab.getCommand();
|
||||||
|
String beanName = getBeanName(invokeTarget);
|
||||||
|
String methodName = getMethodName(invokeTarget);
|
||||||
|
List<Object[]> methodParams = getMethodParams(invokeTarget);
|
||||||
|
|
||||||
|
if (!isValidClassName(beanName)) {
|
||||||
|
Object bean = SpringUtil.getBean(beanName);
|
||||||
|
invokeMethod(bean, methodName, methodParams);
|
||||||
|
} else {
|
||||||
|
Object bean = Class.forName(beanName).newInstance();
|
||||||
|
invokeMethod(bean, methodName, methodParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用任务方法
|
||||||
|
*
|
||||||
|
* @param bean 目标对象
|
||||||
|
* @param methodName 方法名称
|
||||||
|
* @param methodParams 方法参数
|
||||||
|
*/
|
||||||
|
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
|
||||||
|
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
|
||||||
|
InvocationTargetException {
|
||||||
|
if (StringUtil.isNotNull(methodParams) && methodParams.size() > 0) {
|
||||||
|
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
|
||||||
|
method.invoke(bean, getMethodParamsValue(methodParams));
|
||||||
|
} else {
|
||||||
|
Method method = bean.getClass().getMethod(methodName);
|
||||||
|
method.invoke(bean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验是否为为class包名
|
||||||
|
*
|
||||||
|
* @param invokeTarget 名称
|
||||||
|
* @return true是 false否
|
||||||
|
*/
|
||||||
|
public static boolean isValidClassName(String invokeTarget) {
|
||||||
|
return StringUtil.countMatches(invokeTarget, ".") > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取bean名称
|
||||||
|
*
|
||||||
|
* @param invokeTarget 目标字符串
|
||||||
|
* @return bean名称
|
||||||
|
*/
|
||||||
|
public static String getBeanName(String invokeTarget) {
|
||||||
|
String beanName = StringUtil.substringBefore(invokeTarget, "(");
|
||||||
|
return StringUtil.substringBeforeLast(beanName, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取bean方法
|
||||||
|
*
|
||||||
|
* @param invokeTarget 目标字符串
|
||||||
|
* @return method方法
|
||||||
|
*/
|
||||||
|
public static String getMethodName(String invokeTarget) {
|
||||||
|
String methodName = StringUtil.substringBefore(invokeTarget, "(");
|
||||||
|
return StringUtil.substringAfterLast(methodName, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取method方法参数相关列表
|
||||||
|
*
|
||||||
|
* @param invokeTarget 目标字符串
|
||||||
|
* @return method方法相关参数列表
|
||||||
|
*/
|
||||||
|
public static List<Object[]> getMethodParams(String invokeTarget) {
|
||||||
|
String methodStr = StringUtil.substringBetween(invokeTarget, "(", ")");
|
||||||
|
if (StringUtil.isEmpty(methodStr)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
|
||||||
|
List<Object[]> clazz = new LinkedList<>();
|
||||||
|
for (String methodParam : methodParams) {
|
||||||
|
String str = StringUtil.trimToEmpty(methodParam);
|
||||||
|
if (StringUtil.startsWithAny(str, "'", "\"")) {
|
||||||
|
clazz.add(new Object[]{StringUtil.substring(str, 1, str.length() - 1), String.class});
|
||||||
|
} else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
|
||||||
|
clazz.add(new Object[]{Boolean.valueOf(str), Boolean.class});
|
||||||
|
} else if (StringUtil.endsWith(str, "L")) {
|
||||||
|
clazz.add(new Object[]{Long.valueOf(StringUtil.substring(str, 0, str.length() - 1)), Long.class});
|
||||||
|
} else if (StringUtil.endsWith(str, "D")) {
|
||||||
|
clazz.add(new Object[]{Double.valueOf(StringUtil.substring(str, 0, str.length() - 1)), Double.class});
|
||||||
|
} else {
|
||||||
|
clazz.add(new Object[]{Integer.valueOf(str), Integer.class});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数类型
|
||||||
|
*
|
||||||
|
* @param methodParams 参数相关列表
|
||||||
|
* @return 参数类型列表
|
||||||
|
*/
|
||||||
|
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
|
||||||
|
Class<?>[] clazz = new Class<?>[methodParams.size()];
|
||||||
|
int index = 0;
|
||||||
|
for (Object[] os : methodParams)
|
||||||
|
{
|
||||||
|
clazz[index] = (Class<?>) os[1];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数值
|
||||||
|
*
|
||||||
|
* @param methodParams 参数相关列表
|
||||||
|
* @return 参数值列表
|
||||||
|
*/
|
||||||
|
public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
|
||||||
|
Object[] clazz = new Object[methodParams.size()];
|
||||||
|
int index = 0;
|
||||||
|
for (Object[] os : methodParams) {
|
||||||
|
clazz[index] = os[0];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.mdd.admin.config.quartz;
|
||||||
|
|
||||||
|
import com.mdd.admin.config.quartz.exceution.QuartzDisExecution;
|
||||||
|
import com.mdd.admin.config.quartz.exceution.QuartzJobExecution;
|
||||||
|
import com.mdd.common.entity.Crontab;
|
||||||
|
import com.mdd.common.utils.StringUtil;
|
||||||
|
import org.quartz.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划任务工具
|
||||||
|
*/
|
||||||
|
public class QuartzUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 得到quartz任务类
|
||||||
|
*
|
||||||
|
* @param crontab 执行计划
|
||||||
|
* @return 具体执行任务类
|
||||||
|
*/
|
||||||
|
private static Class<? extends Job> getQuartzJobClass(Crontab crontab) {
|
||||||
|
boolean isConcurrent = crontab.getConcurrent().equals(0);
|
||||||
|
return isConcurrent ? QuartzJobExecution.class : QuartzDisExecution.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建任务对象Key
|
||||||
|
*
|
||||||
|
* @param jobId (任务ID)
|
||||||
|
* @param jobGroup (任务分组)
|
||||||
|
* @return JobKey
|
||||||
|
*/
|
||||||
|
public static JobKey getJobKey(Integer jobId, String jobGroup) {
|
||||||
|
return JobKey.jobKey(TaskConstants.TASK_CLASS_NAME + jobId, jobGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建触发对象Key
|
||||||
|
*
|
||||||
|
* @param jobId (任务ID)
|
||||||
|
* @param jobGroup (任务分组)
|
||||||
|
* @return TriggerKey
|
||||||
|
*/
|
||||||
|
public static TriggerKey getTriggerKey(Integer jobId, String jobGroup) {
|
||||||
|
return TriggerKey.triggerKey(TaskConstants.TASK_PROPERTIES + jobId, jobGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建定时任务
|
||||||
|
*
|
||||||
|
* @param scheduler 调度器
|
||||||
|
* @param job 任务
|
||||||
|
* @throws SchedulerException 调度异常
|
||||||
|
*/
|
||||||
|
public static void createScheduleJob(Scheduler scheduler, Crontab job) throws SchedulerException {
|
||||||
|
Integer jobId = job.getId();
|
||||||
|
String jobGroup = job.getGroups();
|
||||||
|
String expression = job.getRules();
|
||||||
|
|
||||||
|
// 构建任务
|
||||||
|
Class<? extends Job> jobClass = getQuartzJobClass(job);
|
||||||
|
JobDetail jobDetail = JobBuilder.newJob(jobClass)
|
||||||
|
.withIdentity(getJobKey(jobId, jobGroup))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建时间
|
||||||
|
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(expression);
|
||||||
|
switch (job.getStrategy()) {
|
||||||
|
case 1: // 立即执行
|
||||||
|
cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
|
||||||
|
break;
|
||||||
|
case 2: // 执行一次
|
||||||
|
cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
|
||||||
|
break;
|
||||||
|
case 3: // 放弃执行
|
||||||
|
cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注入参数
|
||||||
|
jobDetail.getJobDataMap().put(TaskConstants.TASK_PROPERTIES, job);
|
||||||
|
|
||||||
|
// 构建目标
|
||||||
|
CronTrigger trigger = TriggerBuilder.newTrigger()
|
||||||
|
.withIdentity(getTriggerKey(jobId, jobGroup))
|
||||||
|
.withSchedule(cronScheduleBuilder)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 如果存在则删除
|
||||||
|
if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
|
||||||
|
scheduler.deleteJob(getJobKey(jobId, jobGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果过期则调度
|
||||||
|
if (StringUtil.isNotNull(CronUtils.nextExecution(job.getRules()))) {
|
||||||
|
scheduler.scheduleJob(jobDetail, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果暂停则停止
|
||||||
|
if (!job.getStatus().equals(TaskConstants.STATUS_RUN)) {
|
||||||
|
scheduler.pauseJob(getJobKey(jobId, jobGroup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.mdd.admin.config.quartz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划任务常量
|
||||||
|
*/
|
||||||
|
public class TaskConstants {
|
||||||
|
|
||||||
|
/** 执行任务名 */
|
||||||
|
public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
|
||||||
|
|
||||||
|
/** 执行目标键 */
|
||||||
|
public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
|
||||||
|
|
||||||
|
/** 状态: 运行 */
|
||||||
|
public static final Integer STATUS_RUN = 1;
|
||||||
|
|
||||||
|
/** 状态: 停止 */
|
||||||
|
public static final Integer STATUS_STOP = 2;
|
||||||
|
|
||||||
|
/** 状态: 失败 */
|
||||||
|
public static final Integer STATUS_FAIL = 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.mdd.admin.config.quartz.exceution;
|
||||||
|
|
||||||
|
import com.mdd.admin.config.quartz.TaskConstants;
|
||||||
|
import com.mdd.common.entity.Crontab;
|
||||||
|
import com.mdd.common.mapper.CrontabMapper;
|
||||||
|
import com.mdd.common.utils.SpringUtil;
|
||||||
|
import com.mdd.common.utils.StringUtil;
|
||||||
|
import org.quartz.Job;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
public abstract class AbstractQuartzJob implements Job {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程本地变量
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute(JobExecutionContext context) {
|
||||||
|
Crontab crontab = new Crontab();
|
||||||
|
BeanUtils.copyProperties(context.getMergedJobDataMap().get(TaskConstants.TASK_PROPERTIES), crontab);
|
||||||
|
try {
|
||||||
|
before();
|
||||||
|
doExecute(context, crontab);
|
||||||
|
after(crontab, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("任务执行异常:", e);
|
||||||
|
after(crontab, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行前
|
||||||
|
*/
|
||||||
|
protected void before() {
|
||||||
|
threadLocal.set(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行后
|
||||||
|
*
|
||||||
|
* @param crontab 系统计划任务
|
||||||
|
*/
|
||||||
|
protected void after(Crontab crontab, Exception e)
|
||||||
|
{
|
||||||
|
long startTime = threadLocal.get();
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
threadLocal.remove();
|
||||||
|
|
||||||
|
crontab.setError("");
|
||||||
|
crontab.setStartTime(startTime / 1000);
|
||||||
|
crontab.setEndTime(endTime / 1000);
|
||||||
|
crontab.setTaskTime(endTime - startTime);
|
||||||
|
if (StringUtil.isNotNull(e)) {
|
||||||
|
crontab.setError(e.getMessage());
|
||||||
|
crontab.setStatus(TaskConstants.STATUS_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpringUtil.getBean(CrontabMapper.class).updateById(crontab);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行方法
|
||||||
|
*
|
||||||
|
* @param context 工作执行上下文对象
|
||||||
|
* @param sysJob 系统计划任务
|
||||||
|
* @throws Exception 执行过程中的异常
|
||||||
|
*/
|
||||||
|
protected abstract void doExecute(JobExecutionContext context, Crontab sysJob) throws Exception;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.mdd.admin.config.quartz.exceution;
|
||||||
|
|
||||||
|
import com.mdd.admin.config.quartz.InvokeUtils;
|
||||||
|
import com.mdd.common.entity.Crontab;
|
||||||
|
import org.quartz.DisallowConcurrentExecution;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁止并发任务
|
||||||
|
*/
|
||||||
|
@DisallowConcurrentExecution
|
||||||
|
public class QuartzDisExecution extends AbstractQuartzJob {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecutionContext context, Crontab crontab) throws Exception {
|
||||||
|
InvokeUtils.invokeMethod(crontab);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.mdd.admin.config.quartz.exceution;
|
||||||
|
|
||||||
|
import com.mdd.admin.config.quartz.InvokeUtils;
|
||||||
|
import com.mdd.common.entity.Crontab;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许并发任务
|
||||||
|
*/
|
||||||
|
public class QuartzJobExecution extends AbstractQuartzJob {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(JobExecutionContext context, Crontab crontab) throws Exception {
|
||||||
|
InvokeUtils.invokeMethod(crontab);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import com.mdd.admin.vo.CrontabListedVo;
|
||||||
import com.mdd.common.core.AjaxResult;
|
import com.mdd.common.core.AjaxResult;
|
||||||
import com.mdd.common.core.PageResult;
|
import com.mdd.common.core.PageResult;
|
||||||
import com.mdd.common.validator.annotation.IDMust;
|
import com.mdd.common.validator.annotation.IDMust;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ public class CrontabController {
|
||||||
* @return AjaxResult<Object>
|
* @return AjaxResult<Object>
|
||||||
*/
|
*/
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public AjaxResult<Object> add(@Validated @RequestBody CrontabCreateValidate createValidate) {
|
public AjaxResult<Object> add(@Validated @RequestBody CrontabCreateValidate createValidate) throws SchedulerException {
|
||||||
iCrontabService.add(createValidate);
|
iCrontabService.add(createValidate);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +70,7 @@ public class CrontabController {
|
||||||
* @return AjaxResult<Object>
|
* @return AjaxResult<Object>
|
||||||
*/
|
*/
|
||||||
@PostMapping("/edit")
|
@PostMapping("/edit")
|
||||||
public AjaxResult<Object> edit(@Validated @RequestBody CrontabUpdateValidate updateValidate) {
|
public AjaxResult<Object> edit(@Validated @RequestBody CrontabUpdateValidate updateValidate) throws SchedulerException {
|
||||||
iCrontabService.edit(updateValidate);
|
iCrontabService.edit(updateValidate);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +83,7 @@ public class CrontabController {
|
||||||
* @return AjaxResult<Object>
|
* @return AjaxResult<Object>
|
||||||
*/
|
*/
|
||||||
@PostMapping("/del")
|
@PostMapping("/del")
|
||||||
public AjaxResult<Object> del(@Validated @RequestBody IdValidate idValidate) {
|
public AjaxResult<Object> del(@Validated @RequestBody IdValidate idValidate) throws SchedulerException {
|
||||||
iCrontabService.del(idValidate.getId());
|
iCrontabService.del(idValidate.getId());
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.mdd.admin.crontab;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作类的具体实现,即需要定时执行的“某个事件”
|
||||||
|
*/
|
||||||
|
@Component("myJob")
|
||||||
|
public class MyJob {
|
||||||
|
|
||||||
|
public void handle(String s) {
|
||||||
|
System.out.println("执行无参方法: " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import com.mdd.admin.validate.commons.PageValidate;
|
||||||
import com.mdd.admin.vo.CrontabDetailVo;
|
import com.mdd.admin.vo.CrontabDetailVo;
|
||||||
import com.mdd.admin.vo.CrontabListedVo;
|
import com.mdd.admin.vo.CrontabListedVo;
|
||||||
import com.mdd.common.core.PageResult;
|
import com.mdd.common.core.PageResult;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划任务服务接口类
|
* 计划任务服务接口类
|
||||||
|
|
@ -36,7 +37,7 @@ public interface ICrontabService {
|
||||||
* @author fzr
|
* @author fzr
|
||||||
* @param createValidate 参数
|
* @param createValidate 参数
|
||||||
*/
|
*/
|
||||||
void add(CrontabCreateValidate createValidate);
|
void add(CrontabCreateValidate createValidate) throws SchedulerException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划任务编辑
|
* 计划任务编辑
|
||||||
|
|
@ -44,7 +45,7 @@ public interface ICrontabService {
|
||||||
* @author fzr
|
* @author fzr
|
||||||
* @param updateValidate 参数
|
* @param updateValidate 参数
|
||||||
*/
|
*/
|
||||||
void edit(CrontabUpdateValidate updateValidate);
|
void edit(CrontabUpdateValidate updateValidate) throws SchedulerException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划任务删除
|
* 计划任务删除
|
||||||
|
|
@ -52,6 +53,6 @@ public interface ICrontabService {
|
||||||
* @author fzr
|
* @author fzr
|
||||||
* @param id 主键
|
* @param id 主键
|
||||||
*/
|
*/
|
||||||
void del(Integer id);
|
void del(Integer id) throws SchedulerException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.mdd.admin.service.impl;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.mdd.admin.config.quartz.QuartzUtils;
|
||||||
import com.mdd.admin.service.ICrontabService;
|
import com.mdd.admin.service.ICrontabService;
|
||||||
import com.mdd.admin.validate.CrontabCreateValidate;
|
import com.mdd.admin.validate.CrontabCreateValidate;
|
||||||
import com.mdd.admin.validate.CrontabUpdateValidate;
|
import com.mdd.admin.validate.CrontabUpdateValidate;
|
||||||
|
|
@ -11,12 +12,15 @@ import com.mdd.admin.vo.CrontabDetailVo;
|
||||||
import com.mdd.admin.vo.CrontabListedVo;
|
import com.mdd.admin.vo.CrontabListedVo;
|
||||||
import com.mdd.common.core.PageResult;
|
import com.mdd.common.core.PageResult;
|
||||||
import com.mdd.common.entity.Crontab;
|
import com.mdd.common.entity.Crontab;
|
||||||
import com.mdd.common.entity.server.Sys;
|
|
||||||
import com.mdd.common.mapper.CrontabMapper;
|
import com.mdd.common.mapper.CrontabMapper;
|
||||||
|
import com.mdd.common.utils.TimeUtil;
|
||||||
|
import org.quartz.*;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -27,9 +31,27 @@ import java.util.List;
|
||||||
@Service
|
@Service
|
||||||
public class CrontabServiceImpl implements ICrontabService {
|
public class CrontabServiceImpl implements ICrontabService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
Scheduler scheduler;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
CrontabMapper crontabMapper;
|
CrontabMapper crontabMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目启动初始化任务
|
||||||
|
*
|
||||||
|
* @author fzr
|
||||||
|
* @throws SchedulerException 异常
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() throws SchedulerException {
|
||||||
|
scheduler.clear();
|
||||||
|
List<Crontab> jobs = crontabMapper.selectList(new QueryWrapper<Crontab>().eq("is_delete", 0));
|
||||||
|
for (Crontab crontab : jobs) {
|
||||||
|
QuartzUtils.createScheduleJob(scheduler, crontab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划任务列表
|
* 计划任务列表
|
||||||
*
|
*
|
||||||
|
|
@ -52,6 +74,8 @@ public class CrontabServiceImpl implements ICrontabService {
|
||||||
CrontabListedVo vo = new CrontabListedVo();
|
CrontabListedVo vo = new CrontabListedVo();
|
||||||
BeanUtils.copyProperties(crontab, vo);
|
BeanUtils.copyProperties(crontab, vo);
|
||||||
|
|
||||||
|
vo.setStartTime(crontab.getStartTime()<=0?"-": TimeUtil.timestampToDate(crontab.getStartTime()));
|
||||||
|
vo.setEndTime(crontab.getEndTime()<=0?"-": TimeUtil.timestampToDate(crontab.getEndTime()));
|
||||||
list.add(vo);
|
list.add(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,16 +111,21 @@ public class CrontabServiceImpl implements ICrontabService {
|
||||||
* @param createValidate 参数
|
* @param createValidate 参数
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void add(CrontabCreateValidate createValidate) {
|
@Transactional
|
||||||
|
public void add(CrontabCreateValidate createValidate) throws SchedulerException {
|
||||||
Crontab crontab = new Crontab();
|
Crontab crontab = new Crontab();
|
||||||
crontab.setName(createValidate.getName());
|
crontab.setName(createValidate.getName());
|
||||||
crontab.setCommand(createValidate.getCommand());
|
crontab.setCommand(createValidate.getCommand());
|
||||||
crontab.setRules(createValidate.getRules());
|
crontab.setRules(createValidate.getRules());
|
||||||
crontab.setStatus(createValidate.getStatus());
|
crontab.setStatus(createValidate.getStatus());
|
||||||
crontab.setRemark(createValidate.getRemark());
|
crontab.setRemark(createValidate.getRemark());
|
||||||
|
crontab.setStrategy(createValidate.getStrategy());
|
||||||
|
crontab.setConcurrent(createValidate.getConcurrent());
|
||||||
crontab.setCreateTime(System.currentTimeMillis() / 1000);
|
crontab.setCreateTime(System.currentTimeMillis() / 1000);
|
||||||
crontab.setUpdateTime(System.currentTimeMillis() / 1000);
|
crontab.setUpdateTime(System.currentTimeMillis() / 1000);
|
||||||
crontabMapper.insert(crontab);
|
crontabMapper.insert(crontab);
|
||||||
|
|
||||||
|
QuartzUtils.createScheduleJob(scheduler, crontab);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -106,7 +135,8 @@ public class CrontabServiceImpl implements ICrontabService {
|
||||||
* @param updateValidate 参数
|
* @param updateValidate 参数
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void edit(CrontabUpdateValidate updateValidate) {
|
@Transactional
|
||||||
|
public void edit(CrontabUpdateValidate updateValidate) throws SchedulerException {
|
||||||
Crontab crontab = crontabMapper.selectOne(
|
Crontab crontab = crontabMapper.selectOne(
|
||||||
new QueryWrapper<Crontab>()
|
new QueryWrapper<Crontab>()
|
||||||
.eq("id", updateValidate.getId())
|
.eq("id", updateValidate.getId())
|
||||||
|
|
@ -117,15 +147,27 @@ public class CrontabServiceImpl implements ICrontabService {
|
||||||
|
|
||||||
crontab.setName(updateValidate.getName());
|
crontab.setName(updateValidate.getName());
|
||||||
crontab.setCommand(updateValidate.getCommand());
|
crontab.setCommand(updateValidate.getCommand());
|
||||||
|
crontab.setGroups(updateValidate.getGroups());
|
||||||
crontab.setRules(updateValidate.getRules());
|
crontab.setRules(updateValidate.getRules());
|
||||||
crontab.setStatus(updateValidate.getStatus());
|
crontab.setStatus(updateValidate.getStatus());
|
||||||
crontab.setRemark(updateValidate.getRemark());
|
crontab.setRemark(updateValidate.getRemark());
|
||||||
|
crontab.setStrategy(updateValidate.getStrategy());
|
||||||
|
crontab.setConcurrent(updateValidate.getConcurrent());
|
||||||
crontab.setUpdateTime(System.currentTimeMillis() / 1000);
|
crontab.setUpdateTime(System.currentTimeMillis() / 1000);
|
||||||
crontabMapper.updateById(crontab);
|
crontabMapper.updateById(crontab);
|
||||||
|
|
||||||
|
QuartzUtils.createScheduleJob(scheduler, crontab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划任务删除
|
||||||
|
*
|
||||||
|
* @author fzr
|
||||||
|
* @param id 主键
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void del(Integer id) {
|
@Transactional
|
||||||
|
public void del(Integer id) throws SchedulerException {
|
||||||
Crontab crontab = crontabMapper.selectOne(
|
Crontab crontab = crontabMapper.selectOne(
|
||||||
new QueryWrapper<Crontab>()
|
new QueryWrapper<Crontab>()
|
||||||
.eq("id", id)
|
.eq("id", id)
|
||||||
|
|
@ -137,6 +179,8 @@ public class CrontabServiceImpl implements ICrontabService {
|
||||||
crontab.setIsDelete(1);
|
crontab.setIsDelete(1);
|
||||||
crontab.setDeleteTime(System.currentTimeMillis() / 1000);
|
crontab.setDeleteTime(System.currentTimeMillis() / 1000);
|
||||||
crontabMapper.updateById(crontab);
|
crontabMapper.updateById(crontab);
|
||||||
|
|
||||||
|
scheduler.deleteJob(QuartzUtils.getJobKey(crontab.getId(), crontab.getGroups()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.mdd.admin.validate;
|
||||||
|
|
||||||
import com.mdd.common.validator.annotation.IntegerContains;
|
import com.mdd.common.validator.annotation.IntegerContains;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -20,9 +21,19 @@ public class CrontabCreateValidate implements Serializable {
|
||||||
@NotNull(message = "rules参数缺失")
|
@NotNull(message = "rules参数缺失")
|
||||||
private String rules;
|
private String rules;
|
||||||
|
|
||||||
|
@Length(max = 200, message = "remark参数不能超出200个字符")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@NotNull(message = "status参数缺失")
|
||||||
@IntegerContains(values = {1, 2, 3}, message = "status参数取值异常")
|
@IntegerContains(values = {1, 2, 3}, message = "status参数取值异常")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
private String remark;
|
@NotNull(message = "strategy参数缺失")
|
||||||
|
@IntegerContains(values = {1, 2, 3}, message = "strategy参数取值异常")
|
||||||
|
private Integer strategy;
|
||||||
|
|
||||||
|
@NotNull(message = "concurrent参数缺失")
|
||||||
|
@IntegerContains(values = {0, 1}, message = "concurrent参数取值异常")
|
||||||
|
private Integer concurrent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.mdd.admin.validate;
|
||||||
import com.mdd.common.validator.annotation.IDMust;
|
import com.mdd.common.validator.annotation.IDMust;
|
||||||
import com.mdd.common.validator.annotation.IntegerContains;
|
import com.mdd.common.validator.annotation.IntegerContains;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -15,6 +16,9 @@ public class CrontabUpdateValidate implements Serializable {
|
||||||
@IDMust(message = "id参数必传且需大于0")
|
@IDMust(message = "id参数必传且需大于0")
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
|
@NotNull(message = "groups参数缺失")
|
||||||
|
private String groups;
|
||||||
|
|
||||||
@NotNull(message = "name参数缺失")
|
@NotNull(message = "name参数缺失")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
|
@ -24,9 +28,19 @@ public class CrontabUpdateValidate implements Serializable {
|
||||||
@NotNull(message = "rules参数缺失")
|
@NotNull(message = "rules参数缺失")
|
||||||
private String rules;
|
private String rules;
|
||||||
|
|
||||||
|
@Length(max = 200, message = "remark参数不能超出200个字符")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@NotNull(message = "status参数缺失")
|
||||||
@IntegerContains(values = {1, 2, 3}, message = "status参数取值异常")
|
@IntegerContains(values = {1, 2, 3}, message = "status参数取值异常")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
private String remark;
|
@NotNull(message = "strategy参数缺失")
|
||||||
|
@IntegerContains(values = {1, 2, 3}, message = "strategy参数取值异常")
|
||||||
|
private Integer strategy;
|
||||||
|
|
||||||
|
@NotNull(message = "concurrent参数缺失")
|
||||||
|
@IntegerContains(values = {0, 1}, message = "concurrent参数取值异常")
|
||||||
|
private Integer concurrent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,23 @@ import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划任务详情Vo
|
||||||
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class CrontabDetailVo implements Serializable {
|
public class CrontabDetailVo implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private Integer id;
|
private Integer id; // 任务主键
|
||||||
private String name;
|
private String groups; // 任务分组
|
||||||
private String command;
|
private String name; // 任务名称
|
||||||
private String rules;
|
private String command; // 执行命令
|
||||||
private Integer status;
|
private String rules; // 执行规则
|
||||||
|
private String remark; // 备注信息
|
||||||
|
private String error; // 错误信息
|
||||||
|
private Integer status; // 执行状态: 1=正在运行, 2=任务停止, 3=发生错误
|
||||||
|
private Integer strategy; // 执行策略: 1=立即执行, 2=执行一次, 3=放弃执行
|
||||||
|
private Integer concurrent; // 并发执行: 0=否, 1=是
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,25 @@ import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划任务列表Vo
|
||||||
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class CrontabListedVo implements Serializable {
|
public class CrontabListedVo implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private Integer id;
|
private Integer id; // 执行ID
|
||||||
private String name;
|
private String groups; // 执行分组
|
||||||
private String command;
|
private String name; // 执行名称
|
||||||
private String rules;
|
private String command; // 执行命令
|
||||||
private Integer status;
|
private String rules; // 执行规则
|
||||||
|
private String error; // 错误信息
|
||||||
|
private Integer status; // 执行状态: 1=正在运行, 2=任务停止, 3=发生错误
|
||||||
|
private Integer strategy; // 执行策略: 1=立即执行, 2=执行一次, 3=放弃执行
|
||||||
|
private Integer concurrent; // 并发执行: 0=否, 1=是
|
||||||
|
private String startTime; // 开始时间
|
||||||
|
private String endTime; // 结束时间
|
||||||
|
private Long taskTime; // 执行耗时
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,22 @@ public class Crontab implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@TableId(value="id", type= IdType.AUTO)
|
@TableId(value="id", type= IdType.AUTO)
|
||||||
private Integer id; // 主键
|
private Integer id; // 主键
|
||||||
private String name; // 任务名称
|
private String name; // 任务名称
|
||||||
private String command; // 任务命令
|
private String groups; // 任务分组
|
||||||
private String rules; // 任务规则
|
private String command; // 执行命令
|
||||||
private String remark; // 备注信息
|
private String rules; // 执行规则
|
||||||
private Integer status; // 执行状态:1=运行, 2-停止, 3=错误
|
private String remark; // 备注信息
|
||||||
private Integer isDelete; // 是否删除: 0=否, 1=是
|
private String error; // 错误信息
|
||||||
private Long createTime; // 创建时间
|
private Integer status; // 执行状态: 1=正在运行, 2=任务停止, 3=发生错误
|
||||||
private Long updateTime; // 更新时间
|
private Integer strategy; // 执行策略: 1=立即执行, 2=执行一次, 3=放弃执行
|
||||||
private Long deleteTime; // 删除时间
|
private Integer concurrent; // 并发执行: 0=否, 1=是
|
||||||
|
private Integer isDelete; // 是否删除: 0=否, 1=是
|
||||||
|
private Long startTime; // 开始时间
|
||||||
|
private Long endTime; // 结束时间
|
||||||
|
private Long taskTime; // 任务耗时
|
||||||
|
private Long createTime; // 创建时间
|
||||||
|
private Long updateTime; // 更新时间
|
||||||
|
private Long deleteTime; // 删除时间
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,9 @@
|
||||||
<bitwalker.version>1.2.4</bitwalker.version>
|
<bitwalker.version>1.2.4</bitwalker.version>
|
||||||
<oshi-core.version>6.1.2</oshi-core.version>
|
<oshi-core.version>6.1.2</oshi-core.version>
|
||||||
<sa-token.version>1.32.0</sa-token.version>
|
<sa-token.version>1.32.0</sa-token.version>
|
||||||
|
<sa-token-redis.version>1.32.0</sa-token-redis.version>
|
||||||
<easyexcel.version>3.1.3</easyexcel.version>
|
<easyexcel.version>3.1.3</easyexcel.version>
|
||||||
|
<quartz-scheduler.version>2.3.2</quartz-scheduler.version>
|
||||||
|
|
||||||
<qiniu.version>7.9.5</qiniu.version>
|
<qiniu.version>7.9.5</qiniu.version>
|
||||||
<qcloud-version>5.6.54</qcloud-version>
|
<qcloud-version>5.6.54</qcloud-version>
|
||||||
|
|
@ -121,6 +123,20 @@
|
||||||
<version>${sa-token.version}</version>
|
<version>${sa-token.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SaToken-Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||||
|
<version>${sa-token-redis.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Quartz-Scheduler -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
|
<artifactId>quartz</artifactId>
|
||||||
|
<version>${quartz-scheduler.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Gson工具 -->
|
<!-- Gson工具 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue