diff --git a/src/main/java/com/budwk/app/MainLauncher.java b/src/main/java/com/budwk/app/MainLauncher.java index c717fcd..ceee1a7 100644 --- a/src/main/java/com/budwk/app/MainLauncher.java +++ b/src/main/java/com/budwk/app/MainLauncher.java @@ -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 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() { diff --git a/src/main/java/com/budwk/app/sys/controller/SysTaskController.java b/src/main/java/com/budwk/app/sys/controller/SysTaskController.java index be0ecb9..df3f17c 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysTaskController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysTaskController.java @@ -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 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()); - } - sysTaskService.delete(id); - req.setAttribute("name", sysTask.getName()); + Trans.exec(() -> { + Sys_task task = requireTask(id); + sysTaskService.delete(id); + 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) { + return changeDisabled(id, true, req); + } + + private Object changeDisabled(String id, boolean disabled, 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()); - } + 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 previewCron(String normalized) throws Exception { + if (normalized.length() > 50) { + throw new IllegalArgumentException("Cron 表达式最多 50 个字符"); + } + List 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); + } } diff --git a/src/main/java/com/budwk/app/sys/models/Sys_task_execution.java b/src/main/java/com/budwk/app/sys/models/Sys_task_execution.java new file mode 100644 index 0000000..344fa2a --- /dev/null +++ b/src/main/java/com/budwk/app/sys/models/Sys_task_execution.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/task/job/TestJob.java b/src/main/java/com/budwk/app/task/job/TestJob.java index 6f2ad50..e108d21 100644 --- a/src/main/java/com/budwk/app/task/job/TestJob.java +++ b/src/main/java/com/budwk/app/task/job/TestJob.java @@ -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)); } } diff --git a/src/main/java/com/budwk/app/task/log/TaskExecutionListener.java b/src/main/java/com/budwk/app/task/log/TaskExecutionListener.java new file mode 100644 index 0000000..3fe9a0b --- /dev/null +++ b/src/main/java/com/budwk/app/task/log/TaskExecutionListener.java @@ -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); + } + } +} diff --git a/src/main/java/com/budwk/app/task/log/TaskExecutionLogAppender.java b/src/main/java/com/budwk/app/task/log/TaskExecutionLogAppender.java new file mode 100644 index 0000000..509ad4c --- /dev/null +++ b/src/main/java/com/budwk/app/task/log/TaskExecutionLogAppender.java @@ -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 { + 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 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"); + } + } +} diff --git a/src/main/java/com/budwk/app/task/services/TaskPlatformService.java b/src/main/java/com/budwk/app/task/services/TaskPlatformService.java index 2bad0c8..63cc591 100644 --- a/src/main/java/com/budwk/app/task/services/TaskPlatformService.java +++ b/src/main/java/com/budwk/app/task/services/TaskPlatformService.java @@ -19,6 +19,8 @@ public interface TaskPlatformService { void runOnce(Sys_task task) throws Exception; + void restore(Sys_task task) throws Exception; + /** * 添加新任务 * diff --git a/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java b/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java index 0b16bb2..143c7ec 100644 --- a/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java +++ b/src/main/java/com/budwk/app/task/services/impl/TaskPlatformServiceImpl.java @@ -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 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 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 (params != null) { - data.putAll(params); + 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 getCronExeTimes(String cronExpression) throws Exception { - List list = new ArrayList<>(); - CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); - cronTriggerImpl.setCronExpression(cronExpression); - Calendar calendar = Calendar.getInstance(); - Date now = calendar.getTime(); - calendar.add(2, 1); - List dates = TriggerUtils.computeFireTimesBetween(cronTriggerImpl, null, now, calendar.getTime()); + @Override + public List 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 times = new ArrayList<>(5); + Date time = new Date(); + for (int i = 0; i < 5; i++) { + time = expression.getNextValidTimeAfter(time); + if (time == null) { + break; + } + times.add(dateFormat.format(time)); } - return list; + return times; } - @Override - public List getCronExeTimesPlus(String cronExpression) throws Exception { - // 创建一个列表来存储执行时间 - List executionTimes = new ArrayList<>(); - // 创建 CronTriggerImpl 对象,并设置 cron 表达式 - CronTriggerImpl cronTrigger = new CronTriggerImpl(); - cronTrigger.setCronExpression(new CronExpression(cronExpression)); - // 计算未来的执行时间 - List 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 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; } } diff --git a/src/main/java/com/budwk/app/web/commons/base/Globals.java b/src/main/java/com/budwk/app/web/commons/base/Globals.java index 20535a3..83aca40 100644 --- a/src/main/java/com/budwk/app/web/commons/base/Globals.java +++ b/src/main/java/com/budwk/app/web/commons/base/Globals.java @@ -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 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); } } } diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/job/PersonGovernanceSyncJob.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/job/PersonGovernanceSyncJob.java index 32c3b90..df3ba7a 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/job/PersonGovernanceSyncJob.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/job/PersonGovernanceSyncJob.java @@ -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); } } diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java index e417ba9..7425a59 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java @@ -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")); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c220c03..94547c2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -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 diff --git a/src/main/resources/db/quartz/mysql.sql b/src/main/resources/db/quartz/mysql.sql new file mode 100644 index 0000000..8bb2e3f --- /dev/null +++ b/src/main/resources/db/quartz/mysql.sql @@ -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; diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 53662f2..2c3e9d7 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -32,6 +32,8 @@ + + @@ -41,6 +43,7 @@ + -
+
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){ - - + +