From 1ee1289c4deae69777acf68b175f0a7750b1b42b Mon Sep 17 00:00:00 2001 From: may <1742057357@qq.com> Date: Wed, 21 Oct 2026 17:06:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20=E6=96=B0=E5=A2=9E=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1=EF=BC=88=E4=BA=8B=E4=BB=B6=E7=AD=89?= =?UTF-8?q?=E5=BE=85=EF=BC=89=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3=E4=B8=8E?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 FlwEventWaitController:/workflow/eventWait 分页列表、状态汇总、详情、 手动补发,权限分别为 workflow:eventWait:list / query / trigger - FlowEventWaitBo 支持按流程编码、节点编码、业务主键、事件类型、状态、 下次触发时间区间筛选;多值条件用逗号串传递 - FlowEventWaitVo 装配流程名称、业务主键,并透出 timerEnabled, 供前端在定时引擎未开启时给出提示 - 手动补发复用引擎 InsService#startByTimer,不读写等待记录, 下次触发时间不受影响;业务主键留空时生成 long 型雪花 ID - 四方言全量脚本补充「流程监控 → 定时任务」菜单与查询、手动补发按钮 - 升级脚本补充 flow_event_wait 建表(6.0.0 存量库没有该表)与定时任务菜单 --- .../controller/FlwEventWaitController.java | 95 ++++++++ .../workflow/domain/bo/FlowEventWaitBo.java | 136 +++++++++++ .../domain/bo/FlowTimerTriggerBo.java | 37 +++ .../workflow/domain/vo/FlowEventWaitVo.java | 158 ++++++++++++ .../service/IFlwEventWaitService.java | 53 +++++ .../service/impl/FlwEventWaitServiceImpl.java | 225 ++++++++++++++++++ script/sql/oracle/oracle_ry_workflow.sql | 5 + script/sql/postgres/postgres_ry_workflow.sql | 5 + script/sql/ry_workflow.sql | 5 + .../sql/sqlserver/sqlserver_ry_workflow.sql | 8 + script/sql/update/update_6.0.0-6.1.0.sql | 48 ++++ 11 files changed, 775 insertions(+) create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwEventWaitController.java create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowEventWaitBo.java create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTimerTriggerBo.java create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowEventWaitVo.java create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwEventWaitService.java create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwEventWaitServiceImpl.java diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwEventWaitController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwEventWaitController.java new file mode 100644 index 000000000..3ff758728 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwEventWaitController.java @@ -0,0 +1,95 @@ +package org.dromara.workflow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.PageResult; +import org.dromara.common.core.domain.R; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.redis.annotation.RepeatSubmit; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.domain.bo.FlowEventWaitBo; +import org.dromara.workflow.domain.bo.FlowTimerTriggerBo; +import org.dromara.workflow.domain.vo.FlowEventWaitVo; +import org.dromara.workflow.service.IFlwEventWaitService; +import org.springframework.validation.annotation.Validated; +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; + +import java.util.Map; + +/** + * 定时任务(事件等待记录)控制器。 + *

数据来源为引擎事件等待表,默认只查定时类事件;消息类 / 错误类等待需显式放开事件类型范围。 + * 写操作只有一项:对定时启动任务手动补发一次实例——补发不读取、也不推进等待记录, + * 定时任务的下次触发时间不受影响。

