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, '');