feat: 定时任务失效优化,并补充功能

This commit is contained in:
2026-11-13 17:56:58 +08:00
parent 63fc01cbb3
commit d5077d092c
16 changed files with 864 additions and 869 deletions
+12 -12
View File
@@ -5,6 +5,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
import com.budwk.app.base.utils.AppIocUtil; import com.budwk.app.base.utils.AppIocUtil;
import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.services.SysTaskService; import com.budwk.app.sys.services.SysTaskService;
import com.budwk.app.task.log.TaskExecutionListener;
import com.budwk.app.task.services.TaskPlatformService; import com.budwk.app.task.services.TaskPlatformService;
import com.budwk.app.web.commons.auth.satoken.SaTokenContextImpl; import com.budwk.app.web.commons.auth.satoken.SaTokenContextImpl;
import com.budwk.app.web.commons.auth.satoken.SaTokenDaoRedisImpl; import com.budwk.app.web.commons.auth.satoken.SaTokenDaoRedisImpl;
@@ -20,7 +21,6 @@ import org.beetl.core.GroupTemplate;
import org.nutz.boot.NbApp; import org.nutz.boot.NbApp;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.impl.FileSqlManager; import org.nutz.dao.impl.FileSqlManager;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos; import org.nutz.dao.util.Daos;
import org.nutz.integration.jedis.JedisAgent; import org.nutz.integration.jedis.JedisAgent;
import org.nutz.ioc.Ioc; import org.nutz.ioc.Ioc;
@@ -31,6 +31,7 @@ import org.nutz.lang.Mirror;
import org.nutz.mvc.Mvcs; import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.*; import org.nutz.mvc.annotation.*;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
@@ -39,7 +40,6 @@ import java.lang.management.ManagementFactory;
import java.sql.Driver; import java.sql.Driver;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
@IocBean(create = "init", depose = "depose") @IocBean(create = "init", depose = "depose")
@Localization(value = "locales/", defaultLocalizationKey = "zh_CN") @Localization(value = "locales/", defaultLocalizationKey = "zh_CN")
@@ -82,6 +82,7 @@ public class MainLauncher {
Mvcs.DISABLE_X_POWERED_BY = true; Mvcs.DISABLE_X_POWERED_BY = true;
Globals.AppBase = Mvcs.getServletContext().getContextPath(); Globals.AppBase = Mvcs.getServletContext().getContextPath();
Globals.AppRoot = Mvcs.getServletContext().getRealPath("/"); Globals.AppRoot = Mvcs.getServletContext().getRealPath("/");
init_task();
init_sys(); init_sys();
ioc.get(com.budwk.app.zhgh.staffbenefit.therapyRecuperation.TherapyRecuperationModule.class); ioc.get(com.budwk.app.zhgh.staffbenefit.therapyRecuperation.TherapyRecuperationModule.class);
// 2026年10月3日 12:01:28;操作人:wxg:先升级会员表及补齐历史资料,再处理缴费快照和启动计划任务;不依赖 DEBUG。 // 2026年10月3日 12:01:28;操作人:wxg:先升级会员表及补齐历史资料,再处理缴费快照和启动计划任务;不依赖 DEBUG。
@@ -93,7 +94,6 @@ public class MainLauncher {
ClubPayRecordService payRecords = ioc.get(ClubPayRecordService.class, "clubPayRecordServiceImpl"); ClubPayRecordService payRecords = ioc.get(ClubPayRecordService.class, "clubPayRecordServiceImpl");
payRecords.upgradePayRecordSchema(); payRecords.upgradePayRecordSchema();
payRecords.backfillPayRecordSnapshots(); payRecords.backfillPayRecordSnapshots();
init_task();
init_auth(); init_auth();
ioc.get(Globals.class); ioc.get(Globals.class);
ioc.get(FlowEngine.class); ioc.get(FlowEngine.class);
@@ -116,17 +116,17 @@ public class MainLauncher {
} }
private void init_task() { private void init_task() {
if (log.isDebugEnabled() && !dao.exists("sys_qrtz_triggers") && !dao.exists("sys_qrtz_triggers".toUpperCase())) { FileSqlManager schema = new FileSqlManager("db/quartz/mysql.sql");
//执行Quartz SQL脚本 for (String table : schema.keys()) {
String dbType = dao.getJdbcExpert().getDatabaseType(); if (!dao.exists(table)) {
log.debug("dbType:::" + dbType); dao.execute(schema.create(table));
FileSqlManager fmq = new FileSqlManager("db/quartz/" + dbType.toLowerCase() + ".sql");
List<Sql> sqlListq = fmq.createCombo(fmq.keys());
Sql[] sqlsq = sqlListq.toArray(new Sql[sqlListq.size()]);
for (Sql sql : sqlsq) {
dao.execute(sql);
} }
} }
try {
ioc.get(TaskExecutionListener.class).register();
} catch (SchedulerException e) {
throw new IllegalStateException("注册任务执行记录监听器失败", e);
}
} }
private void init_sys() { private void init_sys() {
@@ -2,15 +2,18 @@ package com.budwk.app.sys.controller;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil; import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_task; import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.sys.models.Sys_task_execution;
import com.budwk.app.sys.services.SysTaskService; import com.budwk.app.sys.services.SysTaskService;
import com.budwk.app.task.services.TaskPlatformService; import com.budwk.app.task.services.TaskPlatformService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.ioc.aop.Aop; import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
@@ -20,8 +23,10 @@ import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST; import org.nutz.mvc.annotation.POST;
import org.nutz.mvc.annotation.Param; import org.nutz.mvc.annotation.Param;
import org.nutz.trans.Trans;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.List;
; ;
@@ -33,6 +38,8 @@ public class SysTaskController {
private SysTaskService sysTaskService; private SysTaskService sysTaskService;
@Inject @Inject
private TaskPlatformService taskPlatformService; private TaskPlatformService taskPlatformService;
@Inject
private Dao dao;
@At("") @At("")
@Ok("beetl:/platform/sys/task/index.html") @Ok("beetl:/platform/sys/task/index.html")
@@ -42,10 +49,10 @@ public class SysTaskController {
} }
@At @At
@Ok("beetl:/platform/sys/task/cron.html") @Ok("beetl:/platform/sys/task/index.html")
@SaCheckPermission("sys.manager.task") @SaCheckPermission("sys.manager.task")
public void cron() { public void cron(HttpServletRequest req) {
req.setAttribute("openCron", true);
} }
@At @At
@@ -53,9 +60,9 @@ public class SysTaskController {
@SaCheckPermission("sys.manager.task") @SaCheckPermission("sys.manager.task")
public Object cronExp(@Param("cron") String cron) { public Object cronExp(@Param("cron") String cron) {
try { try {
return Result.success().addData(taskPlatformService.getCronExeTimes(cron)); return Result.success().addData(previewCron(normalizeCron(cron)));
} catch (Exception e) { } catch (Exception e) {
return Result.error(e.getMessage()); return taskError("获取执行时间失败", e);
} }
} }
@@ -74,20 +81,57 @@ public class SysTaskController {
} }
} }
@At("/history/?")
@Ok("json")
@SaCheckPermission("sys.manager.task")
public Object history(String taskId, @Param("pageNumber") int pageNumber) {
try {
requireTask(taskId);
int page = Math.max(1, Math.min(pageNumber, Integer.MAX_VALUE / 10));
Cnd condition = Cnd.where("taskId", "=", taskId);
condition.desc("startedAt").desc("id");
List<Sys_task_execution> list = dao.query(Sys_task_execution.class, condition, dao.createPager(page, 10),
"^(id|taskId|startedAt|finishedAt|status)$");
int total = dao.count(Sys_task_execution.class, Cnd.where("taskId", "=", taskId));
return Result.success(new Pagination<>(page, 10, total, list));
} catch (Exception e) {
return taskError("获取执行历史失败", e);
}
}
@At("/historyLog/?/?")
@Ok("json")
@SaCheckPermission("sys.manager.task")
public Object historyLog(String taskId, String executionId) {
try {
requireTask(taskId);
Sys_task_execution execution = dao.fetch(Sys_task_execution.class,
Cnd.where("id", "=", executionId).and("taskId", "=", taskId));
return execution == null ? Result.error("执行记录不存在") : Result.success(execution);
} catch (Exception e) {
return taskError("获取执行日志失败", e);
}
}
@At @At
@Ok("json") @Ok("json")
@SLog(tag = "新建任务", msg = "任务名称:${args[0].name}") @SLog(tag = "新建任务", msg = "任务名称:${args[0].name}")
@SaCheckPermission("sys.manager.task.add") @SaCheckPermission("sys.manager.task.add")
@Aop(TransAop.READ_COMMITTED)
public Object addDo(@Param("..") Sys_task task, HttpServletRequest req) { public Object addDo(@Param("..") Sys_task task, HttpServletRequest req) {
try { try {
task.setCron(validateCron(task.getCron()));
task.setCreatedBy(SecurityUtil.getUserId()); task.setCreatedBy(SecurityUtil.getUserId());
Sys_task sysTask = sysTaskService.insert(task); task.setExeAt(null);
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron(), task.setExeResult(null);
sysTask.getNote(), sysTask.getData()); Trans.exec(() -> {
Sys_task saved = sysTaskService.insert(task);
if (!saved.isDisabled()) {
schedule(saved);
}
});
return Result.success(); return Result.success();
} catch (Exception e) { } catch (Exception e) {
return Result.error(); return taskError("新建任务失败", e);
} }
} }
@@ -108,18 +152,24 @@ public class SysTaskController {
@SaCheckPermission("sys.manager.task.edit") @SaCheckPermission("sys.manager.task.edit")
public Object editDo(@Param("..") Sys_task task, HttpServletRequest req) { public Object editDo(@Param("..") Sys_task task, HttpServletRequest req) {
try { try {
if (task.getCron() != null) {
task.setCron(validateCron(task.getCron()));
}
task.setUpdatedBy(SecurityUtil.getUserId()); task.setUpdatedBy(SecurityUtil.getUserId());
sysTaskService.updateIgnoreNull(task); Trans.exec(() -> {
if (taskPlatformService.isExist(task.getId(), task.getId())) { requireTask(task.getId());
taskPlatformService.delete(task.getId(), task.getId()); FieldFilter.locked(Sys_task.class, "^(exeAt|exeResult)$")
} .run(() -> sysTaskService.updateIgnoreNull(task));
if (!task.isDisabled()) { Sys_task saved = requireTask(task.getId());
taskPlatformService.add(task.getId(), task.getId(), task.getJobClass(), task.getCron(), if (saved.isDisabled()) {
task.getNote(), task.getData()); taskPlatformService.delete(saved.getId(), saved.getId());
} else {
schedule(saved);
} }
});
return Result.success(); return Result.success();
} catch (Exception e) { } catch (Exception e) {
return Result.error(); return taskError("修改任务失败", e);
} }
} }
@@ -130,19 +180,15 @@ public class SysTaskController {
@SaCheckPermission("sys.manager.task.delete") @SaCheckPermission("sys.manager.task.delete")
public Object delete(String id, HttpServletRequest req) { public Object delete(String id, HttpServletRequest req) {
try { try {
Sys_task sysTask = sysTaskService.fetch(id); Trans.exec(() -> {
try { Sys_task task = requireTask(id);
if (taskPlatformService.isExist(sysTask.getId(), sysTask.getId())) {
taskPlatformService.delete(sysTask.getId(), sysTask.getId());
}
} catch (Exception e) {
log.error(e.getMessage());
}
sysTaskService.delete(id); sysTaskService.delete(id);
req.setAttribute("name", sysTask.getName()); taskPlatformService.delete(id, id);
req.setAttribute("name", task.getName());
});
return Result.success(); return Result.success();
} catch (Exception e) { } catch (Exception e) {
return Result.error(); return taskError("删除任务失败", e);
} }
} }
@@ -171,18 +217,7 @@ public class SysTaskController {
@SaCheckPermission("sys.manager.task.edit") @SaCheckPermission("sys.manager.task.edit")
@SLog(tag = "启用任务", msg = "任务名:${args[1].getAttribute('name')}") @SLog(tag = "启用任务", msg = "任务名:${args[1].getAttribute('name')}")
public Object enable(String id, HttpServletRequest req) { public Object enable(String id, HttpServletRequest req) {
try { return changeDisabled(id, false, req);
Sys_task sysTask = sysTaskService.fetch(id);
req.setAttribute("name", sysTask.getName());
sysTaskService.update(org.nutz.dao.Chain.make("disabled", false), Cnd.where("id", "=", id));
if (!taskPlatformService.isExist(sysTask.getId(), sysTask.getId())) {
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron(),
sysTask.getNote(), sysTask.getData());
}
return Result.success();
} catch (Exception e) {
return Result.error();
}
} }
@At("/disable/?") @At("/disable/?")
@@ -190,16 +225,66 @@ public class SysTaskController {
@SaCheckPermission("sys.manager.task.edit") @SaCheckPermission("sys.manager.task.edit")
@SLog(tag = "禁用任务", msg = "任务名:${args[1].getAttribute('name')}") @SLog(tag = "禁用任务", msg = "任务名:${args[1].getAttribute('name')}")
public Object disable(String id, HttpServletRequest req) { public Object disable(String id, HttpServletRequest req) {
try { return changeDisabled(id, true, req);
Sys_task sysTask = sysTaskService.fetch(id);
req.setAttribute("name", sysTask.getName());
sysTaskService.update(org.nutz.dao.Chain.make("disabled", true), Cnd.where("id", "=", id));
if (taskPlatformService.isExist(sysTask.getId(), sysTask.getId())) {
taskPlatformService.delete(sysTask.getId(), sysTask.getId());
} }
private Object changeDisabled(String id, boolean disabled, HttpServletRequest req) {
try {
Trans.exec(() -> {
sysTaskService.update(Chain.make("disabled", disabled), Cnd.where("id", "=", id));
Sys_task task = requireTask(id);
req.setAttribute("name", task.getName());
if (disabled) {
taskPlatformService.delete(id, id);
} else {
schedule(task);
}
});
return Result.success(); return Result.success();
} catch (Exception e) { } catch (Exception e) {
return Result.error(); return taskError(disabled ? "禁用任务失败" : "启用任务失败", e);
} }
} }
private Sys_task requireTask(String id) {
Sys_task task = Strings.isBlank(id) ? null : sysTaskService.fetch(id);
if (task == null) {
throw new IllegalArgumentException("任务不存在");
}
return task;
}
private void schedule(Sys_task task) {
taskPlatformService.add(task.getId(), task.getId(), task.getJobClass(), task.getCron(),
task.getNote(), task.getData());
}
private String normalizeCron(String cron) {
return Strings.sNull(cron).trim().replaceAll("\\s+", " ");
}
private String validateCron(String cron) throws Exception {
String normalized = normalizeCron(cron);
previewCron(normalized);
return normalized;
}
private List<String> previewCron(String normalized) throws Exception {
if (normalized.length() > 50) {
throw new IllegalArgumentException("Cron 表达式最多 50 个字符");
}
List<String> times = taskPlatformService.getCronExeTimes(normalized);
if (times.isEmpty()) {
throw new IllegalArgumentException("Cron 表达式没有未来执行时间");
}
return times;
}
private Result taskError(String message, Exception e) {
if (e instanceof IllegalArgumentException) {
return Result.error(e.getMessage());
}
log.error(message, e);
return Result.error(message);
}
} }
@@ -0,0 +1,37 @@
package com.budwk.app.sys.models;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
@Data
@Table("sys_task_execution")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@TableIndexes(@Index(name = "IDX_SYS_TASK_EXECUTION_STARTED", fields = {"taskId", "startedAt"}))
public class Sys_task_execution {
public enum Status {
RUNNING, SUCCESS, FAILED, SKIPPED, INTERRUPTED
}
@Name
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String taskId;
@Column
private long startedAt;
@Column
private Long finishedAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 16)
private String status;
@Column
@ColDefine(customType = "MEDIUMTEXT")
private String executionLog;
}
@@ -27,6 +27,6 @@ public class TestJob implements Job {
String taskId = context.getJobDetail().getKey().getName(); String taskId = context.getJobDetail().getKey().getName();
String hi = data.getString("hi"); String hi = data.getString("hi");
log.info("Test Job hi::" + hi); log.info("Test Job hi::" + hi);
sysTaskService.update(Chain.make("exeAt", (int) (System.currentTimeMillis() / 1000)).add("exeResult", "执行成功"), Cnd.where("id", "=", taskId)); sysTaskService.update(Chain.make("exeResult", "执行成功"), Cnd.where("id", "=", taskId));
} }
} }
@@ -0,0 +1,108 @@
package com.budwk.app.task.log;
import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.sys.models.Sys_task_execution;
import com.budwk.app.sys.models.Sys_task_execution.Status;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.trans.Trans;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.listeners.JobListenerSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.UUID;
@IocBean
public class TaskExecutionListener extends JobListenerSupport {
private static final Logger log = LoggerFactory.getLogger(TaskExecutionListener.class);
@Inject
private Dao dao;
@Inject
private Scheduler scheduler;
public void register() throws SchedulerException {
if (!dao.exists(Sys_task_execution.class)) {
dao.create(Sys_task_execution.class, false);
}
dao.update(Sys_task_execution.class, Chain.make("status", Status.INTERRUPTED.name()),
Cnd.where("status", "=", Status.RUNNING.name()));
scheduler.getListenerManager().addJobListener(this);
}
@Override
public String getName() {
return "sysTaskExecutionListener";
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
JobKey key = context.getJobDetail().getKey();
context.put(getName(), null);
if (!key.getName().equals(key.getGroup())) {
return;
}
try {
if (dao.fetch(Sys_task.class, key.getName()) == null) {
return;
}
Sys_task_execution execution = new Sys_task_execution();
execution.setId(UUID.randomUUID().toString().replace("-", ""));
execution.setTaskId(key.getName());
execution.setStartedAt(System.currentTimeMillis());
execution.setStatus(Status.RUNNING.name());
Trans.exec(() -> {
dao.insert(execution);
long startedAtSeconds = execution.getStartedAt() / 1000;
dao.execute(Sqls.create("UPDATE sys_task SET exeAt = @startedAt WHERE id = @id AND (exeAt IS NULL OR exeAt < @startedAt)")
.setParam("startedAt", startedAtSeconds).setParam("id", execution.getTaskId()));
});
context.put(getName(), execution.getId());
context.setResult(null);
TaskExecutionLogAppender.begin();
log.info("开始执行任务 {}", key.getName());
} catch (RuntimeException e) {
log.error("记录任务开始时间失败,任务 {}", key, e);
}
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException exception) {
String id = (String) context.get(getName());
if (id == null) {
return;
}
Status status = exception != null ? Status.FAILED
: context.getResult() instanceof Status result ? result : Status.SUCCESS;
String executionLog = TaskExecutionLogAppender.end("\n任务执行结束,状态 " + status + "\n");
try {
if (exception != null) {
StringWriter stack = new StringWriter();
exception.printStackTrace(new PrintWriter(stack));
String detail = stack.toString();
if (detail.length() > 8000) {
int end = Character.isHighSurrogate(detail.charAt(7999)) ? 7999 : 8000;
detail = detail.substring(0, end) + "\n[异常堆栈已截断]";
}
executionLog += (exception.refireImmediately() ? "Quartz 将立即重试\n" : "") + detail;
}
dao.update(Sys_task_execution.class,
Chain.make("status", status.name()).add("finishedAt", System.currentTimeMillis())
.add("executionLog", executionLog),
Cnd.where("id", "=", id));
} catch (RuntimeException e) {
log.error("保存任务执行结果失败,记录 {}", id, e);
}
}
}
@@ -0,0 +1,49 @@
package com.budwk.app.task.log;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class TaskExecutionLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
private static final int MAX_LENGTH = 16000;
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
.withZone(ZoneId.of("Asia/Shanghai"));
private static final ThreadLocal<StringBuilder> CURRENT_LOG = new ThreadLocal<>();
static void begin() {
CURRENT_LOG.set(new StringBuilder());
}
static String end(String outcome) {
StringBuilder log = CURRENT_LOG.get();
CURRENT_LOG.remove();
return (log == null ? "" : log.toString()) + outcome;
}
@Override
protected void append(ILoggingEvent event) {
StringBuilder log = CURRENT_LOG.get();
if (log == null || log.length() >= MAX_LENGTH || !event.getLevel().isGreaterOrEqual(Level.INFO)) {
return;
}
log.append(TIME_FORMAT.format(Instant.ofEpochMilli(event.getTimeStamp())))
.append(" ").append(event.getLevel()).append(" ").append(event.getLoggerName())
.append(" - ").append(event.getFormattedMessage()).append('\n');
if (event.getThrowableProxy() != null) {
log.append(ThrowableProxyUtil.asString(event.getThrowableProxy()));
}
if (log.length() >= MAX_LENGTH) {
int end = MAX_LENGTH;
if (Character.isHighSurrogate(log.charAt(end - 1))) {
end--;
}
log.setLength(end);
log.append("\n[后续日志已截断]\n");
}
}
}
@@ -19,6 +19,8 @@ public interface TaskPlatformService {
void runOnce(Sys_task task) throws Exception; void runOnce(Sys_task task) throws Exception;
void restore(Sys_task task) throws Exception;
/** /**
* 添加新任务 * 添加新任务
* *
@@ -2,7 +2,6 @@ package com.budwk.app.task.services.impl;
import com.budwk.app.sys.models.Sys_task; import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.task.services.TaskPlatformService; import com.budwk.app.task.services.TaskPlatformService;
import org.nutz.integration.quartz.QuartzJob;
import org.nutz.integration.quartz.QuartzManager; import org.nutz.integration.quartz.QuartzManager;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
@@ -10,27 +9,33 @@ import org.nutz.json.Json;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.quartz.CronExpression; import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobBuilder; import org.quartz.JobBuilder;
import org.quartz.JobDataMap; import org.quartz.JobDataMap;
import org.quartz.JobDetail; import org.quartz.JobDetail;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder; import org.quartz.TriggerBuilder;
import org.quartz.TriggerUtils; import org.quartz.TriggerKey;
import org.quartz.impl.triggers.CronTriggerImpl;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TimeZone;
/** /**
* Created by wizzer on 2018/3/19. * Created by wizzer on 2018/3/19.
*/ */
@IocBean @IocBean
public class TaskPlatformServiceImpl implements TaskPlatformService { public class TaskPlatformServiceImpl implements TaskPlatformService {
private static final TimeZone TASK_TIME_ZONE = TimeZone.getTimeZone("Asia/Shanghai");
@Inject @Inject
private QuartzManager quartzManager; private QuartzManager quartzManager;
@Inject @Inject
@@ -55,18 +60,30 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
return; return;
} }
Class<? extends Job> jobClass = Class.forName(task.getJobClass()).asSubclass(Job.class); JobDetail job = buildJob(key, task.getJobClass(), task.getData());
scheduler.scheduleJob(job, TriggerBuilder.newTrigger().forJob(key).startNow().build());
}
private JobDetail buildJob(JobKey key, String className, String dataMap) {
Class<? extends Job> jobClass;
try {
jobClass = Class.forName(Strings.sNull(className).trim(), false, getClass().getClassLoader())
.asSubclass(Job.class);
} catch (ClassNotFoundException | ClassCastException e) {
throw new IllegalArgumentException("执行类必须是有效的 Quartz Job", e);
}
JobDataMap data = new JobDataMap(); JobDataMap data = new JobDataMap();
if (Strings.isNotBlank(task.getData())) { if (Strings.isNotBlank(dataMap)) {
NutMap params = Json.fromJson(NutMap.class, task.getData()); try {
NutMap params = Json.fromJson(NutMap.class, dataMap);
if (params != null) { if (params != null) {
data.putAll(params); data.putAll(params);
} }
} catch (RuntimeException e) {
throw new IllegalArgumentException("执行参数必须是有效的 JSON 对象", e);
} }
}
// 没有注册定时调度时,临时注册一个执行后自动移除的单次任务。 return JobBuilder.newJob(jobClass).withIdentity(key).usingJobData(data).build();
JobDetail job = JobBuilder.newJob(jobClass).withIdentity(key).usingJobData(data).build();
scheduler.scheduleJob(job, TriggerBuilder.newTrigger().forJob(key).startNow().build());
} }
/** /**
@@ -80,14 +97,55 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
* @param dataMap * @param dataMap
*/ */
public void add(String jobName, String jobGroup, String className, String cron, String comment, String dataMap) { public void add(String jobName, String jobGroup, String className, String cron, String comment, String dataMap) {
QuartzJob qj = new QuartzJob(); JobKey key = JobKey.jobKey(jobName, jobGroup);
qj.setJobName(jobName); JobDetail job = buildJob(key, className, dataMap);
qj.setJobGroup(jobGroup); CronExpression expression = parseCron(cron);
qj.setClassName(className); if (expression.getNextValidTimeAfter(new Date()) == null) {
qj.setCron(cron); throw new IllegalArgumentException("Cron 表达式没有未来执行时间");
qj.setComment(comment); }
qj.setDataMap(dataMap); schedule(job, expression);
quartzManager.add(qj); }
@Override
public void restore(Sys_task task) throws SchedulerException {
JobKey key = JobKey.jobKey(task.getId(), task.getId());
if (task.isDisabled()) {
scheduler.deleteJob(key);
return;
}
CronExpression expression = parseCron(task.getCron());
JobDetail job = buildJob(key, task.getJobClass(), task.getData());
JobDetail existingJob = scheduler.getJobDetail(key);
if (existingJob != null
&& existingJob.getJobClass().equals(job.getJobClass())
&& existingJob.getJobDataMap().equals(job.getJobDataMap())
&& scheduler.getTrigger(TriggerKey.triggerKey(key.getName(), key.getGroup())) instanceof CronTrigger trigger
&& trigger.getCronExpression().equals(expression.getCronExpression())
&& trigger.getTimeZone().hasSameRules(TASK_TIME_ZONE)
&& trigger.getNextFireTime() != null) {
scheduler.resetTriggerFromErrorState(trigger.getKey());
return;
}
if (expression.getNextValidTimeAfter(new Date()) != null) {
schedule(job, expression);
} else if (existingJob != null) {
scheduler.deleteJob(key);
}
}
private void schedule(JobDetail job, CronExpression expression) {
JobKey key = job.getKey();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(key.getName(), key.getGroup())
.forJob(key)
.withSchedule(CronScheduleBuilder.cronSchedule(expression))
.build();
try {
scheduler.scheduleJob(job, Set.of(trigger), true);
} catch (SchedulerException e) {
throw new IllegalStateException("Quartz 调度注册失败", e);
}
} }
/** /**
@@ -98,10 +156,7 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
* @return * @return
*/ */
public boolean delete(String jobName, String jobGroup) { public boolean delete(String jobName, String jobGroup) {
QuartzJob qj = new QuartzJob(); return quartzManager.delete(JobKey.jobKey(jobName, jobGroup));
qj.setJobName(jobName);
qj.setJobGroup(jobGroup);
return quartzManager.delete(qj);
} }
/** /**
@@ -117,36 +172,40 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
* @param cronExpression * @param cronExpression
* @return * @return
*/ */
public List<String> getCronExeTimes(String cronExpression) throws Exception { @Override
List<String> list = new ArrayList<>(); public List<String> getCronExeTimes(String cronExpression) {
CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); CronExpression expression = parseCron(cronExpression);
cronTriggerImpl.setCronExpression(cronExpression);
Calendar calendar = Calendar.getInstance();
Date now = calendar.getTime();
calendar.add(2, 1);
List<Date> dates = TriggerUtils.computeFireTimesBetween(cronTriggerImpl, null, now, calendar.getTime());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < dates.size() && i <= 4; ++i) { dateFormat.setTimeZone(TASK_TIME_ZONE);
list.add(dateFormat.format((Date) dates.get(i))); List<String> times = new ArrayList<>(5);
Date time = new Date();
for (int i = 0; i < 5; i++) {
time = expression.getNextValidTimeAfter(time);
if (time == null) {
break;
} }
return list; times.add(dateFormat.format(time));
}
return times;
} }
@Override @Override
public List<String> getCronExeTimesPlus(String cronExpression) throws Exception { public List<String> getCronExeTimesPlus(String cronExpression) {
// 创建一个列表来存储执行时间 return getCronExeTimes(cronExpression);
List<String> executionTimes = new ArrayList<>(); }
// 创建 CronTriggerImpl 对象,并设置 cron 表达式
CronTriggerImpl cronTrigger = new CronTriggerImpl(); private CronExpression parseCron(String cron) {
cronTrigger.setCronExpression(new CronExpression(cronExpression)); String normalized = Strings.sNull(cron).trim().replaceAll("\\s+", " ");
// 计算未来的执行时间 int fieldCount = normalized.split(" ").length;
List<Date> nextExecutionTimes = TriggerUtils.computeFireTimes(cronTrigger, null, 5); if (fieldCount < 6 || fieldCount > 7) {
// 格式化日期并添加到执行时间列表中 throw new IllegalArgumentException("Quartz Cron 表达式必须包含 6 或 7 个字段");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }
for (Date date : nextExecutionTimes) { try {
executionTimes.add(dateFormat.format(date)); CronExpression expression = new CronExpression(normalized);
expression.setTimeZone(TASK_TIME_ZONE);
return expression;
} catch (ParseException e) {
throw new IllegalArgumentException("无效的 Quartz Cron 表达式:" + e.getMessage(), e);
} }
return executionTimes;
} }
} }
@@ -16,6 +16,8 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -25,6 +27,7 @@ import java.util.Set;
@IocBean(create = "init") @IocBean(create = "init")
public class Globals { public class Globals {
private static final Log log = Logs.get();
//项目路径 //项目路径
public static String AppRoot = ""; public static String AppRoot = "";
//项目目录 //项目目录
@@ -69,15 +72,12 @@ public class Globals {
} }
public void initTask(SysTaskService sysTaskService) { public void initTask(SysTaskService sysTaskService) {
taskPlatformService.clear();
List<Sys_task> taskList = sysTaskService.query(); List<Sys_task> taskList = sysTaskService.query();
for (Sys_task sysTask : taskList) { for (Sys_task sysTask : taskList) {
try { try {
if (!sysTask.isDisabled())//不存在则新建 taskPlatformService.restore(sysTask);
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron()
, sysTask.getNote(), sysTask.getData());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error("恢复定时任务失败:" + sysTask.getName() + " (" + sysTask.getId() + ")", e);
} }
} }
} }
@@ -1,5 +1,6 @@
package com.budwk.app.zhgh.personGovernance.apiConfig.job; package com.budwk.app.zhgh.personGovernance.apiConfig.job;
import com.budwk.app.sys.models.Sys_task_execution.Status;
import com.budwk.app.zhgh.personGovernance.apiConfig.service.PersonGovernanceSyncService; import com.budwk.app.zhgh.personGovernance.apiConfig.service.PersonGovernanceSyncService;
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiConfig; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiConfig;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
@@ -29,6 +30,7 @@ public class PersonGovernanceSyncJob implements Job {
public void execute(JobExecutionContext context) { public void execute(JobExecutionContext context) {
String id = context.getMergedJobDataMap().getString("configId"); String id = context.getMergedJobDataMap().getString("configId");
if (Strings.isBlank(id)) { if (Strings.isBlank(id)) {
context.setResult(Status.SKIPPED);
Logs.get().warn("人员同步任务缺少configId,请停用旧扫描任务并通过API配置重新启用"); Logs.get().warn("人员同步任务缺少configId,请停用旧扫描任务并通过API配置重新启用");
return; return;
} }
@@ -36,18 +38,25 @@ public class PersonGovernanceSyncJob implements Job {
if (config == null || Boolean.TRUE.equals(config.getDelFlag()) || !Boolean.TRUE.equals(config.getEnabled()) if (config == null || Boolean.TRUE.equals(config.getDelFlag()) || !Boolean.TRUE.equals(config.getEnabled())
|| !Objects.equals(config.getTaskId(), context.getJobDetail().getKey().getName()) || !Objects.equals(config.getTaskId(), context.getJobDetail().getKey().getName())
|| !Objects.equals(config.getTaskCron(), context.getMergedJobDataMap().getString("taskCron"))) { || !Objects.equals(config.getTaskCron(), context.getMergedJobDataMap().getString("taskCron"))) {
context.setResult(Status.SKIPPED);
Logs.get().warn("跳过已停用或已被替换的人员同步任务:" + context.getJobDetail().getKey()); Logs.get().warn("跳过已停用或已被替换的人员同步任务:" + context.getJobDetail().getKey());
return; return;
} }
try { try {
// 重启恢复的旧式Quartz触发器也不允许补执行非周期时间点的misfire。 // 重启恢复的旧式Quartz触发器也不允许补执行非周期时间点的misfire。
if (context.getScheduledFireTime() == null if (context.getScheduledFireTime() == null
|| !new CronExpression(config.getTaskCron()).isSatisfiedBy(context.getScheduledFireTime())) return; || !new CronExpression(config.getTaskCron()).isSatisfiedBy(context.getScheduledFireTime())) {
context.setResult(Status.SKIPPED);
Logs.get().info("跳过非计划时间的人员同步触发");
return;
}
var batch = personGovernanceSyncService.execute(id, "SCHEDULED"); var batch = personGovernanceSyncService.execute(id, "SCHEDULED");
if (!"SUCCESS".equals(batch.getStatus())) { if (!"SUCCESS".equals(batch.getStatus())) {
context.setResult(Status.FAILED);
Logs.get().warn("人员中间表同步失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage()); Logs.get().warn("人员中间表同步失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage());
} }
} catch (Exception e) { } catch (Exception e) {
context.setResult(Status.FAILED);
Logs.get().warn("人员中间表同步未完成,配置:" + id, e); Logs.get().warn("人员中间表同步未完成,配置:" + id, e);
} }
} }
@@ -207,14 +207,12 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
List.of(lockKey), List.of(owner)); List.of(lockKey), List.of(owner));
} }
dao.update(batch); dao.update(batch);
// 任务列表按秒级exeAt展示执行时间;失败批次也回写原因,避免误认为未触发。
if ("SCHEDULED".equals(batch.getTriggerType()) && Strings.isNotBlank(config.getTaskId())) { if ("SCHEDULED".equals(batch.getTriggerType()) && Strings.isNotBlank(config.getTaskId())) {
String result = "SUCCESS".equals(batch.getStatus()) String result = "SUCCESS".equals(batch.getStatus())
? "执行成功,新增" + batch.getNewCount() + "条,更新" + batch.getChangeCount() + "条,跳过" ? "执行成功,新增" + batch.getNewCount() + "条,更新" + batch.getChangeCount() + "条,跳过"
+ (batch.getSourceCount() - batch.getSuccessCount()) + "条,批次:" + batch.getBatchNo() + (batch.getSourceCount() - batch.getSuccessCount()) + "条,批次:" + batch.getBatchNo()
: "执行失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage(); : "执行失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage();
dao.update(Sys_task.class, Chain.make("exeAt", batch.getStartedAt().getTime() / 1000) dao.update(Sys_task.class, Chain.make("exeResult", result),
.add("exeResult", result),
Cnd.where("id", "=", config.getTaskId()).and("jobClass", "=", Cnd.where("id", "=", config.getTaskId()).and("jobClass", "=",
"com.budwk.app.zhgh.personGovernance.apiConfig.job.PersonGovernanceSyncJob")); "com.budwk.app.zhgh.personGovernance.apiConfig.job.PersonGovernanceSyncJob"));
} }
+1 -1
View File
@@ -139,7 +139,7 @@ quartz:
#driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate #driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
#Other delegates can see: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/tutorial-lesson-09.html #Other delegates can see: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/tutorial-lesson-09.html
dataSource: myDS dataSource: myDS
tablePrefix: SYS_QRTZ_ tablePrefix: sys_qrtz_
isClustered: false isClustered: false
clusterCheckinInterval: 20000 clusterCheckinInterval: 20000
maxMisfiresToHandleAtATime: 120 maxMisfiresToHandleAtATime: 120
+159
View File
@@ -0,0 +1,159 @@
/* sys_qrtz_job_details */
CREATE TABLE IF NOT EXISTS `sys_qrtz_job_details` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`JOB_CLASS_NAME` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`IS_DURABLE` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`IS_UPDATE_DATA` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_DATA` blob NULL,
PRIMARY KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) USING BTREE,
INDEX `IDX_SYS_QRTZ_01`(`SCHED_NAME` ASC, `REQUESTS_RECOVERY` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_02`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`DESCRIPTION` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`NEXT_FIRE_TIME` bigint NULL DEFAULT NULL,
`PREV_FIRE_TIME` bigint NULL DEFAULT NULL,
`PRIORITY` int NULL DEFAULT NULL,
`TRIGGER_STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_TYPE` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`START_TIME` bigint NOT NULL,
`END_TIME` bigint NULL DEFAULT NULL,
`CALENDAR_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`MISFIRE_INSTR` smallint NULL DEFAULT NULL,
`JOB_DATA` blob NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE,
INDEX `IDX_SYS_QRTZ_03`(`SCHED_NAME` ASC, `JOB_NAME` ASC, `JOB_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_04`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_05`(`SCHED_NAME` ASC, `CALENDAR_NAME` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_06`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_07`(`SCHED_NAME` ASC, `TRIGGER_STATE` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_08`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_09`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_10`(`SCHED_NAME` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_11`(`SCHED_NAME` ASC, `TRIGGER_STATE` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_12`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_13`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC, `TRIGGER_STATE` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_14`(`SCHED_NAME` ASC, `MISFIRE_INSTR` ASC, `NEXT_FIRE_TIME` ASC, `TRIGGER_GROUP` ASC, `TRIGGER_STATE` ASC) USING BTREE,
CONSTRAINT `SYS_QRTZ_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `sys_qrtz_job_details` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_blob_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_blob_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`BLOB_DATA` blob NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE,
INDEX `SCHED_NAME`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE,
CONSTRAINT `SYS_QRTZ_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `sys_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_calendars */
CREATE TABLE IF NOT EXISTS `sys_qrtz_calendars` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`CALENDAR_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`CALENDAR` blob NOT NULL,
PRIMARY KEY (`SCHED_NAME`, `CALENDAR_NAME`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_cron_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_cron_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`CRON_EXPRESSION` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TIME_ZONE_ID` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE,
CONSTRAINT `SYS_QRTZ_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `sys_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_fired_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_fired_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`ENTRY_ID` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`INSTANCE_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`FIRED_TIME` bigint NOT NULL,
`SCHED_TIME` bigint NOT NULL,
`PRIORITY` int NOT NULL,
`STATE` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`JOB_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`JOB_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`, `ENTRY_ID`) USING BTREE,
INDEX `IDX_SYS_QRTZ_15`(`SCHED_NAME` ASC, `INSTANCE_NAME` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_16`(`SCHED_NAME` ASC, `INSTANCE_NAME` ASC, `REQUESTS_RECOVERY` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_17`(`SCHED_NAME` ASC, `JOB_NAME` ASC, `JOB_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_18`(`SCHED_NAME` ASC, `JOB_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_19`(`SCHED_NAME` ASC, `TRIGGER_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE,
INDEX `IDX_SYS_QRTZ_20`(`SCHED_NAME` ASC, `TRIGGER_GROUP` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_locks */
CREATE TABLE IF NOT EXISTS `sys_qrtz_locks` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`LOCK_NAME` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
PRIMARY KEY (`SCHED_NAME`, `LOCK_NAME`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_paused_trigger_grps */
CREATE TABLE IF NOT EXISTS `sys_qrtz_paused_trigger_grps` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_GROUP`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_scheduler_state */
CREATE TABLE IF NOT EXISTS `sys_qrtz_scheduler_state` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`INSTANCE_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`LAST_CHECKIN_TIME` bigint NOT NULL,
`CHECKIN_INTERVAL` bigint NOT NULL,
PRIMARY KEY (`SCHED_NAME`, `INSTANCE_NAME`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_simple_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_simple_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`REPEAT_COUNT` bigint NOT NULL,
`REPEAT_INTERVAL` bigint NOT NULL,
`TIMES_TRIGGERED` bigint NOT NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE,
CONSTRAINT `SYS_QRTZ_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `sys_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
/* sys_qrtz_simprop_triggers */
CREATE TABLE IF NOT EXISTS `sys_qrtz_simprop_triggers` (
`SCHED_NAME` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`TRIGGER_GROUP` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL,
`STR_PROP_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`STR_PROP_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`STR_PROP_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`INT_PROP_1` int NULL DEFAULT NULL,
`INT_PROP_2` int NULL DEFAULT NULL,
`LONG_PROP_1` bigint NULL DEFAULT NULL,
`LONG_PROP_2` bigint NULL DEFAULT NULL,
`DEC_PROP_1` decimal(13, 4) NULL DEFAULT NULL,
`DEC_PROP_2` decimal(13, 4) NULL DEFAULT NULL,
`BOOL_PROP_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
`BOOL_PROP_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) USING BTREE,
CONSTRAINT `SYS_QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `sys_qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_german2_ci ROW_FORMAT = DYNAMIC;
+3
View File
@@ -32,6 +32,8 @@
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<appender name="TASK_EXECUTION" class="com.budwk.app.task.log.TaskExecutionLogAppender"/>
<!-- 框架日志级别(按需调整) --> <!-- 框架日志级别(按需调整) -->
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>
<logger name="org.quartz" level="INFO"/> <logger name="org.quartz" level="INFO"/>
@@ -41,6 +43,7 @@
<root level="DEBUG"> <root level="DEBUG">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
<appender-ref ref="ALL_FILE"/> <appender-ref ref="ALL_FILE"/>
<appender-ref ref="TASK_EXECUTION"/>
</root> </root>
<!-- <!--
@@ -1,732 +1,39 @@
<!doctype html> <el-dialog title="Cron 表达式生成器" :visible.sync="cronDialogVisible" width="min(760px, 94vw)"
<html> :close-on-click-modal="false" append-to-body @close="closeCronGenerator">
<head> <el-form label-position="top" size="small" @submit.native.prevent="previewCron()">
<title>Cron表达式生成器</title> <el-form-item label="Quartz Cron 表达式">
<link href="${base}/assets/platform/cron/css/easyui.min.css" rel="stylesheet" type="text/css" /> <el-input v-model="cronDraft" @input="resetCronPreview" placeholder="秒 分 时 日 月 星期 [年]"></el-input>
<style type="text/css"> </el-form-item>
.line { <el-form-item label="常用周期">
height: 25px; <el-button size="mini" @click="selectCron('0 0/5 * * * ?')">每 5 分钟</el-button>
line-height: 25px; <el-button size="mini" @click="selectCron('0 0 9 * * ?')">每天 09:00</el-button>
margin: 3px; <el-button size="mini" @click="selectCron('0 0 9 ? * 2-6')">周一至周五 09:00</el-button>
<el-button size="mini" @click="selectCron('0 0 9 1 * ?')">每月 1 日 09:00</el-button>
<el-button size="mini" icon="el-icon-setting" @click="openCronBuilder">自定义字段</el-button>
</el-form-item>
</el-form>
<el-alert v-if="cronPreviewError" :title="cronPreviewError" type="error" :closable="false" show-icon></el-alert>
<div v-loading="cronPreviewLoading" aria-live="polite">
<strong>未来执行时间(北京时间,最多 5 次)</strong>
<ol v-if="cronPreviewTimes.length">
<li v-for="time in cronPreviewTimes" :key="time">{{time}}</li>
</ol>
<p v-else-if="!cronPreviewLoading && !cronPreviewError">输入表达式后点击预览。</p>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="cronDialogVisible = false">关闭</el-button>
<el-button :loading="cronPreviewLoading" @click="previewCron()">预览</el-button>
<el-button v-if="addDialogVisible || editDialogVisible" type="primary" :loading="cronPreviewLoading"
@click="previewCron(true)">应用到任务</el-button>
</span>
</el-dialog>
<el-dialog title="自定义 Cron 字段" :visible.sync="cronBuilderVisible" width="min(860px, 96vw)"
:close-on-click-modal="false" append-to-body custom-class="sys-task-cron-builder">
<vcron v-if="cronBuilderVisible" :expression="cronBuilderExpression"
@fill="selectCron" @hide="cronBuilderVisible = false"></vcron>
</el-dialog>
<style>
.sys-task-cron-builder .popup-result + .popup-result {
display: none;
} }
</style>
.imp {
padding-left: 25px;
}
.col {
width: 95px;
}
ul {
list-style: none;
padding-left: 10px;
}
li {
height: 20px;
}
body {
text-align: center; /*IE*/
text-align: -moz-center; /*Firefox*/
text-align: -webkit-center; /*Chrome*/
}
</style>
<script nonce="${cspNonce!}">
var base = "${base!}"
</script>
</head>
<body>
<div class="easyui-layout" style="width: 830px; height: 560px; border: 1px rgb(202, 196, 196) solid; border-radius: 5px">
<div style="height: 100%">
<div class="easyui-tabs" data-options="fit:true,border:false">
<div title="秒">
<div class="line">
<input type="radio" checked="checked" name="second" onclick="everyTime(this)" />
每秒 允许的通配符[, - * /]
</div>
<div class="line">
<input type="radio" name="second" onclick="cycle(this)" />
周期从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:58" value="1" id="secondStart_0" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:59" value="2" id="secondEnd_0" />
秒
</div>
<div class="line">
<input type="radio" name="second" onclick="startOn(this)" />
从
<input class="numberspinner" style="width: 60px" data-options="min:0,max:59" value="0" id="secondStart_1" />
秒开始,每
<input class="numberspinner" style="width: 60px" data-options="min:1,max:59" value="1" id="secondEnd_1" />
秒执行一次
</div>
<div class="line">
<input type="radio" name="second" id="sencond_appoint" />
指定
</div>
<div class="imp secondList">
<input type="checkbox" value="0" />
00
<input type="checkbox" value="1" />
01
<input type="checkbox" value="2" />
02
<input type="checkbox" value="3" />
03
<input type="checkbox" value="4" />
04
<input type="checkbox" value="5" />
05
<input type="checkbox" value="6" />
06
<input type="checkbox" value="7" />
07
<input type="checkbox" value="8" />
08
<input type="checkbox" value="9" />
09
</div>
<div class="imp secondList">
<input type="checkbox" value="10" />
10
<input type="checkbox" value="11" />
11
<input type="checkbox" value="12" />
12
<input type="checkbox" value="13" />
13
<input type="checkbox" value="14" />
14
<input type="checkbox" value="15" />
15
<input type="checkbox" value="16" />
16
<input type="checkbox" value="17" />
17
<input type="checkbox" value="18" />
18
<input type="checkbox" value="19" />
19
</div>
<div class="imp secondList">
<input type="checkbox" value="20" />
20
<input type="checkbox" value="21" />
21
<input type="checkbox" value="22" />
22
<input type="checkbox" value="23" />
23
<input type="checkbox" value="24" />
24
<input type="checkbox" value="25" />
25
<input type="checkbox" value="26" />
26
<input type="checkbox" value="27" />
27
<input type="checkbox" value="28" />
28
<input type="checkbox" value="29" />
29
</div>
<div class="imp secondList">
<input type="checkbox" value="30" />
30
<input type="checkbox" value="31" />
31
<input type="checkbox" value="32" />
32
<input type="checkbox" value="33" />
33
<input type="checkbox" value="34" />
34
<input type="checkbox" value="35" />
35
<input type="checkbox" value="36" />
36
<input type="checkbox" value="37" />
37
<input type="checkbox" value="38" />
38
<input type="checkbox" value="39" />
39
</div>
<div class="imp secondList">
<input type="checkbox" value="40" />
40
<input type="checkbox" value="41" />
41
<input type="checkbox" value="42" />
42
<input type="checkbox" value="43" />
43
<input type="checkbox" value="44" />
44
<input type="checkbox" value="45" />
45
<input type="checkbox" value="46" />
46
<input type="checkbox" value="47" />
47
<input type="checkbox" value="48" />
48
<input type="checkbox" value="49" />
49
</div>
<div class="imp secondList">
<input type="checkbox" value="50" />
50
<input type="checkbox" value="51" />
51
<input type="checkbox" value="52" />
52
<input type="checkbox" value="53" />
53
<input type="checkbox" value="54" />
54
<input type="checkbox" value="55" />
55
<input type="checkbox" value="56" />
56
<input type="checkbox" value="57" />
57
<input type="checkbox" value="58" />
58
<input type="checkbox" value="59" />
59
</div>
</div>
<div title="分钟">
<div class="line">
<input type="radio" checked="checked" name="min" onclick="everyTime(this)" />
分钟 允许的通配符[, - * /]
</div>
<div class="line">
<input type="radio" name="min" onclick="cycle(this)" />
周期从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:58" value="1" id="minStart_0" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:59" value="2" id="minEnd_0" />
分钟
</div>
<div class="line">
<input type="radio" name="min" onclick="startOn(this)" />
从
<input class="numberspinner" style="width: 60px" data-options="min:0,max:59" value="0" id="minStart_1" />
分钟开始,每
<input class="numberspinner" style="width: 60px" data-options="min:1,max:59" value="1" id="minEnd_1" />
分钟执行一次
</div>
<div class="line">
<input type="radio" name="min" id="min_appoint" />
指定
</div>
<div class="imp minList">
<input type="checkbox" value="0" />
00
<input type="checkbox" value="1" />
01
<input type="checkbox" value="2" />
02
<input type="checkbox" value="3" />
03
<input type="checkbox" value="4" />
04
<input type="checkbox" value="5" />
05
<input type="checkbox" value="6" />
06
<input type="checkbox" value="7" />
07
<input type="checkbox" value="8" />
08
<input type="checkbox" value="9" />
09
</div>
<div class="imp minList">
<input type="checkbox" value="10" />
10
<input type="checkbox" value="11" />
11
<input type="checkbox" value="12" />
12
<input type="checkbox" value="13" />
13
<input type="checkbox" value="14" />
14
<input type="checkbox" value="15" />
15
<input type="checkbox" value="16" />
16
<input type="checkbox" value="17" />
17
<input type="checkbox" value="18" />
18
<input type="checkbox" value="19" />
19
</div>
<div class="imp minList">
<input type="checkbox" value="20" />
20
<input type="checkbox" value="21" />
21
<input type="checkbox" value="22" />
22
<input type="checkbox" value="23" />
23
<input type="checkbox" value="24" />
24
<input type="checkbox" value="25" />
25
<input type="checkbox" value="26" />
26
<input type="checkbox" value="27" />
27
<input type="checkbox" value="28" />
28
<input type="checkbox" value="29" />
29
</div>
<div class="imp minList">
<input type="checkbox" value="30" />
30
<input type="checkbox" value="31" />
31
<input type="checkbox" value="32" />
32
<input type="checkbox" value="33" />
33
<input type="checkbox" value="34" />
34
<input type="checkbox" value="35" />
35
<input type="checkbox" value="36" />
36
<input type="checkbox" value="37" />
37
<input type="checkbox" value="38" />
38
<input type="checkbox" value="39" />
39
</div>
<div class="imp minList">
<input type="checkbox" value="40" />
40
<input type="checkbox" value="41" />
41
<input type="checkbox" value="42" />
42
<input type="checkbox" value="43" />
43
<input type="checkbox" value="44" />
44
<input type="checkbox" value="45" />
45
<input type="checkbox" value="46" />
46
<input type="checkbox" value="47" />
47
<input type="checkbox" value="48" />
48
<input type="checkbox" value="49" />
49
</div>
<div class="imp minList">
<input type="checkbox" value="50" />
50
<input type="checkbox" value="51" />
51
<input type="checkbox" value="52" />
52
<input type="checkbox" value="53" />
53
<input type="checkbox" value="54" />
54
<input type="checkbox" value="55" />
55
<input type="checkbox" value="56" />
56
<input type="checkbox" value="57" />
57
<input type="checkbox" value="58" />
58
<input type="checkbox" value="59" />
59
</div>
</div>
<div title="小时">
<div class="line">
<input type="radio" checked="checked" name="hour" onclick="everyTime(this)" />
小时 允许的通配符[, - * /]
</div>
<div class="line">
<input type="radio" name="hour" onclick="cycle(this)" />
周期从
<input class="numberspinner" style="width: 60px" data-options="min:0,max:23" value="0" id="hourStart_0" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:23" value="2" id="hourEnd_1" />
小时
</div>
<div class="line">
<input type="radio" name="hour" onclick="startOn(this)" />
从
<input class="numberspinner" style="width: 60px" data-options="min:0,max:23" value="0" id="hourStart_1" />
小时开始,每
<input class="numberspinner" style="width: 60px" data-options="min:1,max:23" value="1" id="hourEnd_1" />
小时执行一次
</div>
<div class="line">
<input type="radio" name="hour" id="hour_appoint" />
指定
</div>
<div class="imp hourList">
AM:
<input type="checkbox" value="0" />
00
<input type="checkbox" value="1" />
01
<input type="checkbox" value="2" />
02
<input type="checkbox" value="3" />
03
<input type="checkbox" value="4" />
04
<input type="checkbox" value="5" />
05
<input type="checkbox" value="6" />
06
<input type="checkbox" value="7" />
07
<input type="checkbox" value="8" />
08
<input type="checkbox" value="9" />
09
<input type="checkbox" value="10" />
10
<input type="checkbox" value="11" />
11
</div>
<div class="imp hourList">
PM:
<input type="checkbox" value="12" />
12
<input type="checkbox" value="13" />
13
<input type="checkbox" value="14" />
14
<input type="checkbox" value="15" />
15
<input type="checkbox" value="16" />
16
<input type="checkbox" value="17" />
17
<input type="checkbox" value="18" />
18
<input type="checkbox" value="19" />
19
<input type="checkbox" value="20" />
20
<input type="checkbox" value="21" />
21
<input type="checkbox" value="22" />
22
<input type="checkbox" value="23" />
23
</div>
</div>
<div title="日">
<div class="line">
<input type="radio" checked="checked" name="day" onclick="everyTime(this)" />
日 允许的通配符[, - * / L W]
</div>
<div class="line">
<input type="radio" name="day" onclick="unAppoint(this)" />
不指定
</div>
<div class="line">
<input type="radio" name="day" onclick="cycle(this)" />
周期从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:31" value="1" id="dayStart_0" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:31" value="2" id="dayEnd_0" />
日
</div>
<div class="line">
<input type="radio" name="day" onclick="startOn(this)" />
从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:31" value="1" id="dayStart_1" />
日开始,每
<input class="numberspinner" style="width: 60px" data-options="min:1,max:31" value="1" id="dayEnd_1" />
天执行一次
</div>
<div class="line">
<input type="radio" name="day" onclick="workDay(this)" />
每月
<input class="numberspinner" style="width: 60px" data-options="min:1,max:31" value="1" id="dayStart_2" />
号最近的那个工作日
</div>
<div class="line">
<input type="radio" name="day" onclick="lastDay(this)" />
本月最后一天
</div>
<div class="line">
<input type="radio" name="day" id="day_appoint" />
指定
</div>
<div class="imp dayList">
<input type="checkbox" value="1" />
1
<input type="checkbox" value="2" />
2
<input type="checkbox" value="3" />
3
<input type="checkbox" value="4" />
4
<input type="checkbox" value="5" />
5
<input type="checkbox" value="6" />
6
<input type="checkbox" value="7" />
7
<input type="checkbox" value="8" />
8
<input type="checkbox" value="9" />
9
<input type="checkbox" value="10" />
10
<input type="checkbox" value="11" />
11
<input type="checkbox" value="12" />
12
<input type="checkbox" value="13" />
13
<input type="checkbox" value="14" />
14
<input type="checkbox" value="15" />
15
<input type="checkbox" value="16" />
16
</div>
<div class="imp dayList">
<input type="checkbox" value="17" />
17
<input type="checkbox" value="18" />
18
<input type="checkbox" value="19" />
19
<input type="checkbox" value="20" />
20
<input type="checkbox" value="21" />
21
<input type="checkbox" value="22" />
22
<input type="checkbox" value="23" />
23
<input type="checkbox" value="24" />
24
<input type="checkbox" value="25" />
25
<input type="checkbox" value="26" />
26
<input type="checkbox" value="27" />
27
<input type="checkbox" value="28" />
28
<input type="checkbox" value="29" />
29
<input type="checkbox" value="30" />
30
<input type="checkbox" value="31" />
31
</div>
</div>
<div title="月">
<div class="line">
<input type="radio" checked="checked" name="mouth" onclick="everyTime(this)" />
月 允许的通配符[, - * /]
</div>
<div class="line">
<input type="radio" name="mouth" onclick="unAppoint(this)" />
不指定
</div>
<div class="line">
<input type="radio" name="mouth" onclick="cycle(this)" />
周期从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:12" value="1" id="mouthStart_0" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:12" value="2" id="mouthEnd_0" />
月
</div>
<div class="line">
<input type="radio" name="mouth" onclick="startOn(this)" />
从
<input class="numberspinner" style="width: 60px" data-options="min:1,max:12" value="1" id="mouthStart_1" />
日开始,每
<input class="numberspinner" style="width: 60px" data-options="min:1,max:12" value="1" id="mouthEnd_1" />
月执行一次
</div>
<div class="line">
<input type="radio" name="mouth" id="mouth_appoint" />
指定
</div>
<div class="imp mouthList">
<input type="checkbox" value="1" />
1
<input type="checkbox" value="2" />
2
<input type="checkbox" value="3" />
3
<input type="checkbox" value="4" />
4
<input type="checkbox" value="5" />
5
<input type="checkbox" value="6" />
6
<input type="checkbox" value="7" />
7
<input type="checkbox" value="8" />
8
<input type="checkbox" value="9" />
9
<input type="checkbox" value="10" />
10
<input type="checkbox" value="11" />
11
<input type="checkbox" value="12" />
12
</div>
</div>
<div title="周">
<div class="line">
<input type="radio" checked="checked" name="week" onclick="everyTime(this)" />
周 允许的通配符[, - * / L #]
</div>
<div class="line">
<input type="radio" name="week" onclick="unAppoint(this)" />
不指定
</div>
<div class="line">
<input type="radio" name="week" onclick="startOn(this)" />
周期 从星期
<input class="numberspinner" style="width: 60px" data-options="min:1,max:7" id="weekStart_0" value="1" />
-
<input class="numberspinner" style="width: 60px" data-options="min:2,max:7" value="2" id="weekEnd_0" />
</div>
<div class="line">
<input type="radio" name="week" onclick="weekOfDay(this)" />
第
<input class="numberspinner" style="width: 60px" data-options="min:1,max:4" value="1" id="weekStart_1" />
周 的星期
<input class="numberspinner" style="width: 60px" data-options="min:1,max:7" id="weekEnd_1" value="1" />
</div>
<div class="line">
<input type="radio" name="week" onclick="lastWeek(this)" />
本月最后一个星期
<input class="numberspinner" style="width: 60px" data-options="min:1,max:7" id="weekStart_2" value="1" />
</div>
<div class="line">
<input type="radio" name="week" id="week_appoint" />
指定
</div>
<div class="imp weekList">
<input type="checkbox" value="1" />
1
<input type="checkbox" value="2" />
2
<input type="checkbox" value="3" />
3
<input type="checkbox" value="4" />
4
<input type="checkbox" value="5" />
5
<input type="checkbox" value="6" />
6
<input type="checkbox" value="7" />
7
</div>
</div>
<div title="年">
<div class="line">
<input type="radio" checked="checked" name="year" onclick="unAppoint(this)" />
不指定 允许的通配符[, - * /] 非必填
</div>
<div class="line">
<input type="radio" name="year" onclick="everyTime(this)" />
每年
</div>
<div class="line">
<input type="radio" name="year" onclick="cycle(this)" />
周期 从
<input class="numberspinner" style="width: 90px" data-options="min:2018,max:3000" id="yearStart_0" value="2018" />
-
<input class="numberspinner" style="width: 90px" data-options="min:2020,max:3000" id="yearEnd_0" value="2020" />
</div>
</div>
</div>
</div>
<div data-options="region:'south',border:false" style="height: 250px">
<fieldset style="border-radius: 3px; height: 220px">
<legend>表达式</legend>
<table style="height: 100px">
<tbody>
<tr>
<td></td>
<td align="center">秒</td>
<td align="center">分钟</td>
<td align="center">小时</td>
<td align="center">日</td>
<td align="center">
月
<br />
</td>
<td align="center">星期</td>
<td align="center">年</td>
</tr>
<tr>
<td>表达式字段:</td>
<td>
<input type="text" name="v_second" class="col" value="*" readonly="readonly" />
</td>
<td>
<input type="text" name="v_min" class="col" value="*" readonly="readonly" />
</td>
<td>
<input type="text" name="v_hour" class="col" value="*" readonly="readonly" />
</td>
<td>
<input type="text" name="v_day" class="col" value="*" readonly="readonly" />
</td>
<td>
<input type="text" name="v_mouth" class="col" value="*" readonly="readonly" />
</td>
<td>
<input type="text" name="v_week" class="col" value="?" readonly="readonly" />
</td>
<td>
<input type="text" name="v_year" class="col" readonly="readonly" />
</td>
</tr>
<tr>
<td>Cron 表达式:</td>
<td colspan="6"><input type="text" name="cron" style="width: 100%" value="* * * * * ?" id="cron" /></td>
<td><input type="button" value="反解析到UI " id="btnFan" onclick="btnFan()" /></td>
</tr>
<tr>
<td colspan="8">最近5次运行时间:</td>
</tr>
<tr>
<td colspan="8" id="runTime"></td>
</tr>
</tbody>
</table>
</fieldset>
</div>
<div></div>
</div>
<script src="${base}/assets/platform/plugins/jquery/jquery.js" type="text/javascript"></script>
<script src="${base}/assets/platform/cron/js/cron.js" type="text/javascript"></script>
</body>
</html>
@@ -1,7 +1,7 @@
<!--# <!--#
layout("/layouts/platform.html"){ layout("/layouts/platform.html"){
#--> #-->
<div id="app" v-cloak> <div id="app" data-open-cron="${openCron!false}" v-cloak>
<guava> <guava>
<el-card shadow="never"> <el-card shadow="never">
<el-row type="flex" justify="end"> <el-row type="flex" justify="end">
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){
<el-table-column prop="jobClass" label="执行类" header-align="center" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="jobClass" label="执行类" header-align="center" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="data" label="执行参数" header-align="center" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="data" label="执行参数" header-align="center" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="cron" label="定时规则" header-align="center" align="center"></el-table-column> <el-table-column prop="cron" label="定时规则" header-align="center" align="center"></el-table-column>
<el-table-column prop="exeAt" label="执行时间" header-align="center" align="center"> <el-table-column prop="exeAt" label="最近执行时间" min-width="160" header-align="center" align="center">
<template slot-scope="scope">{{formatAt(scope.row.exeAt)}}</template> <template slot-scope="scope">{{formatAt(scope.row.exeAt < 100000000000 ? scope.row.exeAt * 1000 : scope.row.exeAt)}}</template>
</el-table-column> </el-table-column>
<el-table-column sortable prop="disabled" label="启用状态" header-align="center" align="center"> <el-table-column sortable prop="disabled" label="启用状态" header-align="center" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
@@ -34,6 +34,7 @@ layout("/layouts/platform.html"){
<span class="ti-angle-down"></span> <span class="ti-angle-down"></span>
</el-button> </el-button>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{type:'history',id:scope.row.id,name:scope.row.name}">执行历史</el-dropdown-item>
<el-dropdown-item v-if="$auth.hasPermission('sys.manager.task.edit')" <el-dropdown-item v-if="$auth.hasPermission('sys.manager.task.edit')"
:disabled="runningOnce[scope.row.id]" :disabled="runningOnce[scope.row.id]"
:command="{type:'runOnce',id:scope.row.id}">执行一次</el-dropdown-item> :command="{type:'runOnce',id:scope.row.id}">执行一次</el-dropdown-item>
@@ -63,7 +64,7 @@ layout("/layouts/platform.html"){
<el-form-item prop="cron" label="执行周期"> <el-form-item prop="cron" label="执行周期">
<el-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text"> <el-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text">
<template slot="append"> <template slot="append">
<a href="${base}/platform/sys/task/cron" style="color: #409eff" target="_blank">表达式生成器</a> <el-button icon="el-icon-time" @click="openCronGenerator">表达式生成器</el-button>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -71,7 +72,7 @@ layout("/layouts/platform.html"){
<el-input type="textarea" v-model="formData.data" placeholder="{}" tabindex="4"></el-input> <el-input type="textarea" v-model="formData.data" placeholder="{}" tabindex="4"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="disabled" label="启用状态"> <el-form-item prop="disabled" label="启用状态">
<el-switch v-model="formData.disabled" active-color="#ff4949" inactive-color="#13ce66"></el-switch> <el-switch v-model="formData.disabled" :active-value="false" :inactive-value="true" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
</el-form-item> </el-form-item>
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@@ -90,7 +91,7 @@ layout("/layouts/platform.html"){
<el-form-item prop="cron" label="执行周期"> <el-form-item prop="cron" label="执行周期">
<el-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text"> <el-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text">
<template slot="append"> <template slot="append">
<a href="${base}/platform/sys/task/cron" style="color: #409eff" target="_blank">表达式生成器</a> <el-button icon="el-icon-time" @click="openCronGenerator">表达式生成器</el-button>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -98,7 +99,7 @@ layout("/layouts/platform.html"){
<el-input type="textarea" v-model="formData.data" placeholder="{}" tabindex="4"></el-input> <el-input type="textarea" v-model="formData.data" placeholder="{}" tabindex="4"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="disabled" label="启用状态"> <el-form-item prop="disabled" label="启用状态">
<el-switch v-model="formData.disabled" active-color="#ff4949" inactive-color="#13ce66"></el-switch> <el-switch v-model="formData.disabled" :active-value="false" :inactive-value="true" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
</el-form-item> </el-form-item>
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@@ -106,16 +107,72 @@ layout("/layouts/platform.html"){
<el-button type="primary" @click="doEdit">确 定</el-button> <el-button type="primary" @click="doEdit">确 定</el-button>
</span> </span>
</el-dialog> </el-dialog>
<el-dialog :title="historyTask.name + ' · 执行历史'" :visible.sync="historyVisible" append-to-body
width="min(780px, 94vw)" @close="closeHistory">
<el-table :data="historyRows" v-loading="historyLoading" empty-text="暂无执行记录">
<el-table-column label="开始时间(北京时间)" min-width="180">
<template slot-scope="scope">{{formatAt(scope.row.startedAt)}}</template>
</el-table-column>
<el-table-column label="结束时间(北京时间)" min-width="180">
<template slot-scope="scope">{{formatAt(scope.row.finishedAt)}}</template>
</el-table-column>
<el-table-column label="执行状态" width="100">
<template slot-scope="scope">
<el-tag size="small" :type="historyStatusType(scope.row.status)">{{historyStatus(scope.row.status)}}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="95">
<template slot-scope="scope">
<el-button type="text" @click="openExecutionLog(scope.row)">查看日志</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer" style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between">
<el-pagination :current-page="historyPage" :page-size="10" :total="historyTotal"
layout="total, prev, pager, next" @current-change="loadHistory"></el-pagination>
<el-button size="small" :loading="historyLoading" @click="loadHistory(historyPage)">刷新</el-button>
</div>
</el-dialog>
<el-dialog title="执行日志" :visible.sync="executionLogVisible" append-to-body width="min(920px, 94vw)" @close="closeExecutionLog">
<div v-loading="executionLogLoading">
<p>{{formatAt(executionLog.startedAt)}} · {{historyStatus(executionLog.status)}}</p>
<pre style="max-height: 55vh; overflow: auto; padding: 12px; white-space: pre-wrap; overflow-wrap: anywhere; background: #f5f7fa">{{executionLog.executionLog || (executionLog.status === "RUNNING" ? "任务执行结束后可查看日志。" : "暂无日志")}}</pre>
</div>
<span slot="footer">
<el-button size="small" :loading="executionLogLoading" @click="openExecutionLog(executionLog)">刷新</el-button>
</span>
</el-dialog>
<!--#include("/platform/sys/task/cron.html"){}#-->
</div> </div>
<script nonce="${cspNonce!}"> <script nonce="${cspNonce!}">
new Vue({ new Vue({
el: "#app", el: "#app",
components: { vcron: vcrontab.default },
data: function () { data: function () {
return { return {
addDialogVisible: false, addDialogVisible: false,
editDialogVisible: false, editDialogVisible: false,
cronDialogVisible: false,
cronDraft: "",
cronBuilderVisible: false,
cronBuilderExpression: "",
cronPreviewLoading: false,
cronPreviewRequest: 0,
cronPreviewTimes: [],
cronPreviewError: "",
tableData: [], tableData: [],
runningOnce: {}, runningOnce: {},
historyVisible: false,
historyTask: { id: "", name: "" },
historyRows: [],
historyPage: 1,
historyTotal: 0,
historyLoading: false,
historyRequest: null,
executionLogVisible: false,
executionLog: {},
executionLogLoading: false,
executionLogRequest: null,
pageForm: { pageForm: {
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
@@ -134,6 +191,53 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { methods: {
openCronGenerator: function () {
this.cronDraft = this.formData.cron || "0 0 9 * * ?"
this.cronDialogVisible = true
this.previewCron()
},
resetCronPreview: function () {
this.cronPreviewRequest++
this.cronPreviewLoading = false
this.cronPreviewTimes = []
this.cronPreviewError = ""
},
closeCronGenerator: function () {
this.cronBuilderVisible = false
this.resetCronPreview()
},
selectCron: function (expression) {
this.cronDraft = expression
this.cronBuilderVisible = false
this.previewCron()
},
openCronBuilder: function () {
this.cronBuilderExpression = this.cronDraft.trim().replace(/\s+/g, " ")
this.cronBuilderVisible = true
},
previewCron: function (apply) {
var self = this
self.resetCronPreview()
var request = self.cronPreviewRequest
var cron = self.cronDraft.trim().replace(/\s+/g, " ")
self.cronPreviewLoading = true
$.post(base + "/platform/sys/task/cronExp", { cron: cron }, function (response) {
if (request !== self.cronPreviewRequest) return
if (response.code == 0) {
self.cronPreviewTimes = response.data
if (apply === true) {
self.$set(self.formData, "cron", cron)
self.cronDialogVisible = false
}
} else {
self.cronPreviewError = response.msg || "Cron 表达式校验失败"
}
}, "json").fail(function () {
if (request === self.cronPreviewRequest) self.cronPreviewError = "获取执行时间失败"
}).always(function () {
if (request === self.cronPreviewRequest) self.cronPreviewLoading = false
})
},
pageOrder: function (column) { pageOrder: function (column) {
//按字段排序 //按字段排序
this.pageForm.pageOrderName = column.prop this.pageForm.pageOrderName = column.prop
@@ -171,18 +275,86 @@ layout("/layouts/platform.html"){
) )
}, },
formatAt: function (val) { formatAt: function (val) {
if (val && val > 0) return moment(val).format("YYYY-MM-DD HH:mm:ss") return val > 0 ? moment(val).utcOffset(480).format("YYYY-MM-DD HH:mm:ss") : "—"
return "" },
historyStatus: function (status) {
return { RUNNING: "执行中", SUCCESS: "成功", FAILED: "失败", SKIPPED: "已跳过", INTERRUPTED: "已中断" }[status] || "—"
},
historyStatusType: function (status) {
return { RUNNING: "warning", SUCCESS: "success", FAILED: "danger" }[status] || "info"
},
openHistory: function (task) {
this.historyTask = task
this.historyRows = []
this.historyTotal = 0
this.historyVisible = true
this.loadHistory(1)
},
closeHistory: function () {
if (this.historyRequest) this.historyRequest.abort()
this.closeExecutionLog()
this.executionLogVisible = false
this.pageData()
},
loadHistory: function (page) {
var self = this
if (self.historyRequest) self.historyRequest.abort()
self.historyPage = page
self.historyLoading = true
self.historyRequest = $.post(base + "/platform/sys/task/history/" + encodeURIComponent(self.historyTask.id),
{ pageNumber: page }, function (response) {
if (response.code == 0) {
self.historyRows = response.data.list
self.historyTotal = response.data.totalCount
} else {
self.$message.error(response.msg || "获取执行历史失败")
}
}, "json").fail(function (xhr, status) {
if (status !== "abort") self.$message.error("获取执行历史失败")
}).always(function () {
self.historyLoading = false
self.historyRequest = null
})
},
closeExecutionLog: function () {
if (this.executionLogRequest) this.executionLogRequest.abort()
},
openExecutionLog: function (row) {
var self = this
if (self.executionLogRequest) self.executionLogRequest.abort()
self.executionLog = row
self.executionLogVisible = true
self.executionLogLoading = true
var url = base + "/platform/sys/task/historyLog/" + encodeURIComponent(self.historyTask.id)
+ "/" + encodeURIComponent(row.id)
self.executionLogRequest = $.post(url, {}, function (response) {
if (response.code == 0) {
self.executionLog = response.data
var record = self.historyRows.find(function (item) { return item.id === row.id })
if (record) {
record.status = response.data.status
self.$set(record, "finishedAt", response.data.finishedAt)
}
} else {
self.$message.error(response.msg || "获取执行日志失败")
}
}, "json").fail(function (xhr, status) {
if (status !== "abort") self.$message.error("获取执行日志失败")
}).always(function () {
self.executionLogLoading = false
self.executionLogRequest = null
})
}, },
doSearch: function () { doSearch: function () {
this.pageForm.pageNumber = 1 this.pageForm.pageNumber = 1
this.pageData() this.pageData()
}, },
openAdd: function () { openAdd: function () {
var self = this this.formData = { name: "", jobClass: "", cron: "", data: "", disabled: false }
this.addDialogVisible = true this.addDialogVisible = true
this.formData = {} //打开新增窗口,表单先清空 this.$nextTick(function () {
if (this.$refs["addForm"]) this.$refs["addForm"].resetFields() this.$refs.addForm.clearValidate()
})
}, },
doAdd: function () { doAdd: function () {
var self = this var self = this
@@ -204,7 +376,7 @@ layout("/layouts/platform.html"){
type: "error" type: "error"
}) })
} }
self.pageData() //添加失败也可以入库,所以加载一下看看 self.pageData()
}, },
"json" "json"
) )
@@ -242,6 +414,10 @@ layout("/layouts/platform.html"){
dropdownCommand: function (command) { dropdownCommand: function (command) {
//监听下拉框事件 //监听下拉框事件
var self = this var self = this
if ("history" == command.type) {
self.openHistory(command)
return
}
if ("enable" == command.type || "disable" == command.type) { if ("enable" == command.type || "disable" == command.type) {
$.post( $.post(
base + "/platform/sys/task/" + command.type + "/" + command.id, base + "/platform/sys/task/" + command.type + "/" + command.id,
@@ -334,6 +510,9 @@ layout("/layouts/platform.html"){
}, },
created: function () { created: function () {
this.pageData() this.pageData()
},
mounted: function () {
if (this.$el.dataset.openCron === "true") this.openCronGenerator()
} }
}) })
</script> </script>