+ * + * @author may + * @since 2026/10/21 + */ +@ConditionalOnEnable +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/eventWait") +public class FlwEventWaitController extends BaseController { + + private final IFlwEventWaitService flwEventWaitService; + + /** + * 分页查询定时任务(事件等待记录)列表。 + * + * @param bo 查询条件 + * @return 等待记录分页数据 + */ + @SaCheckPermission("workflow:eventWait:list") + @GetMapping("/list") + public R> list(FlowEventWaitBo bo) { + return R.ok(flwEventWaitService.queryPageList(bo)); + } + + /** + * 按同一组筛选条件统计各状态等待记录数量。 + * + * @param bo 查询条件 + * @return 各状态计数 + */ + @SaCheckPermission("workflow:eventWait:list") + @GetMapping("/summary") + public R> summary(FlowEventWaitBo bo) { + return R.ok(flwEventWaitService.summary(bo)); + } + + /** + * 查询定时任务详情。 + * + * @param id 事件等待记录主键 + * @return 等待记录详情 + */ + @SaCheckPermission("workflow:eventWait:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(flwEventWaitService.queryById(id)); + } + + /** + * 手动补发一次定时启动任务(运维应急)。 + *

不推进等待记录,下一次定时触发照常进行;实例发起人记为当前登录用户。

+ * + * @param bo 补发参数 + * @return 新建实例主键 + */ + @SaCheckPermission("workflow:eventWait:trigger") + @Log(title = "定时任务手动补发", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/trigger") + public R trigger(@RequestBody FlowTimerTriggerBo bo) { + return R.ok(flwEventWaitService.manualTrigger(bo)); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowEventWaitBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowEventWaitBo.java new file mode 100644 index 000000000..8f924060a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowEventWaitBo.java @@ -0,0 +1,136 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; +import org.dromara.warm.flow.core.dto.EventWaitQuery; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * 定时任务(事件等待记录)查询业务对象。 + *

过滤维度与引擎 {@link EventWaitQuery} 一一对应:多值条件取「或」、不同条件之间取「且」; + * 事件类型留空时按 {@link #timerOnly} 决定是否只查定时类事件。数据来自引擎 flow_event_wait, + * 本对象只读,不含写操作字段。

+ * + * @author may + * @since 2026/10/21 + */ +@Data +public class FlowEventWaitBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 页码 + */ + private Integer pageNum; + + /** + * 每页条数 + */ + private Integer pageSize; + + /** + * 排序字段(引擎白名单:id / nextTriggerAt / retryAt / createTime / updateTime / status) + */ + private String orderByColumn; + + /** + * 排序方向 asc / desc + */ + private String isAsc; + + /** + * 等待状态多选(WAITING / RETRY / RUNNING / DEAD / CANCELLED),逗号分隔,留空表示不限 + */ + private String statuses; + + /** + * 事件类型多选(TIMER_START / TIMER_CATCH / TIMER_BOUNDARY / …),逗号分隔, + * 留空时按 timerOnly 取默认范围 + */ + private String eventTypes; + + /** + * 是否只查定时类事件,默认 true + */ + private Boolean timerOnly = true; + + /** + * 流程定义编码 + */ + private String flowCode; + + /** + * 业务流程主键 + */ + private String businessId; + + /** + * 事件节点编码 + */ + private String nodeCode; + + /** + * 事件名称(消息名 / 错误码) + */ + private String eventName; + + /** + * 下次触发时间下界 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date nextTriggerAtFrom; + + /** + * 下次触发时间上界 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date nextTriggerAtTo; + + /** + * 转换为引擎查询条件。 + * + * @return 引擎只读查询条件 + */ + public EventWaitQuery toQuery() { + EventWaitQuery query = new EventWaitQuery(); + query.setPageNum(pageNum == null || pageNum < 1 ? 1 : pageNum); + query.setPageSize(pageSize == null || pageSize < 1 ? 10 : pageSize); + query.setStatuses(splitCsv(statuses)); + query.setEventTypes(splitCsv(eventTypes)); + query.setTimerOnly(!Boolean.FALSE.equals(timerOnly)); + query.setFlowCode(flowCode); + query.setBusinessId(businessId); + query.setNodeCode(nodeCode); + query.setEventName(eventName); + query.setNextTriggerAtFrom(nextTriggerAtFrom); + query.setNextTriggerAtTo(nextTriggerAtTo); + if (orderByColumn != null && !orderByColumn.isBlank()) { + query.setOrderBy(orderByColumn); + query.setIsAsc(isAsc); + } + return query; + } + + /** + * 逗号分隔串拆成引擎多值条件;空串返回 {@code null} 表示不限。 + * + * @param csv 逗号分隔串 + * @return 多值列表或 null + */ + private static List splitCsv(String csv) { + if (csv == null || csv.isBlank()) { + return null; + } + return Arrays.stream(csv.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTimerTriggerBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTimerTriggerBo.java new file mode 100644 index 000000000..7508afb1a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTimerTriggerBo.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; + +/** + * 定时启动任务手动补发业务对象。 + *

只对 TIMER_START 事件的等待记录生效;补发只创建一次实例,不读取也不推进等待记录, + * 定时任务的下次触发时间不受影响。

+ * + * @author may + * @since 2026/10/21 + */ +@Data +public class FlowTimerTriggerBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 事件等待记录主键 + */ + private Long waitId; + + /** + * 业务主键,留空时按 M{waitId}_{时间戳} 自动生成 + */ + private String businessId; + + /** + * 额外流程变量,合并到节点默认变量之后 + */ + private Map variables; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowEventWaitVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowEventWaitVo.java new file mode 100644 index 000000000..97de74cec --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowEventWaitVo.java @@ -0,0 +1,158 @@ +package org.dromara.workflow.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 定时任务(事件等待记录)视图对象 flow_event_wait。 + *

字段来自引擎 flow_event_wait;flowName / businessId 由服务层按定义与实例主键装配, + * eventTypeName / statusName 为展示文案。

+ * + * @author may + * @since 2026/10/21 + */ +@Data +public class FlowEventWaitVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + private Long id; + + /** + * 流程定义id + */ + private Long definitionId; + + /** + * 流程名称 + */ + private String flowName; + + /** + * 流程实例id(定时启动事件为空) + */ + private Long instanceId; + + /** + * 业务流程主键 + */ + private String businessId; + + /** + * 事件节点编码 + */ + private String nodeCode; + + /** + * 事件类型(TIMER_START / TIMER_CATCH / TIMER_BOUNDARY / …) + */ + private String eventType; + + /** + * 事件类型文案 + */ + private String eventTypeName; + + /** + * 事件名称(消息名 / 错误码) + */ + private String eventName; + + /** + * 边界事件关联的待办任务id + */ + private Long attachedTaskId; + + /** + * 定时触发策略(AFTER / FIXED / POINT_IN_TIME) + */ + private String triggerType; + + /** + * 触发间隔表达式 + */ + private String triggerInterval; + + /** + * 下次触发时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date nextTriggerAt; + + /** + * 时区 + */ + private String timezone; + + /** + * 已触发次数 + */ + private Integer repeatCount; + + /** + * 最大触发次数 + */ + private Integer maxRepeatCount; + + /** + * 等待状态(WAITING / RETRY / RUNNING / CANCELLED / DEAD / SUCCESS) + */ + private String status; + + /** + * 等待状态文案 + */ + private String statusName; + + /** + * 已重试次数 + */ + private Integer retryCount; + + /** + * 最大重试次数 + */ + private Integer maxRetryCount; + + /** + * 下次重试时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date retryAt; + + /** + * 载荷 + */ + private String payload; + + /** + * 最近一次错误 + */ + private String lastError; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * 定时引擎扫描开关是否开启(warm-flow.timer-enabled)。 + * 关闭时等待记录仍会生成但不会被自动触发,页面据此给出提醒。 + */ + private Boolean timerEnabled; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwEventWaitService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwEventWaitService.java new file mode 100644 index 000000000..31b709230 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwEventWaitService.java @@ -0,0 +1,53 @@ +package org.dromara.workflow.service; + +import org.dromara.common.core.domain.PageResult; +import org.dromara.workflow.domain.bo.FlowEventWaitBo; +import org.dromara.workflow.domain.bo.FlowTimerTriggerBo; +import org.dromara.workflow.domain.vo.FlowEventWaitVo; + +import java.util.Map; + +/** + * 定时任务(事件等待记录)服务接口。 + *

数据来自引擎 flow_event_wait,页面提供查询、状态统计与定时启动任务的手动补发。 + * 手动补发只创建一次实例,不读取也不推进等待记录,定时任务的下次触发时间不受影响。

+ * + * @author may + * @since 2026/10/21 + */ +public interface IFlwEventWaitService { + + /** + * 分页查询事件等待记录,默认只查定时类事件、按下次触发时间升序。 + * + * @param bo 查询条件 + * @return 等待记录分页数据 + */ + PageResult queryPageList(FlowEventWaitBo bo); + + /** + * 按同一组筛选条件统计各状态等待记录数量。 + * + * @param bo 查询条件(其中状态条件被逐一分组覆盖) + * @return 各状态计数,键为 waiting / retry / running / dead / cancelled + */ + Map summary(FlowEventWaitBo bo); + + /** + * 查询单条等待记录详情。 + * + * @param id 事件等待记录主键 + * @return 等待记录详情,不存在时返回 {@code null} + */ + FlowEventWaitVo queryById(Long id); + + /** + * 手动补发一次定时启动任务(运维应急)。 + *

实例发起人记为当前登录用户;等待记录保持不变,「每天 9 点的任务今天提前补发一次」 + * 不会吞掉当天 9 点的正常触发。

+ * + * @param bo 补发参数(等待记录主键、可选业务主键与变量) + * @return 新建流程实例主键 + */ + Long manualTrigger(FlowTimerTriggerBo bo); +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwEventWaitServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwEventWaitServiceImpl.java new file mode 100644 index 000000000..d686c77a4 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwEventWaitServiceImpl.java @@ -0,0 +1,225 @@ +package org.dromara.workflow.service.impl; + +import com.baomidou.lock.annotation.Lock4j; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.PageResult; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.warm.flow.core.FlowEngine; +import org.dromara.warm.flow.core.constant.EventCons; +import org.dromara.warm.flow.core.dto.EventWaitQuery; +import org.dromara.warm.flow.core.entity.EventWait; +import org.dromara.warm.flow.core.entity.Instance; +import org.dromara.warm.flow.core.enums.EventType; +import org.dromara.warm.flow.core.exception.FlowException; +import org.dromara.warm.flow.core.utils.page.Page; +import org.dromara.warm.flow.core.workflow.context.WorkflowContext; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.domain.bo.FlowEventWaitBo; +import org.dromara.workflow.domain.bo.FlowTimerTriggerBo; +import org.dromara.workflow.domain.vo.FlowEventWaitVo; +import org.dromara.workflow.service.IFlwEventWaitService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 定时任务(事件等待记录)服务实现。 + *

条件解析与合法性校验由引擎 {@link EventWaitQuery} 与门面层完成,本类只负责参数透传、 + * 引擎实体到视图对象的装配(流程名 / 业务主键)与状态文案。手动补发复用引擎 + * {@code InsService#startByTimer},不读写等待记录,定时任务下次触发时间不受影响。

+ * + * @author may + * @since 2026/10/21 + */ +@ConditionalOnEnable +@RequiredArgsConstructor +@Slf4j +@Service +public class FlwEventWaitServiceImpl implements IFlwEventWaitService { + + /** + * 事件类型持久化值 → 页面文案。 + */ + private static final Map EVENT_TYPE_NAMES = new LinkedHashMap<>(); + + /** + * 等待状态持久化值 → 页面文案。 + */ + private static final Map STATUS_NAMES = new LinkedHashMap<>(); + + static { + EVENT_TYPE_NAMES.put(EventType.MESSAGE_START.getValue(), "消息启动"); + EVENT_TYPE_NAMES.put(EventType.TIMER_START.getValue(), "定时启动"); + EVENT_TYPE_NAMES.put(EventType.TIMER_CATCH.getValue(), "定时捕获"); + EVENT_TYPE_NAMES.put(EventType.MESSAGE_CATCH.getValue(), "消息捕获"); + EVENT_TYPE_NAMES.put(EventType.MESSAGE_THROW.getValue(), "消息抛出"); + EVENT_TYPE_NAMES.put(EventType.TIMER_BOUNDARY.getValue(), "定时边界"); + EVENT_TYPE_NAMES.put(EventType.MESSAGE_BOUNDARY.getValue(), "消息边界"); + EVENT_TYPE_NAMES.put(EventType.TERMINATE_END.getValue(), "终止结束"); + EVENT_TYPE_NAMES.put(EventType.MESSAGE_END.getValue(), "消息结束"); + EVENT_TYPE_NAMES.put(EventType.ERROR_CATCH.getValue(), "错误捕获"); + EVENT_TYPE_NAMES.put(EventType.ERROR_BOUNDARY.getValue(), "错误边界"); + + STATUS_NAMES.put(EventCons.STATUS_WAITING, "待触发"); + STATUS_NAMES.put(EventCons.STATUS_RETRY, "重试中"); + STATUS_NAMES.put(EventCons.STATUS_RUNNING, "执行中"); + STATUS_NAMES.put(EventCons.STATUS_CANCELLED, "已取消"); + STATUS_NAMES.put(EventCons.STATUS_DEAD, "已死"); + STATUS_NAMES.put(EventCons.STATUS_SUCCESS, "成功"); + } + + /** + * 分页查询事件等待记录。 + * + * @param bo 查询条件 + * @return 分页等待记录 + */ + @Override + public PageResult queryPageList(FlowEventWaitBo bo) { + Page page = FlowEngine.workflow().pageEventWaits(bo.toQuery()); + List items = page.getList() == null ? List.of() : page.getList(); + boolean timerEnabled = FlowEngine.getFlowConfig().isTimerEnabled(); + List rows = new ArrayList<>(items.size()); + for (EventWait wait : items) { + FlowEventWaitVo vo = toVo(wait); + vo.setTimerEnabled(timerEnabled); + rows.add(vo); + } + return PageResult.build(rows, page.getTotal()); + } + + /** + * 按同一组筛选条件统计各状态等待记录数量。 + * + * @param bo 查询条件 + * @return 各状态计数 + */ + @Override + public Map summary(FlowEventWaitBo bo) { + Map summary = new LinkedHashMap<>(); + summary.put("waiting", countByStatus(bo, EventCons.STATUS_WAITING)); + summary.put("retry", countByStatus(bo, EventCons.STATUS_RETRY)); + summary.put("running", countByStatus(bo, EventCons.STATUS_RUNNING)); + summary.put("dead", countByStatus(bo, EventCons.STATUS_DEAD)); + summary.put("cancelled", countByStatus(bo, EventCons.STATUS_CANCELLED)); + return summary; + } + + /** + * 查询单条等待记录详情。 + * + * @param id 事件等待记录主键 + * @return 等待记录详情,不存在时返回 null + */ + @Override + public FlowEventWaitVo queryById(Long id) { + EventWait wait = FlowEngine.eventWaitService().getById(id); + if (wait == null) { + return null; + } + FlowEventWaitVo vo = toVo(wait); + vo.setTimerEnabled(FlowEngine.getFlowConfig().isTimerEnabled()); + return vo; + } + + /** + * 手动补发一次定时启动任务(运维应急)。 + *

+ * 只做「按定义 + 事件节点直接发起」,不读写等待记录:定时任务的下次触发时间保持原样, + * 提前补发不会吞掉当天的正常触发。业务主键优先取调用方传入值,留空时用雪花 long 型 id + * (与宿主其它业务表主键同型,便于下游按 long 解析),天然不会与定时触发的 + * {@code T{waitId}_{计划时间}} 撞号。加分布式锁防止同机/多机双击连发。 + * + * @param bo 补发参数 + * @return 新建实例主键 + */ + @Override + @Lock4j(keys = {"#bo.waitId"}) + public Long manualTrigger(FlowTimerTriggerBo bo) { + if (bo == null || bo.getWaitId() == null) { + throw new ServiceException("事件等待记录主键不能为空"); + } + EventWait wait = FlowEngine.eventWaitService().getById(bo.getWaitId()); + if (wait == null) { + throw new ServiceException("事件等待记录不存在: " + bo.getWaitId()); + } + if (!EventType.TIMER_START.getValue().equals(wait.getEventType())) { + throw new ServiceException("只有定时启动任务支持手动触发,当前事件类型为 " + wait.getEventType()); + } + String finalBusinessId = StringUtils.isNotBlank(bo.getBusinessId()) + ? bo.getBusinessId().trim() + : String.valueOf(IdWorker.getId()); + WorkflowContext context = new WorkflowContext(); + context.setHandler(LoginHelper.getUserIdStr()); + try { + Instance instance = FlowEngine.insService().startByTimer(finalBusinessId, wait.getDefinitionId() + , wait.getNodeCode(), bo.getVariables(), context); + return instance.getId(); + } catch (FlowException e) { + log.warn("定时启动任务手动补发失败 waitId={} 原因={}", bo.getWaitId(), e.getMessage()); + throw new ServiceException(e.getMessage()); + } + } + + /** + * 统计某一状态下的等待记录数量。 + * + * @param bo 查询参数(其中状态条件被覆盖) + * @param status 等待状态 + * @return 记录数量 + */ + private long countByStatus(FlowEventWaitBo bo, String status) { + EventWaitQuery query = bo.toQuery(); + query.setStatuses(List.of(status)); + return FlowEngine.workflow().countEventWaits(query); + } + + /** + * 引擎等待记录 → 视图对象。 + * + * @param wait 引擎等待记录 + * @return 视图对象 + */ + private FlowEventWaitVo toVo(EventWait wait) { + FlowEventWaitVo vo = new FlowEventWaitVo(); + vo.setId(wait.getId()); + vo.setDefinitionId(wait.getDefinitionId()); + vo.setInstanceId(wait.getInstanceId()); + vo.setNodeCode(wait.getNodeCode()); + vo.setEventType(wait.getEventType()); + vo.setEventName(wait.getEventName()); + vo.setAttachedTaskId(wait.getAttachedTaskId()); + vo.setTriggerType(wait.getTriggerType()); + vo.setTriggerInterval(wait.getTriggerInterval()); + vo.setNextTriggerAt(wait.getNextTriggerAt()); + vo.setTimezone(wait.getTimezone()); + vo.setRepeatCount(wait.getRepeatCount()); + vo.setMaxRepeatCount(wait.getMaxRepeatCount()); + vo.setStatus(wait.getStatus()); + vo.setRetryCount(wait.getRetryCount()); + vo.setMaxRetryCount(wait.getMaxRetryCount()); + vo.setRetryAt(wait.getRetryAt()); + vo.setPayload(wait.getPayload()); + vo.setLastError(wait.getLastError()); + vo.setCreateTime(wait.getCreateTime()); + vo.setUpdateTime(wait.getUpdateTime()); + vo.setEventTypeName(EVENT_TYPE_NAMES.getOrDefault(wait.getEventType(), wait.getEventType())); + vo.setStatusName(STATUS_NAMES.getOrDefault(wait.getStatus(), wait.getStatus())); + if (wait.getDefinitionId() != null) { + var definition = FlowEngine.defService().getById(wait.getDefinitionId()); + vo.setFlowName(definition == null ? null : definition.getFlowName()); + } + if (wait.getInstanceId() != null) { + var instance = FlowEngine.insService().getById(wait.getInstanceId()); + vo.setBusinessId(instance == null ? null : instance.getBusinessId()); + } + return vo; + } +} diff --git a/script/sql/oracle/oracle_ry_workflow.sql b/script/sql/oracle/oracle_ry_workflow.sql index bf953712e..c80227ae7 100644 --- a/script/sql/oracle/oracle_ry_workflow.sql +++ b/script/sql/oracle/oracle_ry_workflow.sql @@ -601,6 +601,11 @@ INSERT INTO sys_menu VALUES (1761400000000011804, '流程spel表达式定义修 INSERT INTO sys_menu VALUES (1761400000000011805, '流程spel表达式定义删除', 1761400000000011801, 4, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:remove', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011806, '流程spel表达式定义导出', 1761400000000011801, 5, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:export', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); +-- 定时任务(事件等待记录)菜单与按钮 +INSERT INTO sys_menu VALUES (1761400000000011901, '定时任务', 1761400000000011630, 3, 'timer', 'workflow/timer/index', '', 'N', 'N', 'C', '0', '0', 'workflow:eventWait:list', 'time', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, '定时任务与事件等待监控菜单'); +INSERT INTO sys_menu VALUES (1761400000000011902, '定时任务查询', 1761400000000011901, 1, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:query', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES (1761400000000011903, '定时任务手动补发', 1761400000000011901, 2, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:trigger', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); + INSERT INTO sys_menu VALUES (1761400000000011638, '请假申请', 1761400000000000005, 1, 'leave', 'workflow/leave/index', '', 'N', 'Y', 'C', '0', '0', 'workflow:leave:list', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, '请假申请菜单'); INSERT INTO sys_menu VALUES (1761400000000011639, '请假申请查询', 1761400000000011638, 1, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:leave:query', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011640, '请假申请新增', 1761400000000011638, 2, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:leave:add', '#', '', '', 1761000000000000103, 1761100000000000001, SYSDATE, NULL, NULL, ''); diff --git a/script/sql/postgres/postgres_ry_workflow.sql b/script/sql/postgres/postgres_ry_workflow.sql index 45bc55832..610dba2bf 100644 --- a/script/sql/postgres/postgres_ry_workflow.sql +++ b/script/sql/postgres/postgres_ry_workflow.sql @@ -569,6 +569,11 @@ INSERT INTO sys_menu VALUES (1761400000000011804, '流程spel表达式定义修 INSERT INTO sys_menu VALUES (1761400000000011805, '流程spel表达式定义删除', 1761400000000011801, 4, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:remove', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011806, '流程spel表达式定义导出', 1761400000000011801, 5, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:export', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); +-- 定时任务(事件等待记录)菜单与按钮 +INSERT INTO sys_menu VALUES (1761400000000011901, '定时任务', 1761400000000011630, 3, 'timer', 'workflow/timer/index', '', 'N', 'N', 'C', '0', '0', 'workflow:eventWait:list', 'time', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, '定时任务与事件等待监控菜单'); +INSERT INTO sys_menu VALUES (1761400000000011902, '定时任务查询', 1761400000000011901, 1, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:query', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES (1761400000000011903, '定时任务手动补发', 1761400000000011901, 2, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:trigger', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); + INSERT INTO sys_menu VALUES (1761400000000011638, '请假申请', 1761400000000000005, 1, 'leave', 'workflow/leave/index', '', 'N', 'Y', 'C', '0', '0', 'workflow:leave:list', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, '请假申请菜单'); INSERT INTO sys_menu VALUES (1761400000000011639, '请假申请查询', 1761400000000011638, 1, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:leave:query', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011640, '请假申请新增', 1761400000000011638, 2, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:leave:add', '#', '', '', 1761000000000000103, 1761100000000000001, now(), NULL, NULL, ''); diff --git a/script/sql/ry_workflow.sql b/script/sql/ry_workflow.sql index b12244bb8..8ecb940e7 100644 --- a/script/sql/ry_workflow.sql +++ b/script/sql/ry_workflow.sql @@ -349,6 +349,11 @@ INSERT INTO sys_menu VALUES (1761400000000011803, '流程达式定义新增', 17 INSERT INTO sys_menu VALUES (1761400000000011804, '流程达式定义修改', 1761400000000011801, 3, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:edit', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011805, '流程达式定义删除', 1761400000000011801, 4, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:remove', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, ''); INSERT INTO sys_menu VALUES (1761400000000011806, '流程达式定义导出', 1761400000000011801, 5, '#', '', NULL, 'N', 'Y', 'F', '0', '0', 'workflow:spel:export', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, ''); + +-- 定时任务(事件等待记录)菜单与按钮 +insert into sys_menu values (1761400000000011901, '定时任务', 1761400000000011630, 3, 'timer', 'workflow/timer/index', '', 'N', 'N', 'C', '0', '0', 'workflow:eventWait:list', 'time', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '定时任务与事件等待监控菜单'); +insert into sys_menu values (1761400000000011902, '定时任务查询', 1761400000000011901, 1, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:query', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, ''); +insert into sys_menu values (1761400000000011903, '定时任务手动补发', 1761400000000011901, 2, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:trigger', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, ''); -- 请假测试相关按钮 insert into sys_menu VALUES (1761400000000011638, '请假申请', 1761400000000000005, 1, 'leave', 'workflow/leave/index', '', 'N', 'Y', 'C', '0', '0', 'workflow:leave:list', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '请假申请菜单'); insert into sys_menu VALUES (1761400000000011639, '请假申请查询', 1761400000000011638, 1, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:leave:query', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, ''); diff --git a/script/sql/sqlserver/sqlserver_ry_workflow.sql b/script/sql/sqlserver/sqlserver_ry_workflow.sql index 0370948c9..711556d45 100644 --- a/script/sql/sqlserver/sqlserver_ry_workflow.sql +++ b/script/sql/sqlserver/sqlserver_ry_workflow.sql @@ -1913,6 +1913,14 @@ GO INSERT sys_menu VALUES (1761400000000011806, N'流程spel表达式定义导出', 1761400000000011801, 5, N'#', N'', NULL, N'N', N'Y', N'F', N'0', N'0', N'workflow:spel:export', N'#', N'', N'', 1761000000000000103, 1761100000000000001, GETDATE(), NULL, NULL, N''); GO +-- 定时任务(事件等待记录)菜单与按钮 +INSERT sys_menu VALUES (1761400000000011901, N'定时任务', 1761400000000011630, 3, N'timer', N'workflow/timer/index', N'', N'N', N'N', N'C', N'0', N'0', N'workflow:eventWait:list', N'time', N'', N'', 1761000000000000103, 1761100000000000001, GETDATE(), NULL, NULL, N'定时任务与事件等待监控菜单'); +GO +INSERT sys_menu VALUES (1761400000000011902, N'定时任务查询', 1761400000000011901, 1, N'#', N'', NULL, N'N', N'Y', N'F', N'0', N'0', N'workflow:eventWait:query', N'#', N'', N'', 1761000000000000103, 1761100000000000001, GETDATE(), NULL, NULL, N''); +GO +INSERT sys_menu VALUES (1761400000000011903, N'定时任务手动补发', 1761400000000011901, 2, N'#', N'', NULL, N'N', N'Y', N'F', N'0', N'0', N'workflow:eventWait:trigger', N'#', N'', N'', 1761000000000000103, 1761100000000000001, GETDATE(), NULL, NULL, N''); +GO + -- 请假测试相关按钮 INSERT sys_menu VALUES (1761400000000011638, N'请假申请', 1761400000000000005, 1, N'leave', N'workflow/leave/index', N'', N'N', N'Y', N'C', N'0', N'0', N'workflow:leave:list', N'#', N'', N'', 1761000000000000103, 1761100000000000001, GETDATE(), NULL, NULL, N'请假申请菜单'); GO diff --git a/script/sql/update/update_6.0.0-6.1.0.sql b/script/sql/update/update_6.0.0-6.1.0.sql index dd4c87a41..cd5245472 100644 --- a/script/sql/update/update_6.0.0-6.1.0.sql +++ b/script/sql/update/update_6.0.0-6.1.0.sql @@ -31,3 +31,51 @@ INSERT INTO sys_dict_data VALUES (1761600000000000067, 12, '减签', 'remove_sig -- 3. 实例状态字典补 restart(引擎 FlowStatus 有,字典缺) INSERT INTO sys_dict_data VALUES (1761600000000000063, 8, '重启', 'restart', 'wf_business_status', '', 'info', 'N', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '重启'); + +-- 4. 事件等待表:定时 / 消息等需要「等待」的事件统一落在此表(与全量脚本一致)。 +-- 6.0.0 存量库没有这张表,不建表则定时器与定时任务页面全部不可用 +-- 注意:建表后还需在 application.yml 打开 warm-flow.timer-enabled: true,引擎才会扫描本表 +CREATE TABLE IF NOT EXISTS `flow_event_wait` +( + `id` bigint NOT NULL COMMENT '主键id', + `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', + `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', + `definition_id` bigint DEFAULT NULL COMMENT '流程定义id(定时启动事件必须有)', + `instance_id` bigint DEFAULT NULL COMMENT '流程实例id(实例等待事件必须有)', + `node_code` varchar(100) DEFAULT NULL COMMENT '事件节点编码(定位沿用 definition_id + node_code)', + `event_type` varchar(40) NOT NULL COMMENT '事件类型(MESSAGE_START TIMER_START TIMER_CATCH MESSAGE_CATCH MESSAGE_THROW TIMER_BOUNDARY MESSAGE_BOUNDARY ERROR_CATCH ERROR_BOUNDARY TERMINATE_END MESSAGE_END)', + `event_name` varchar(100) DEFAULT NULL COMMENT '事件名称(消息事件使用)', + `attached_task_id` bigint DEFAULT NULL COMMENT '边界事件关联的待办任务id', + `trigger_type` varchar(40) DEFAULT NULL COMMENT '触发策略类型(AFTER FIXED POINT_IN_TIME)', + `trigger_interval` varchar(100) DEFAULT NULL COMMENT '触发策略原始配置', + `next_trigger_at` datetime DEFAULT NULL COMMENT '下次触发时间', + `timezone` varchar(40) DEFAULT NULL COMMENT '定时触发使用的时区', + `repeat_count` int DEFAULT NULL COMMENT '已触发次数', + `max_repeat_count` int DEFAULT NULL COMMENT '最大触发次数', + `status` varchar(20) NOT NULL COMMENT '等待状态(WAITING RUNNING CANCELLED RETRY DEAD)', + `retry_count` int DEFAULT NULL COMMENT '已重试次数', + `max_retry_count` int DEFAULT NULL COMMENT '最大重试次数', + `retry_at` datetime DEFAULT NULL COMMENT '下次重试时间', + `lock_owner` varchar(64) DEFAULT NULL COMMENT '抢占持有者标识', + `lock_until` datetime DEFAULT NULL COMMENT '抢占租约到期时间', + `version` int DEFAULT NULL COMMENT '乐观锁版本号', + `payload` text COMMENT '消息事件接收的业务载荷', + `last_error` varchar(500) DEFAULT NULL COMMENT '最近一次失败原因摘要', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `event_wait_status_next` (`status`, `next_trigger_at`, `id`) USING BTREE, + KEY `event_wait_status_retry` (`status`, `retry_at`, `id`) USING BTREE, + KEY `event_wait_tenant_status_next` (`tenant_id`, `status`, `next_trigger_at`, `id`) USING BTREE, + KEY `event_wait_tenant_status_retry` (`tenant_id`, `status`, `retry_at`, `id`) USING BTREE, + KEY `event_wait_instance_task` (`instance_id`, `attached_task_id`) USING BTREE, + KEY `event_wait_name_instance_status` (`event_name`, `instance_id`, `status`) USING BTREE, + KEY `event_wait_def_node_status` (`definition_id`, `node_code`, `status`) USING BTREE +) ENGINE = InnoDB COMMENT ='事件等待表'; + +-- 5. 「流程监控」下补「定时任务」菜单 + 查询/手动补发按钮(与 script/sql/ry_workflow.sql 全量脚本一致) +-- 先删后插保证可重复执行;重新执行后记得给角色重新勾选按钮权限 +DELETE FROM sys_menu WHERE menu_id IN (1761400000000011901, 1761400000000011902, 1761400000000011903); +insert into sys_menu values (1761400000000011901, '定时任务', 1761400000000011630, 3, 'timer', 'workflow/timer/index', '', 'N', 'N', 'C', '0', '0', 'workflow:eventWait:list', 'time', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '定时任务与事件等待监控菜单'); +insert into sys_menu values (1761400000000011902, '定时任务查询', 1761400000000011901, 1, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:query', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, ''); +insert into sys_menu values (1761400000000011903, '定时任务手动补发', 1761400000000011901, 2, '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:eventWait:trigger', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');