From 0ad9d8239c50c06180fbafea8ca7704540dca15f Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Sat, 6 Sep 2025 14:54:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8liteflow=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9A=84=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/compiler.xml | 72 ------ leopard-server/pom.xml | 207 +++++++++--------- .../controller/CommonOptionsController.java | 27 ++- .../server/controller/QuartzController.java | 90 ++++++++ .../server/controller/TaskController.java | 5 +- .../controller/TaskTemplateController.java | 32 ++- .../leopard/server/entity/TaskTemplate.java | 19 +- .../entity/templates/ClassTaskTemplate.java | 36 --- .../leopard/server/service/QuartzService.java | 106 +++++++++ .../leopard/server/service/TaskService.java | 27 +-- .../server/service/task/TaskMonitorNodes.java | 79 +++++++ .../server/service/task/TaskRunner.java | 41 ---- ...k.java => UpdateStockInformationNode.java} | 17 +- .../src/main/resources/application.yml | 10 + leopard-server/src/main/resources/flow.xml | 7 + leopard-web/src/index.tsx | 15 ++ leopard-web/src/pages/Root.tsx | 12 +- .../src/pages/task/TaskScheduleList.tsx | 125 +++++++++++ .../src/pages/task/TaskScheduleSave.tsx | 74 +++++++ .../src/pages/task/TaskTemplateList.tsx | 8 +- .../src/pages/task/TaskTemplateSave.tsx | 24 +- pom.xml | 158 ++++++------- 22 files changed, 763 insertions(+), 428 deletions(-) create mode 100644 leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/QuartzController.java delete mode 100644 leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/entity/templates/ClassTaskTemplate.java create mode 100644 leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/QuartzService.java create mode 100644 leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskMonitorNodes.java delete mode 100644 leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskRunner.java rename leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/{UpdateStockInformationTask.java => UpdateStockInformationNode.java} (76%) create mode 100644 leopard-server/src/main/resources/flow.xml create mode 100644 leopard-web/src/pages/task/TaskScheduleList.tsx create mode 100644 leopard-web/src/pages/task/TaskScheduleSave.tsx diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a9efd3a..29f6ae8 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -85,78 +85,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/leopard-server/pom.xml b/leopard-server/pom.xml index 89ac7fc..f102056 100644 --- a/leopard-server/pom.xml +++ b/leopard-server/pom.xml @@ -9,107 +9,116 @@ 1.0.0 - leopard-server + leopard-server - - - com.lanyuanxiaoyao - spring-boot-service-template - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - org.springframework.boot - spring-boot-starter-jetty - - - org.springframework.boot - spring-boot-configuration-processor - true - + + + com.lanyuanxiaoyao + spring-boot-service-template + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-quartz + - - cn.hutool - hutool-core - - - cn.hutool - hutool-http - + + com.yomahub + liteflow-spring-boot-starter + - - com.mysql - mysql-connector-j - runtime - - + + cn.hutool + hutool-core + + + cn.hutool + hutool-http + - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.springframework.boot - spring-boot-configuration-processor - - - org.projectlombok - lombok - - - org.hibernate - hibernate-jpamodelgen - 6.6.8.Final - - - io.github.openfeign.querydsl - querydsl-apt - 7.0 - jpa - - - jakarta.persistence - jakarta.persistence-api - 3.2.0 - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - package - - repackage - - - - - - + + com.mysql + mysql-connector-j + runtime + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + org.hibernate + hibernate-jpamodelgen + 6.6.8.Final + + + io.github.openfeign.querydsl + querydsl-apt + 7.0 + jpa + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + package + + repackage + + + + + + \ No newline at end of file diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/CommonOptionsController.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/CommonOptionsController.java index e3a1b23..627452d 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/CommonOptionsController.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/CommonOptionsController.java @@ -2,14 +2,15 @@ package com.lanyuanxiaoyao.leopard.server.controller; import com.lanyuanxiaoyao.leopard.server.entity.Stock; import com.lanyuanxiaoyao.leopard.server.entity.Task; -import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate; import com.lanyuanxiaoyao.leopard.server.service.StockService; +import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService; import com.lanyuanxiaoyao.service.template.controller.GlobalResponse; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.quartz.Trigger; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -82,9 +83,11 @@ public class CommonOptionsController { ); private final StockService stockService; + private final TaskTemplateService taskTemplateService; - public CommonOptionsController(StockService stockService) { + public CommonOptionsController(StockService stockService, TaskTemplateService taskTemplateService) { this.stockService = stockService; + this.taskTemplateService = taskTemplateService; } @GetMapping("/options/{name}") @@ -107,9 +110,10 @@ public class CommonOptionsController { new Option("已退市", false) ) ); - case "task_template_type" -> GlobalResponse.responseSuccess( - Arrays.stream(TaskTemplate.Type.values()) - .map(type -> new Option(type.getChineseName(), type.name())) + case "task_template_id" -> GlobalResponse.responseSuccess( + taskTemplateService.list() + .stream() + .map(template -> new Option(template.getName(), template.getId())) .toList() ); default -> GlobalResponse.responseSuccess(List.of()); @@ -138,10 +142,15 @@ public class CommonOptionsController { .toList(), field )); - case "task_template_type" -> GlobalResponse.responseSuccess(buildMapping( - Arrays.stream(TaskTemplate.Type.values()) - .map(type -> new Mapping(type.name(), type.getChineseName())) - .toList(), + case "trigger_status" -> GlobalResponse.responseSuccess(buildMapping( + List.of( + new Mapping(Trigger.TriggerState.NONE.name(), "无"), + new Mapping(Trigger.TriggerState.NORMAL.name(), "正常"), + new Mapping(Trigger.TriggerState.PAUSED.name(), "暂停"), + new Mapping(Trigger.TriggerState.COMPLETE.name(), "完成"), + new Mapping(Trigger.TriggerState.ERROR.name(), "错误"), + new Mapping(Trigger.TriggerState.BLOCKED.name(), "阻塞") + ), field )); default -> GlobalResponse.responseSuccess(Map.of()); diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/QuartzController.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/QuartzController.java new file mode 100644 index 0000000..b8c01b6 --- /dev/null +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/QuartzController.java @@ -0,0 +1,90 @@ +package com.lanyuanxiaoyao.leopard.server.controller; + +import com.lanyuanxiaoyao.leopard.server.service.QuartzService; +import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService; +import com.lanyuanxiaoyao.service.template.controller.GlobalResponse; +import java.time.LocalDateTime; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("task_schedule") +public class QuartzController { + private final QuartzService quartzService; + private final TaskTemplateService taskTemplateService; + + public QuartzController(QuartzService quartzService, TaskTemplateService taskTemplateService) { + this.quartzService = quartzService; + this.taskTemplateService = taskTemplateService; + } + + @PostMapping("save") + public GlobalResponse save(@RequestBody SaveItem item) throws SchedulerException { + quartzService.save(item.templateId(), item.cron()); + return GlobalResponse.responseSuccess(); + } + + @GetMapping("list") + public GlobalResponse> list() throws SchedulerException { + var list = quartzService.list() + .stream() + .map(task -> { + var template = taskTemplateService.detail(task.templateId()); + return new ListItem( + task.key(), + template.getName(), + template.getDescription(), + task.cron(), + task.status(), + task.previousFireTime(), + task.nextFireTime() + ); + }) + .toList(); + return GlobalResponse.responseSuccess(list); + } + + @GetMapping("pause/{key}") + public GlobalResponse pause(@PathVariable("key") String key) throws SchedulerException { + quartzService.pause(key); + return GlobalResponse.responseSuccess(); + } + + @GetMapping("resume/{key}") + public GlobalResponse resume(@PathVariable("key") String key) throws SchedulerException { + quartzService.resume(key); + return GlobalResponse.responseSuccess(); + } + + @GetMapping("remove/{key}") + public GlobalResponse remove(@PathVariable("key") String key) throws SchedulerException { + quartzService.remove(key); + return GlobalResponse.responseSuccess(); + } + + public record SaveItem( + Long templateId, + String cron + ) { + } + + public record ListItem( + String key, + String templateName, + String templateDescription, + String cron, + Trigger.TriggerState status, + LocalDateTime previousFireTime, + LocalDateTime nextFireTime + ) { + } +} diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskController.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskController.java index 9203a03..8a8f8f8 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskController.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskController.java @@ -5,6 +5,7 @@ import com.lanyuanxiaoyao.leopard.server.service.TaskService; import com.lanyuanxiaoyao.service.template.controller.GlobalResponse; import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport; import java.time.LocalDateTime; +import java.util.Map; import java.util.function.Function; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; @@ -36,7 +37,7 @@ public class TaskController extends SimpleControllerSupport execute(@RequestBody ExecuteRequest request) { - taskService.execute(request.templateId()); + taskService.execute(request.templateId(), request.params()); return GlobalResponse.responseSuccess(); } @@ -93,6 +94,6 @@ public class TaskController extends SimpleControllerSupport params) { } } diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskTemplateController.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskTemplateController.java index da5f6d7..8989ee5 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskTemplateController.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/controller/TaskTemplateController.java @@ -1,7 +1,6 @@ package com.lanyuanxiaoyao.leopard.server.controller; import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate; -import com.lanyuanxiaoyao.leopard.server.entity.templates.ClassTaskTemplate; import com.lanyuanxiaoyao.leopard.server.service.TaskTemplateService; import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport; import java.util.function.Function; @@ -20,26 +19,22 @@ public class TaskTemplateController extends SimpleControllerSupport saveItemMapper() { return item -> { - return switch (item.type) { - case CLASS -> { - var template = new ClassTaskTemplate(); - template.setId(item.id()); - template.setName(item.name()); - template.setDescription(item.description()); - template.setClazz(item.clazz()); - yield template; - } - }; + var template = new TaskTemplate(); + template.setId(item.id()); + template.setName(item.name()); + template.setDescription(item.description()); + template.setChain(item.chain()); + return template; }; } private Item convert(TaskTemplate template) { - return switch (template.getType()) { - case CLASS -> { - ClassTaskTemplate classTaskTemplate = (ClassTaskTemplate) template; - yield new Item(classTaskTemplate.getId(), classTaskTemplate.getName(), classTaskTemplate.getDescription(), classTaskTemplate.getType(), classTaskTemplate.getClazz()); - } - }; + return new Item( + template.getId(), + template.getName(), + template.getDescription(), + template.getChain() + ); } @Override @@ -56,8 +51,7 @@ public class TaskTemplateController extends SimpleControllerSupport list() throws SchedulerException { + var tasks = new ArrayList(); + for (var key : scheduler.getJobKeys(GroupMatcher.anyGroup())) { + var detail = scheduler.getJobDetail(key); + var trigger = (CronTrigger) scheduler.getTriggersOfJob(key).get(0); + tasks.add(new QuartzTask( + detail.getKey().getName(), + detail.getJobDataMap().getLong("template_id"), + trigger.getCronExpression(), + scheduler.getTriggerState(trigger.getKey()), + ObjectUtil.isNull(trigger.getPreviousFireTime()) ? null : LocalDateTime.ofInstant(trigger.getPreviousFireTime().toInstant(), ZoneId.systemDefault()), + ObjectUtil.isNull(trigger.getNextFireTime()) ? null : LocalDateTime.ofInstant(trigger.getNextFireTime().toInstant(), ZoneId.systemDefault()) + )); + } + return tasks; + } + + public void save(Long templateId, String cron) throws SchedulerException { + var detail = JobBuilder.newJob(TaskExecutionJob.class) + .withIdentity("task_execution_" + IdUtil.fastUUID()) + .usingJobData("template_id", templateId) + .storeDurably() + .build(); + var trigger = TriggerBuilder.newTrigger() + .forJob(detail) + .withIdentity("task_execution_" + IdUtil.fastUUID()) + .withSchedule( + CronScheduleBuilder.cronSchedule(cron) + .withMisfireHandlingInstructionDoNothing() + ) + .build(); + scheduler.scheduleJob(detail, trigger); + } + + public void pause(String jobKey) throws SchedulerException { + scheduler.pauseJob(JobKey.jobKey(jobKey)); + } + + public void resume(String jobKey) throws SchedulerException { + scheduler.resumeJob(JobKey.jobKey(jobKey)); + } + + public void remove(String jobKey) throws SchedulerException { + pause(jobKey); + scheduler.deleteJob(JobKey.jobKey(jobKey)); + } + + @Slf4j + public static class TaskExecutionJob extends QuartzJobBean { + private final TaskService taskService; + + public TaskExecutionJob(TaskService taskService) { + this.taskService = taskService; + } + + @Override + protected void executeInternal(JobExecutionContext context) { + var dataMap = context.getMergedJobDataMap(); + var templateId = dataMap.getLong("template_id"); + var parms = (Map) dataMap.getOrDefault("params", Map.of()); + taskService.execute(templateId, parms); + } + } + + public record QuartzTask( + String key, + Long templateId, + String cron, + Trigger.TriggerState status, + LocalDateTime previousFireTime, + LocalDateTime nextFireTime + ) { + } +} diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/TaskService.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/TaskService.java index f68290d..47bbcb1 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/TaskService.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/TaskService.java @@ -1,15 +1,12 @@ package com.lanyuanxiaoyao.leopard.server.service; import com.lanyuanxiaoyao.leopard.server.entity.Task; -import com.lanyuanxiaoyao.leopard.server.entity.templates.ClassTaskTemplate; import com.lanyuanxiaoyao.leopard.server.repository.TaskRepository; -import com.lanyuanxiaoyao.leopard.server.service.task.TaskRunner; +import com.lanyuanxiaoyao.leopard.server.service.task.TaskMonitorNodes; import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; +import com.yomahub.liteflow.core.FlowExecutor; import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; /** @@ -19,26 +16,18 @@ import org.springframework.stereotype.Service; @Slf4j @Service public class TaskService extends SimpleServiceSupport { - private final ApplicationContext context; private final TaskTemplateService taskTemplateService; - private final ExecutorService executors = Executors.newFixedThreadPool(50); + private final FlowExecutor flowExecutor; - public TaskService(TaskRepository repository, ApplicationContext context, TaskTemplateService taskTemplateService) { + public TaskService(TaskRepository repository, TaskTemplateService taskTemplateService, @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") FlowExecutor flowExecutor) { super(repository); - this.context = context; this.taskTemplateService = taskTemplateService; + this.flowExecutor = flowExecutor; } - public void execute(Long templateId) { + public void execute(Long templateId, Map params) { var template = taskTemplateService.detail(templateId); - switch (template.getType()) { - case CLASS -> { - ClassTaskTemplate classTaskTemplate = (ClassTaskTemplate) template; - var runner = context.getBean(classTaskTemplate.getClazz(), TaskRunner.class); - executors.submit(() -> { - runner.runTask(classTaskTemplate, Map.of()); - }); - } - } + var context = new TaskMonitorNodes.TaskMonitorContext(template); + flowExecutor.execute2Future(template.getChain(), params, context); } } diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskMonitorNodes.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskMonitorNodes.java new file mode 100644 index 0000000..9c6f77d --- /dev/null +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskMonitorNodes.java @@ -0,0 +1,79 @@ +package com.lanyuanxiaoyao.leopard.server.service.task; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.leopard.server.entity.Task; +import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate; +import com.lanyuanxiaoyao.leopard.server.service.TaskService; +import com.yomahub.liteflow.annotation.LiteflowComponent; +import com.yomahub.liteflow.annotation.LiteflowFact; +import com.yomahub.liteflow.annotation.LiteflowMethod; +import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.enums.LiteFlowMethodEnum; +import com.yomahub.liteflow.enums.NodeTypeEnum; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@LiteflowComponent +public class TaskMonitorNodes { + private final TaskService taskService; + + public TaskMonitorNodes(TaskService taskService) { + this.taskService = taskService; + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_start", nodeName = "任务开始", nodeType = NodeTypeEnum.COMMON) + public void taskStart(NodeComponent node) { + var context = node.getContextBean(TaskMonitorContext.class); + if (ObjectUtil.isNotNull(context)) { + var task = new Task(); + task.setName(context.getTemplate().getName()); + task.setDescription(context.getTemplate().getDescription()); + task.setStatus(Task.Status.RUNNING); + task.setLaunchedTime(LocalDateTime.now()); + var taskId = taskService.save(task); + context.setTaskId(taskId); + } + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_end", nodeName = "任务结束", nodeType = NodeTypeEnum.COMMON) + public void taskEnd(NodeComponent node, @LiteflowFact("taskId") Long taskId) { + if (ObjectUtil.isNotNull(taskId)) { + var task = taskService.detail(taskId); + task.setStatus(Task.Status.SUCCESS); + task.setFinishedTime(LocalDateTime.now()); + var result = node.getContextValue("taskResult"); + if (StrUtil.isNotBlank(result)) { + task.setResult(result); + } + taskService.save(task); + } + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "task_error", nodeName = "任务错误", nodeType = NodeTypeEnum.COMMON) + public void taskError(NodeComponent node, @LiteflowFact("taskId") Long taskId) { + if (ObjectUtil.isNotNull(taskId)) { + var task = taskService.detail(taskId); + task.setStatus(Task.Status.FAILURE); + task.setFinishedTime(LocalDateTime.now()); + var exception = node.getSlot().getException(); + if (ObjectUtil.isNotNull(exception)) { + task.setError(exception.getMessage()); + } + taskService.save(task); + } + } + + @Data + public static final class TaskMonitorContext { + private TaskTemplate template; + private Long taskId; + private String taskResult; + + public TaskMonitorContext(TaskTemplate template) { + this.template = template; + } + } +} diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskRunner.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskRunner.java deleted file mode 100644 index bd0bc82..0000000 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/TaskRunner.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.lanyuanxiaoyao.leopard.server.service.task; - -import cn.hutool.core.util.StrUtil; -import com.lanyuanxiaoyao.leopard.server.entity.Task; -import com.lanyuanxiaoyao.leopard.server.entity.TaskTemplate; -import com.lanyuanxiaoyao.leopard.server.service.TaskService; -import java.time.LocalDateTime; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public abstract class TaskRunner { - private final TaskService taskService; - - protected TaskRunner(TaskService taskService) { - this.taskService = taskService; - } - - public void runTask(TaskTemplate template, Map params) { - var task = new Task(); - task.setName(template.getName()); - task.setDescription(template.getDescription()); - task.setStatus(Task.Status.RUNNING); - task.setLaunchedTime(LocalDateTime.now()); - task.setId(taskService.save(task)); - try { - var result = run(params); - task.setStatus(Task.Status.SUCCESS); - if (StrUtil.isNotBlank(result)) { - task.setResult(result); - } - } catch (Exception e) { - task.setStatus(Task.Status.FAILURE); - task.setError(e.getMessage()); - } - task.setFinishedTime(LocalDateTime.now()); - taskService.save(task); - } - - abstract String run(Map params); -} diff --git a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationTask.java b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationNode.java similarity index 76% rename from leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationTask.java rename to leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationNode.java index ba7a478..1741046 100644 --- a/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationTask.java +++ b/leopard-server/src/main/java/com/lanyuanxiaoyao/leopard/server/service/task/UpdateStockInformationNode.java @@ -4,25 +4,25 @@ import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.StrUtil; import com.lanyuanxiaoyao.leopard.server.entity.Stock; import com.lanyuanxiaoyao.leopard.server.service.StockService; -import com.lanyuanxiaoyao.leopard.server.service.TaskService; import com.lanyuanxiaoyao.leopard.server.service.TuShareService; -import java.util.Map; +import com.yomahub.liteflow.annotation.LiteflowComponent; +import com.yomahub.liteflow.core.NodeComponent; +import jakarta.transaction.Transactional; import java.util.stream.Collectors; -import org.springframework.stereotype.Component; -@Component("com.lanyuanxiaoyao.leopard.server.service.task.UpdateStockInformationTask") -public class UpdateStockInformationTask extends TaskRunner { +@LiteflowComponent("update_stock_information") +public class UpdateStockInformationNode extends NodeComponent { private final StockService stockService; private final TuShareService tuShareService; - public UpdateStockInformationTask(TaskService taskService, StockService stockService, TuShareService tuShareService) { - super(taskService); + public UpdateStockInformationNode(StockService stockService, TuShareService tuShareService) { this.stockService = stockService; this.tuShareService = tuShareService; } + @Transactional(rollbackOn = Throwable.class) @Override - public String run(Map params) { + public void process() throws Exception { var stocks = stockService.list(); var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock)); tuShareService.stockList() @@ -54,6 +54,5 @@ public class UpdateStockInformationTask extends TaskRunner { } }); stockService.save(stocks); - return null; } } diff --git a/leopard-server/src/main/resources/application.yml b/leopard-server/src/main/resources/application.yml index b7d2c86..944a64a 100644 --- a/leopard-server/src/main/resources/application.yml +++ b/leopard-server/src/main/resources/application.yml @@ -15,5 +15,15 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver jpa: generate-ddl: true + quartz: + wait-for-jobs-to-complete-on-shutdown: true + startup-delay: 30s + job-store-type: jdbc + jdbc: + platform: mysql fenix: print-banner: false +liteflow: + print-banner: false + rule-source: flow.xml + check-node-exists: false \ No newline at end of file diff --git a/leopard-server/src/main/resources/flow.xml b/leopard-server/src/main/resources/flow.xml new file mode 100644 index 0000000..a7d4480 --- /dev/null +++ b/leopard-server/src/main/resources/flow.xml @@ -0,0 +1,7 @@ + + + + + CATCH(THEN(task_start, update_stock_information, task_end)).DO(task_error) + + diff --git a/leopard-web/src/index.tsx b/leopard-web/src/index.tsx index d09fd21..395c81e 100644 --- a/leopard-web/src/index.tsx +++ b/leopard-web/src/index.tsx @@ -11,6 +11,8 @@ import TaskList from './pages/task/TaskList.tsx' import TaskAdd from './pages/task/TaskAdd.tsx' import TaskTemplateList from './pages/task/TaskTemplateList.tsx' import TaskTemplateSave from './pages/task/TaskTemplateSave.tsx' +import TaskScheduleList from './pages/task/TaskScheduleList.tsx' +import TaskScheduleSave from './pages/task/TaskScheduleSave.tsx' const routes: RouteObject[] = [ { @@ -66,6 +68,19 @@ const routes: RouteObject[] = [ }, ], }, + { + path: 'schedule', + children: [ + { + path: 'list', + Component: TaskScheduleList, + }, + { + path: 'save', + Component: TaskScheduleSave, + }, + ], + }, ], }, { diff --git a/leopard-web/src/pages/Root.tsx b/leopard-web/src/pages/Root.tsx index 0ae20d8..2f23629 100644 --- a/leopard-web/src/pages/Root.tsx +++ b/leopard-web/src/pages/Root.tsx @@ -2,8 +2,8 @@ import { DeploymentUnitOutlined, InfoCircleOutlined, MoneyCollectOutlined, - UnorderedListOutlined -} from "@ant-design/icons"; + UnorderedListOutlined, +} from '@ant-design/icons' import {type AppItemProps, ProLayout} from '@ant-design/pro-components' import {ConfigProvider} from 'antd' import {dateFormat} from 'licia' @@ -52,8 +52,12 @@ const menus = { { path: '/task/template/list', name: '任务模板', - } - ] + }, + { + path: '/task/schedule/list', + name: '定时任务', + }, + ], }, { path: '/test', diff --git a/leopard-web/src/pages/task/TaskScheduleList.tsx b/leopard-web/src/pages/task/TaskScheduleList.tsx new file mode 100644 index 0000000..2e2ea1a --- /dev/null +++ b/leopard-web/src/pages/task/TaskScheduleList.tsx @@ -0,0 +1,125 @@ +import React from 'react' +import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, remoteMappings, time} from '../../util/amis.tsx' +import {useNavigate} from 'react-router' + +function TaskScheduleList() { + const navigate = useNavigate() + return ( +
+ {amisRender( + { + type: 'page', + title: '定时任务', + body: [ + { + type: 'crud', + api: `get:${commonInfo.baseUrl}/task_schedule/list`, + ...crudCommonOptions(), + ...paginationTemplate( + 15, + undefined, + [ + { + type: 'action', + label: '', + icon: 'fa fa-plus', + tooltip: '添加定时任务', + tooltipPlacement: 'top', + onEvent: { + click: { + actions: [ + { + actionType: 'custom', + // @ts-ignore + script: (context, action, event) => { + navigate('/task/schedule/save') + }, + }, + ], + }, + }, + }, + ], + ), + columns: [ + { + name: 'templateName', + label: '模板名称', + width: 150, + }, + { + name: 'templateDescription', + label: '模板描述', + }, + { + name: 'cron', + label: '定时策略', + width: 150, + }, + { + name: 'status', + label: '状态', + width: 100, + ...remoteMappings('trigger_status', 'status'), + }, + { + name: 'previousFireTime', + label: '上次启动时间', + width: 150, + align: 'center', + ...time('previousFireTime'), + }, + { + name: 'nextFireTime', + label: '下次启动时间', + width: 150, + align: 'center', + ...time('nextFireTime'), + }, + { + type: 'operation', + label: '操作', + width: 150, + buttons: [ + { + visibleOn: 'status !== \'PAUSED\' ', + type: 'action', + label: '暂停', + level: 'link', + actionType: 'ajax', + api: `get:${commonInfo.baseUrl}/task_schedule/pause/\${key}`, + confirmText: '确认暂停定时任务${templateName}?', + confirmTitle: '暂停', + }, + { + visibleOn: 'status === \'PAUSED\' ', + type: 'action', + label: '恢复', + level: 'link', + actionType: 'ajax', + api: `get:${commonInfo.baseUrl}/task_schedule/resume/\${key}`, + confirmText: '确认恢复定时任务${templateName}?', + confirmTitle: '恢复', + }, + { + className: 'text-danger', + type: 'action', + label: '删除', + level: 'link', + actionType: 'ajax', + api: `get:${commonInfo.baseUrl}/task_schedule/remove/\${key}`, + confirmText: '确认删除定时任务${templateName}?', + confirmTitle: '删除', + }, + ], + }, + ], + }, + ], + }, + )} +
+ ) +} + +export default React.memo(TaskScheduleList) \ No newline at end of file diff --git a/leopard-web/src/pages/task/TaskScheduleSave.tsx b/leopard-web/src/pages/task/TaskScheduleSave.tsx new file mode 100644 index 0000000..d7bf8aa --- /dev/null +++ b/leopard-web/src/pages/task/TaskScheduleSave.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import {amisRender, commonInfo, remoteOptions} from '../../util/amis.tsx' +import {useNavigate} from 'react-router' + +function TaskScheduleSave() { + const navigate = useNavigate() + return ( +
+ {amisRender( + { + type: 'page', + title: '任务模板添加', + body: [ + { + debug: commonInfo.debug, + type: 'form', + api: `post:${commonInfo.baseUrl}/task_schedule/save`, + wrapWithPanel: false, + mode: 'horizontal', + labelAlign: 'left', + onEvent: { + submitSucc: { + actions: [ + { + actionType: 'custom', + // @ts-ignore + script: (context, action, event) => { + navigate(-1) + }, + }, + ], + }, + }, + body: [ + { + name: 'templateId', + label: '名称', + required: true, + selectFirst: true, + ...remoteOptions('select', 'task_template_id'), + }, + { + type: 'input-text', + name: 'cron', + label: '定时策略', + required: true, + clearable: true, + }, + { + type: 'button-toolbar', + buttons: [ + { + type: 'action', + label: '提交', + actionType: 'submit', + level: 'primary', + }, + { + type: 'action', + label: '重置', + actionType: 'reset', + }, + ], + }, + ], + }, + ], + }, + )} +
+ ) +} + +export default React.memo(TaskScheduleSave) \ No newline at end of file diff --git a/leopard-web/src/pages/task/TaskTemplateList.tsx b/leopard-web/src/pages/task/TaskTemplateList.tsx index 4ef8065..baa0df3 100644 --- a/leopard-web/src/pages/task/TaskTemplateList.tsx +++ b/leopard-web/src/pages/task/TaskTemplateList.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, remoteMappings} from '../../util/amis.tsx' +import {amisRender, commonInfo, crudCommonOptions, paginationTemplate} from '../../util/amis.tsx' import {useNavigate} from 'react-router' function TaskTemplateList() { @@ -60,12 +60,6 @@ function TaskTemplateList() { name: 'description', label: '描述', }, - { - name: 'type', - label: '类型', - width: 100, - ...remoteMappings('task_template_type', 'type'), - }, { type: 'operation', label: '操作', diff --git a/leopard-web/src/pages/task/TaskTemplateSave.tsx b/leopard-web/src/pages/task/TaskTemplateSave.tsx index ee51af5..c74152f 100644 --- a/leopard-web/src/pages/task/TaskTemplateSave.tsx +++ b/leopard-web/src/pages/task/TaskTemplateSave.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {amisRender, commonInfo, remoteOptions} from '../../util/amis.tsx' +import {amisRender, commonInfo} from '../../util/amis.tsx' import {useNavigate, useParams} from 'react-router' function TaskTemplateSave() { @@ -31,8 +31,8 @@ function TaskTemplateSave() { navigate(-1) }, }, - ] - } + ], + }, }, body: [ { @@ -43,29 +43,21 @@ function TaskTemplateSave() { type: 'input-text', name: 'name', label: '名称', - require: true, + required: true, clearable: true, }, { type: 'textarea', name: 'description', label: '描述', - require: true, + required: true, clearable: true, }, { - name: 'type', - label: '任务类型', - require: true, - selectFirst: true, - ...remoteOptions('select', 'task_template_type'), - }, - { - visibleOn: 'type === \'CLASS\'', type: 'input-text', - name: 'clazz', - label: '类路径', - require: true, + name: 'chain', + label: '流程编号', + required: true, clearable: true, }, { diff --git a/pom.xml b/pom.xml index 5060bc0..054cb6e 100644 --- a/pom.xml +++ b/pom.xml @@ -2,97 +2,97 @@ - 4.0.0 + 4.0.0 com.lanyuanxiaoyao leopard 1.0.0 pom - - leopard-server - + + leopard-server + - - 17 - 17 - 17 - UTF-8 + + 17 + 17 + 17 + UTF-8 - 3.5.0 - 2025.0.0 - 1.0.1 + 3.5.0 + 2025.0.0 + 1.0.1 - 13.0.0 - 5.8.39 - 2.13.2 - + 13.0.0 + 5.8.39 + 2.15.0 + + + + org.projectlombok + lombok + compile + + + + - - org.projectlombok - lombok - compile - + + com.lanyuanxiaoyao + spring-boot-service-template + 1.0.0-SNAPSHOT + + + + cn.hutool + hutool-core + ${hutool.version} + + + cn.hutool + hutool-http + ${hutool.version} + + + + com.yomahub + liteflow-spring-boot-starter + ${liteflow.version} + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + - - - - com.lanyuanxiaoyao - spring-boot-service-template - 1.0.0-SNAPSHOT - - - - cn.hutool - hutool-core - ${hutool.version} - - - cn.hutool - hutool-http - ${hutool.version} - - - - com.yomahub - liteflow-spring-boot-starter - ${liteflow.version} - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - org.springframework.ai - spring-ai-bom - ${spring-ai.version} - pom - import - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.14.0 - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + \ No newline at end of file