feat: 定时任务失效优化,并补充功能
This commit is contained in:
@@ -5,6 +5,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import com.budwk.app.base.utils.AppIocUtil;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
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.web.commons.auth.satoken.SaTokenContextImpl;
|
||||
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.dao.Dao;
|
||||
import org.nutz.dao.impl.FileSqlManager;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.integration.jedis.JedisAgent;
|
||||
import org.nutz.ioc.Ioc;
|
||||
@@ -31,6 +31,7 @@ import org.nutz.lang.Mirror;
|
||||
import org.nutz.mvc.Mvcs;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
@@ -39,7 +40,6 @@ import java.lang.management.ManagementFactory;
|
||||
import java.sql.Driver;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean(create = "init", depose = "depose")
|
||||
@Localization(value = "locales/", defaultLocalizationKey = "zh_CN")
|
||||
@@ -82,6 +82,7 @@ public class MainLauncher {
|
||||
Mvcs.DISABLE_X_POWERED_BY = true;
|
||||
Globals.AppBase = Mvcs.getServletContext().getContextPath();
|
||||
Globals.AppRoot = Mvcs.getServletContext().getRealPath("/");
|
||||
init_task();
|
||||
init_sys();
|
||||
ioc.get(com.budwk.app.zhgh.staffbenefit.therapyRecuperation.TherapyRecuperationModule.class);
|
||||
// 2026年10月3日 12:01:28;操作人:wxg:先升级会员表及补齐历史资料,再处理缴费快照和启动计划任务;不依赖 DEBUG。
|
||||
@@ -93,7 +94,6 @@ public class MainLauncher {
|
||||
ClubPayRecordService payRecords = ioc.get(ClubPayRecordService.class, "clubPayRecordServiceImpl");
|
||||
payRecords.upgradePayRecordSchema();
|
||||
payRecords.backfillPayRecordSnapshots();
|
||||
init_task();
|
||||
init_auth();
|
||||
ioc.get(Globals.class);
|
||||
ioc.get(FlowEngine.class);
|
||||
@@ -116,17 +116,17 @@ public class MainLauncher {
|
||||
}
|
||||
|
||||
private void init_task() {
|
||||
if (log.isDebugEnabled() && !dao.exists("sys_qrtz_triggers") && !dao.exists("sys_qrtz_triggers".toUpperCase())) {
|
||||
//执行Quartz SQL脚本
|
||||
String dbType = dao.getJdbcExpert().getDatabaseType();
|
||||
log.debug("dbType:::" + dbType);
|
||||
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);
|
||||
FileSqlManager schema = new FileSqlManager("db/quartz/mysql.sql");
|
||||
for (String table : schema.keys()) {
|
||||
if (!dao.exists(table)) {
|
||||
dao.execute(schema.create(table));
|
||||
}
|
||||
}
|
||||
try {
|
||||
ioc.get(TaskExecutionListener.class).register();
|
||||
} catch (SchedulerException e) {
|
||||
throw new IllegalStateException("注册任务执行记录监听器失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void init_sys() {
|
||||
|
||||
@@ -2,15 +2,18 @@ package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
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.utils.PageUtil;
|
||||
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.task.services.TaskPlatformService;
|
||||
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.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.IocBean;
|
||||
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.POST;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.trans.Trans;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
;
|
||||
|
||||
@@ -33,6 +38,8 @@ public class SysTaskController {
|
||||
private SysTaskService sysTaskService;
|
||||
@Inject
|
||||
private TaskPlatformService taskPlatformService;
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/sys/task/index.html")
|
||||
@@ -42,10 +49,10 @@ public class SysTaskController {
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("beetl:/platform/sys/task/cron.html")
|
||||
@Ok("beetl:/platform/sys/task/index.html")
|
||||
@SaCheckPermission("sys.manager.task")
|
||||
public void cron() {
|
||||
|
||||
public void cron(HttpServletRequest req) {
|
||||
req.setAttribute("openCron", true);
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -53,9 +60,9 @@ public class SysTaskController {
|
||||
@SaCheckPermission("sys.manager.task")
|
||||
public Object cronExp(@Param("cron") String cron) {
|
||||
try {
|
||||
return Result.success().addData(taskPlatformService.getCronExeTimes(cron));
|
||||
return Result.success().addData(previewCron(normalizeCron(cron)));
|
||||
} 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
|
||||
@Ok("json")
|
||||
@SLog(tag = "新建任务", msg = "任务名称:${args[0].name}")
|
||||
@SaCheckPermission("sys.manager.task.add")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Object addDo(@Param("..") Sys_task task, HttpServletRequest req) {
|
||||
try {
|
||||
task.setCron(validateCron(task.getCron()));
|
||||
task.setCreatedBy(SecurityUtil.getUserId());
|
||||
Sys_task sysTask = sysTaskService.insert(task);
|
||||
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron(),
|
||||
sysTask.getNote(), sysTask.getData());
|
||||
task.setExeAt(null);
|
||||
task.setExeResult(null);
|
||||
Trans.exec(() -> {
|
||||
Sys_task saved = sysTaskService.insert(task);
|
||||
if (!saved.isDisabled()) {
|
||||
schedule(saved);
|
||||
}
|
||||
});
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
return taskError("新建任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,18 +152,24 @@ public class SysTaskController {
|
||||
@SaCheckPermission("sys.manager.task.edit")
|
||||
public Object editDo(@Param("..") Sys_task task, HttpServletRequest req) {
|
||||
try {
|
||||
if (task.getCron() != null) {
|
||||
task.setCron(validateCron(task.getCron()));
|
||||
}
|
||||
task.setUpdatedBy(SecurityUtil.getUserId());
|
||||
sysTaskService.updateIgnoreNull(task);
|
||||
if (taskPlatformService.isExist(task.getId(), task.getId())) {
|
||||
taskPlatformService.delete(task.getId(), task.getId());
|
||||
}
|
||||
if (!task.isDisabled()) {
|
||||
taskPlatformService.add(task.getId(), task.getId(), task.getJobClass(), task.getCron(),
|
||||
task.getNote(), task.getData());
|
||||
Trans.exec(() -> {
|
||||
requireTask(task.getId());
|
||||
FieldFilter.locked(Sys_task.class, "^(exeAt|exeResult)$")
|
||||
.run(() -> sysTaskService.updateIgnoreNull(task));
|
||||
Sys_task saved = requireTask(task.getId());
|
||||
if (saved.isDisabled()) {
|
||||
taskPlatformService.delete(saved.getId(), saved.getId());
|
||||
} else {
|
||||
schedule(saved);
|
||||
}
|
||||
});
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
return taskError("修改任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,19 +180,15 @@ public class SysTaskController {
|
||||
@SaCheckPermission("sys.manager.task.delete")
|
||||
public Object delete(String id, HttpServletRequest req) {
|
||||
try {
|
||||
Sys_task sysTask = sysTaskService.fetch(id);
|
||||
try {
|
||||
if (taskPlatformService.isExist(sysTask.getId(), sysTask.getId())) {
|
||||
taskPlatformService.delete(sysTask.getId(), sysTask.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
Trans.exec(() -> {
|
||||
Sys_task task = requireTask(id);
|
||||
sysTaskService.delete(id);
|
||||
req.setAttribute("name", sysTask.getName());
|
||||
taskPlatformService.delete(id, id);
|
||||
req.setAttribute("name", task.getName());
|
||||
});
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
return taskError("删除任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,18 +217,7 @@ public class SysTaskController {
|
||||
@SaCheckPermission("sys.manager.task.edit")
|
||||
@SLog(tag = "启用任务", msg = "任务名:${args[1].getAttribute('name')}")
|
||||
public Object enable(String id, HttpServletRequest req) {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
return changeDisabled(id, false, req);
|
||||
}
|
||||
|
||||
@At("/disable/?")
|
||||
@@ -190,16 +225,66 @@ public class SysTaskController {
|
||||
@SaCheckPermission("sys.manager.task.edit")
|
||||
@SLog(tag = "禁用任务", msg = "任务名:${args[1].getAttribute('name')}")
|
||||
public Object disable(String id, HttpServletRequest req) {
|
||||
try {
|
||||
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());
|
||||
return changeDisabled(id, true, req);
|
||||
}
|
||||
|
||||
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();
|
||||
} 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 hi = data.getString("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 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.task.services.TaskPlatformService;
|
||||
import org.nutz.integration.quartz.QuartzJob;
|
||||
import org.nutz.integration.quartz.QuartzManager;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
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.util.NutMap;
|
||||
import org.quartz.CronExpression;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.CronTrigger;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.quartz.TriggerUtils;
|
||||
import org.quartz.impl.triggers.CronTriggerImpl;
|
||||
import org.quartz.TriggerKey;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Created by wizzer on 2018/3/19.
|
||||
*/
|
||||
@IocBean
|
||||
public class TaskPlatformServiceImpl implements TaskPlatformService {
|
||||
private static final TimeZone TASK_TIME_ZONE = TimeZone.getTimeZone("Asia/Shanghai");
|
||||
|
||||
@Inject
|
||||
private QuartzManager quartzManager;
|
||||
@Inject
|
||||
@@ -55,18 +60,30 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
|
||||
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();
|
||||
if (Strings.isNotBlank(task.getData())) {
|
||||
NutMap params = Json.fromJson(NutMap.class, task.getData());
|
||||
if (Strings.isNotBlank(dataMap)) {
|
||||
try {
|
||||
NutMap params = Json.fromJson(NutMap.class, dataMap);
|
||||
if (params != null) {
|
||||
data.putAll(params);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalArgumentException("执行参数必须是有效的 JSON 对象", e);
|
||||
}
|
||||
|
||||
// 没有注册定时调度时,临时注册一个执行后自动移除的单次任务。
|
||||
JobDetail job = JobBuilder.newJob(jobClass).withIdentity(key).usingJobData(data).build();
|
||||
scheduler.scheduleJob(job, TriggerBuilder.newTrigger().forJob(key).startNow().build());
|
||||
}
|
||||
return JobBuilder.newJob(jobClass).withIdentity(key).usingJobData(data).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,14 +97,55 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
|
||||
* @param dataMap
|
||||
*/
|
||||
public void add(String jobName, String jobGroup, String className, String cron, String comment, String dataMap) {
|
||||
QuartzJob qj = new QuartzJob();
|
||||
qj.setJobName(jobName);
|
||||
qj.setJobGroup(jobGroup);
|
||||
qj.setClassName(className);
|
||||
qj.setCron(cron);
|
||||
qj.setComment(comment);
|
||||
qj.setDataMap(dataMap);
|
||||
quartzManager.add(qj);
|
||||
JobKey key = JobKey.jobKey(jobName, jobGroup);
|
||||
JobDetail job = buildJob(key, className, dataMap);
|
||||
CronExpression expression = parseCron(cron);
|
||||
if (expression.getNextValidTimeAfter(new Date()) == null) {
|
||||
throw new IllegalArgumentException("Cron 表达式没有未来执行时间");
|
||||
}
|
||||
schedule(job, expression);
|
||||
}
|
||||
|
||||
@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
|
||||
*/
|
||||
public boolean delete(String jobName, String jobGroup) {
|
||||
QuartzJob qj = new QuartzJob();
|
||||
qj.setJobName(jobName);
|
||||
qj.setJobGroup(jobGroup);
|
||||
return quartzManager.delete(qj);
|
||||
return quartzManager.delete(JobKey.jobKey(jobName, jobGroup));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,36 +172,40 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
|
||||
* @param cronExpression
|
||||
* @return
|
||||
*/
|
||||
public List<String> getCronExeTimes(String cronExpression) throws Exception {
|
||||
List<String> list = new ArrayList<>();
|
||||
CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
|
||||
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());
|
||||
@Override
|
||||
public List<String> getCronExeTimes(String cronExpression) {
|
||||
CronExpression expression = parseCron(cronExpression);
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
for (int i = 0; i < dates.size() && i <= 4; ++i) {
|
||||
list.add(dateFormat.format((Date) dates.get(i)));
|
||||
dateFormat.setTimeZone(TASK_TIME_ZONE);
|
||||
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
|
||||
public List<String> getCronExeTimesPlus(String cronExpression) throws Exception {
|
||||
// 创建一个列表来存储执行时间
|
||||
List<String> executionTimes = new ArrayList<>();
|
||||
// 创建 CronTriggerImpl 对象,并设置 cron 表达式
|
||||
CronTriggerImpl cronTrigger = new CronTriggerImpl();
|
||||
cronTrigger.setCronExpression(new CronExpression(cronExpression));
|
||||
// 计算未来的执行时间
|
||||
List<Date> nextExecutionTimes = TriggerUtils.computeFireTimes(cronTrigger, null, 5);
|
||||
// 格式化日期并添加到执行时间列表中
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
for (Date date : nextExecutionTimes) {
|
||||
executionTimes.add(dateFormat.format(date));
|
||||
public List<String> getCronExeTimesPlus(String cronExpression) {
|
||||
return getCronExeTimes(cronExpression);
|
||||
}
|
||||
|
||||
private CronExpression parseCron(String cron) {
|
||||
String normalized = Strings.sNull(cron).trim().replaceAll("\\s+", " ");
|
||||
int fieldCount = normalized.split(" ").length;
|
||||
if (fieldCount < 6 || fieldCount > 7) {
|
||||
throw new IllegalArgumentException("Quartz Cron 表达式必须包含 6 或 7 个字段");
|
||||
}
|
||||
try {
|
||||
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.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.log.Log;
|
||||
import org.nutz.log.Logs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -25,6 +27,7 @@ import java.util.Set;
|
||||
|
||||
@IocBean(create = "init")
|
||||
public class Globals {
|
||||
private static final Log log = Logs.get();
|
||||
//项目路径
|
||||
public static String AppRoot = "";
|
||||
//项目目录
|
||||
@@ -69,15 +72,12 @@ public class Globals {
|
||||
}
|
||||
|
||||
public void initTask(SysTaskService sysTaskService) {
|
||||
taskPlatformService.clear();
|
||||
List<Sys_task> taskList = sysTaskService.query();
|
||||
for (Sys_task sysTask : taskList) {
|
||||
try {
|
||||
if (!sysTask.isDisabled())//不存在则新建
|
||||
taskPlatformService.add(sysTask.getId(), sysTask.getId(), sysTask.getJobClass(), sysTask.getCron()
|
||||
, sysTask.getNote(), sysTask.getData());
|
||||
taskPlatformService.restore(sysTask);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("恢复定时任务失败:" + sysTask.getName() + " (" + sysTask.getId() + ")", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -1,5 +1,6 @@
|
||||
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.model.PersonApiConfig;
|
||||
import org.nutz.dao.Dao;
|
||||
@@ -29,6 +30,7 @@ public class PersonGovernanceSyncJob implements Job {
|
||||
public void execute(JobExecutionContext context) {
|
||||
String id = context.getMergedJobDataMap().getString("configId");
|
||||
if (Strings.isBlank(id)) {
|
||||
context.setResult(Status.SKIPPED);
|
||||
Logs.get().warn("人员同步任务缺少configId,请停用旧扫描任务并通过API配置重新启用");
|
||||
return;
|
||||
}
|
||||
@@ -36,18 +38,25 @@ public class PersonGovernanceSyncJob implements Job {
|
||||
if (config == null || Boolean.TRUE.equals(config.getDelFlag()) || !Boolean.TRUE.equals(config.getEnabled())
|
||||
|| !Objects.equals(config.getTaskId(), context.getJobDetail().getKey().getName())
|
||||
|| !Objects.equals(config.getTaskCron(), context.getMergedJobDataMap().getString("taskCron"))) {
|
||||
context.setResult(Status.SKIPPED);
|
||||
Logs.get().warn("跳过已停用或已被替换的人员同步任务:" + context.getJobDetail().getKey());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 重启恢复的旧式Quartz触发器也不允许补执行非周期时间点的misfire。
|
||||
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");
|
||||
if (!"SUCCESS".equals(batch.getStatus())) {
|
||||
context.setResult(Status.FAILED);
|
||||
Logs.get().warn("人员中间表同步失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
context.setResult(Status.FAILED);
|
||||
Logs.get().warn("人员中间表同步未完成,配置:" + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -207,14 +207,12 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
||||
List.of(lockKey), List.of(owner));
|
||||
}
|
||||
dao.update(batch);
|
||||
// 任务列表按秒级exeAt展示执行时间;失败批次也回写原因,避免误认为未触发。
|
||||
if ("SCHEDULED".equals(batch.getTriggerType()) && Strings.isNotBlank(config.getTaskId())) {
|
||||
String result = "SUCCESS".equals(batch.getStatus())
|
||||
? "执行成功,新增" + batch.getNewCount() + "条,更新" + batch.getChangeCount() + "条,跳过"
|
||||
+ (batch.getSourceCount() - batch.getSuccessCount()) + "条,批次:" + batch.getBatchNo()
|
||||
: "执行失败,批次:" + batch.getBatchNo() + ",原因:" + batch.getErrorMessage();
|
||||
dao.update(Sys_task.class, Chain.make("exeAt", batch.getStartedAt().getTime() / 1000)
|
||||
.add("exeResult", result),
|
||||
dao.update(Sys_task.class, Chain.make("exeResult", result),
|
||||
Cnd.where("id", "=", config.getTaskId()).and("jobClass", "=",
|
||||
"com.budwk.app.zhgh.personGovernance.apiConfig.job.PersonGovernanceSyncJob"));
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ quartz:
|
||||
#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
|
||||
dataSource: myDS
|
||||
tablePrefix: SYS_QRTZ_
|
||||
tablePrefix: sys_qrtz_
|
||||
isClustered: false
|
||||
clusterCheckinInterval: 20000
|
||||
maxMisfiresToHandleAtATime: 120
|
||||
|
||||
@@ -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;
|
||||
@@ -32,6 +32,8 @@
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<appender name="TASK_EXECUTION" class="com.budwk.app.task.log.TaskExecutionLogAppender"/>
|
||||
|
||||
<!-- 框架日志级别(按需调整) -->
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="org.quartz" level="INFO"/>
|
||||
@@ -41,6 +43,7 @@
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="ALL_FILE"/>
|
||||
<appender-ref ref="TASK_EXECUTION"/>
|
||||
</root>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,732 +1,39 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Cron表达式生成器</title>
|
||||
<link href="${base}/assets/platform/cron/css/easyui.min.css" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">
|
||||
.line {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
margin: 3px;
|
||||
<el-dialog title="Cron 表达式生成器" :visible.sync="cronDialogVisible" width="min(760px, 94vw)"
|
||||
:close-on-click-modal="false" append-to-body @close="closeCronGenerator">
|
||||
<el-form label-position="top" size="small" @submit.native.prevent="previewCron()">
|
||||
<el-form-item label="Quartz Cron 表达式">
|
||||
<el-input v-model="cronDraft" @input="resetCronPreview" placeholder="秒 分 时 日 月 星期 [年]"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="常用周期">
|
||||
<el-button size="mini" @click="selectCron('0 0/5 * * * ?')">每 5 分钟</el-button>
|
||||
<el-button size="mini" @click="selectCron('0 0 9 * * ?')">每天 09:00</el-button>
|
||||
<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;
|
||||
}
|
||||
|
||||
.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>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<div id="app" data-open-cron="${openCron!false}" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<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="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="exeAt" label="执行时间" header-align="center" align="center">
|
||||
<template slot-scope="scope">{{formatAt(scope.row.exeAt)}}</template>
|
||||
<el-table-column prop="exeAt" label="最近执行时间" min-width="160" header-align="center" align="center">
|
||||
<template slot-scope="scope">{{formatAt(scope.row.exeAt < 100000000000 ? scope.row.exeAt * 1000 : scope.row.exeAt)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column sortable prop="disabled" label="启用状态" header-align="center" align="center">
|
||||
<template slot-scope="scope">
|
||||
@@ -34,6 +34,7 @@ layout("/layouts/platform.html"){
|
||||
<span class="ti-angle-down"></span>
|
||||
</el-button>
|
||||
<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')"
|
||||
:disabled="runningOnce[scope.row.id]"
|
||||
: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-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text">
|
||||
<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>
|
||||
</el-input>
|
||||
</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-form-item>
|
||||
<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>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
@@ -90,7 +91,7 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item prop="cron" label="执行周期">
|
||||
<el-input placeholder="Cron表达式" v-model="formData.cron" auto-complete="off" tabindex="3" type="text">
|
||||
<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>
|
||||
</el-input>
|
||||
</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-form-item>
|
||||
<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>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
@@ -106,16 +107,72 @@ layout("/layouts/platform.html"){
|
||||
<el-button type="primary" @click="doEdit">确 定</el-button>
|
||||
</span>
|
||||
</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>
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
components: { vcron: vcrontab.default },
|
||||
data: function () {
|
||||
return {
|
||||
addDialogVisible: false,
|
||||
editDialogVisible: false,
|
||||
cronDialogVisible: false,
|
||||
cronDraft: "",
|
||||
cronBuilderVisible: false,
|
||||
cronBuilderExpression: "",
|
||||
cronPreviewLoading: false,
|
||||
cronPreviewRequest: 0,
|
||||
cronPreviewTimes: [],
|
||||
cronPreviewError: "",
|
||||
tableData: [],
|
||||
runningOnce: {},
|
||||
historyVisible: false,
|
||||
historyTask: { id: "", name: "" },
|
||||
historyRows: [],
|
||||
historyPage: 1,
|
||||
historyTotal: 0,
|
||||
historyLoading: false,
|
||||
historyRequest: null,
|
||||
executionLogVisible: false,
|
||||
executionLog: {},
|
||||
executionLogLoading: false,
|
||||
executionLogRequest: null,
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
@@ -134,6 +191,53 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
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) {
|
||||
//按字段排序
|
||||
this.pageForm.pageOrderName = column.prop
|
||||
@@ -171,18 +275,86 @@ layout("/layouts/platform.html"){
|
||||
)
|
||||
},
|
||||
formatAt: function (val) {
|
||||
if (val && val > 0) return moment(val).format("YYYY-MM-DD HH:mm:ss")
|
||||
return ""
|
||||
return val > 0 ? moment(val).utcOffset(480).format("YYYY-MM-DD HH:mm:ss") : "—"
|
||||
},
|
||||
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 () {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData()
|
||||
},
|
||||
openAdd: function () {
|
||||
var self = this
|
||||
this.formData = { name: "", jobClass: "", cron: "", data: "", disabled: false }
|
||||
this.addDialogVisible = true
|
||||
this.formData = {} //打开新增窗口,表单先清空
|
||||
if (this.$refs["addForm"]) this.$refs["addForm"].resetFields()
|
||||
this.$nextTick(function () {
|
||||
this.$refs.addForm.clearValidate()
|
||||
})
|
||||
},
|
||||
doAdd: function () {
|
||||
var self = this
|
||||
@@ -204,7 +376,7 @@ layout("/layouts/platform.html"){
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
self.pageData() //添加失败也可以入库,所以加载一下看看
|
||||
self.pageData()
|
||||
},
|
||||
"json"
|
||||
)
|
||||
@@ -242,6 +414,10 @@ layout("/layouts/platform.html"){
|
||||
dropdownCommand: function (command) {
|
||||
//监听下拉框事件
|
||||
var self = this
|
||||
if ("history" == command.type) {
|
||||
self.openHistory(command)
|
||||
return
|
||||
}
|
||||
if ("enable" == command.type || "disable" == command.type) {
|
||||
$.post(
|
||||
base + "/platform/sys/task/" + command.type + "/" + command.id,
|
||||
@@ -334,6 +510,9 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
created: function () {
|
||||
this.pageData()
|
||||
},
|
||||
mounted: function () {
|
||||
if (this.$el.dataset.openCron === "true") this.openCronGenerator()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user