引擎通过事件通知下游业务系统「发生了什么」。所有流程事件为异步事件,业务方通过实现 IHandler<T> 接口订阅。引擎不提供任何内置消息发送(站内信/IM/邮件),消息推送由订阅方自行实现。
package com.codingapi.flow.event;
import com.codingapi.springboot.framework.event.IAsyncEvent;
/** 流程触发的异步事件 */
public interface IFlowEvent extends IAsyncEvent {
boolean isMock();
}IAsyncEvent 来自外部框架 springboot-starter:
public interface IEvent extends Serializable {} // 默认同步事件标记
public interface ISyncEvent extends IEvent {} // 同步事件标记
public interface IAsyncEvent extends IEvent {} // 异步事件标记 ← IFlowEvent 继承它同步/异步由 DomainEventContext.push 的 instanceof 判定:IAsyncEvent → 异步(线程池执行);ISyncEvent/裸 IEvent → 同步(发布线程内联执行)。
记录类事件均携带 FlowRecord(或当前记录)与 boolean mock 标记(Mock 模式为 true)。
| 事件类 | 触发时机 | 构造参数 | 访问器 |
|---|---|---|---|
FlowRecordStartEvent |
流程发起(每条新建记录) | FlowRecord, boolean mock |
getFlowRecord() / isMock() |
FlowRecordTodoEvent |
生成待办记录 | FlowRecord, boolean mock |
getFlowRecord() / isMock() |
FlowRecordDoneEvent |
记录办结 | FlowRecord, boolean mock |
getFlowRecord() / isMock() |
FlowRecordFinishEvent |
流程正常完成(结束节点) | FlowRecord, boolean mock |
getFlowRecord() / isMock() |
FlowRecordRevokeEvent |
撤销(被撤销的后置记录) | FlowRecord currentRecord, boolean mock |
getCurrentRecord() / isMock() |
FlowRecordUrgeEvent |
催办(每条待办) | FlowRecord, IFlowOperator urgeOperator, boolean mock |
getFlowRecord() / getUrgeOperator() / isMock() |
FlowRecordDeleteEvent |
删除未流转实例(开始节点) | FlowRecord, boolean mock |
getFlowRecord() / isMock() |
FlowSubProcessResetEvent |
子流程数据重置(issue #219) | SubProcessRecord oldRecord, SubProcessRecord newRecord, long resetRecordId, IFlowOperator resetOperator, boolean mock |
getOldRecord() / getNewRecord() / getResetRecordId() / getResetOperator() / isMock() |
子流程重置事件订阅须知:
- 事件提醒业务方子流程数据已被重置:
oldRecord为被取代的旧聚合组快照(含全部旧实例流程id),newRecord为重置后的新聚合组——继承实例沿用原流程id,重建实例为新流程id, 重建实例的getSourceProcessId()记录其替换的旧实例流程id,据此完成旧 → 新流程id映射; - 事件经异步线程池分发、到达顺序不保证(重置同时伴随被作废记录的
RevokeEvent与重建实例的StartEvent/TodoEvent),订阅方应按「同一parentRecordId+ 节点维度下 最大聚合组 id 为活跃组」做幂等覆盖式收敛,禁止增量累加; - 事件的
oldRecordid 与订阅方当前活跃组 id 不一致时,说明存在丢失的重置事件, 应通过节点记录查询(/api/cmd/record/processNodes)全量对账; - 旧实例记录不会被删除(仅聚合组标记已取代),订阅方收到事件后应自行将旧流程id的数据标记失效。
范例(FlowRecordUrgeEvent.java,唯一带三个字段的事件):
@Getter
@AllArgsConstructor
public class FlowRecordUrgeEvent implements IFlowEvent {
private final FlowRecord flowRecord;
private final IFlowOperator urgeOperator;
private final boolean mock;
}FlowRecord(flow-engine-framework/.../record/FlowRecord.java)提供:
- 基本信息:
getId()、getProcessId()、getWorkTitle()、getWorkCode()、getNodeId()、getNodeType()、getNodeName()、getTitle()、getFormData()(Map<String,Object>) - 操作人:
getCurrentOperatorId/Name()、getSubmitOperatorId/Name()、getCreateOperatorId/Name()、getForwardOperatorId/Name() - 状态:
isTodo()、isDone()、isFinish()、isAutoDone()、isShow()、isNotEndNode() - 抄送标记:
isNotify() - 动作:
getActionId()、getActionType()、getActionName()、getAdvice() - 并行:
getParallelId()、getParallelBranchNodeId()、getParallelBranchTotal() - 时间:
getCreateTime()、getUpdateTime()、getFinishTime()、getTimeoutTime()
package com.codingapi.springboot.framework.event;
/** 事件推送助手 */
public class EventPusher {
/** 推送事件。默认自动检测循环事件,出现循环事件时抛出循环调用异常。 */
public static void push(IEvent event) {
push(event, false);
}
/** hasLoopEvent=true 时跳过循环事件检测 */
public static void push(IEvent event, boolean hasLoopEvent) {
DomainEventContext.getInstance().push(event, hasLoopEvent);
}
}调用方式(引擎内部统一使用单参形式):
EventPusher.push(new FlowRecordStartEvent(flowRecord, session.isMock()));| 位置 | 推送内容 |
|---|---|
FlowCreateService.create() |
每条新建记录:StartEvent + TodoEvent |
PassAction.run() |
当前记录 DoneEvent;顺序多人激活 TodoEvent;或签/并签自动办结他人 DoneEvent;抄送记录 DoneEvent;委托回退 TodoEvent;下游节点记录 TodoEvent(抄送为 DoneEvent) |
RejectAction.run() |
当前记录 DoneEvent + 回退新记录 TodoEvent |
ReturnAction.run() |
当前记录 DoneEvent + 退回节点新记录 TodoEvent |
AddAuditAction.run() |
加签记录 TodoEvent |
TransferAction.run() |
当前记录 DoneEvent + 转交人新记录 TodoEvent |
DelegateAction.run() |
当前记录 DoneEvent + 委托人新记录 TodoEvent |
EndNode.fillNewRecord() |
FinishEvent(流程正常完成) |
FlowSubProcessResultService.save() |
子流程结果记录 TodoEvent(非抄送)或 DoneEvent |
FlowUrgeService.urge() |
每条待办 UrgeEvent |
FlowRevokeService.revoke() |
当前记录恢复待办 TodoEvent + 被撤销后置记录 RevokeEvent |
FlowDeleteService.delete() |
DeleteEvent |
FlowSubProcessResetService.reset() |
被作废记录链逐条 RevokeEvent + 子流程重置 SubProcessResetEvent;重建实例经 FlowCreateService 推送各自的 StartEvent + TodoEvent |
事件在
repositoryHolder.saveRecords(...)落库之后推送,保证订阅方看到的记录已持久化。
PassAction 会拦截结束节点虚拟记录的 TodoEvent(结束节点不持久化、operatorId=-1,并非真实业务待办),避免下游收到「已完成流程的待办」:
flowEvents.stream()
.filter(event -> !isEndNodeTodoEvent(event))
.forEach(EventPusher::push);
private boolean isEndNodeTodoEvent(IFlowEvent event) {
return event instanceof FlowRecordTodoEvent todoEvent
&& !todoEvent.getFlowRecord().isNotEndNode();
}业务方实现 IHandler<T extends IEvent> 接口并注册为 Spring Bean,即可订阅对应事件。
package com.codingapi.springboot.framework.event;
public interface IHandler<T extends IEvent> {
default int order() { return 0; } // 同事件多订阅排序,升序执行
void handler(T event);
default void error(Exception e) { throw e instanceof RuntimeException r ? r : new RuntimeException(e); } // 默认重新抛出
}flow-engine-example/src/main/java/com/codingapi/example/handler/MyFlowRecordUrgeEventHandler.java:
package com.codingapi.example.handler;
import com.codingapi.flow.event.FlowRecordUrgeEvent;
import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyFlowRecordUrgeEventHandler implements IHandler<FlowRecordUrgeEvent> {
@Override
public void handler(FlowRecordUrgeEvent event) {
log.info("催办 event:{}", event);
}
}要点:
- 泛型决定订阅的事件类型:
IHandler<FlowRecordUrgeEvent>只订阅催办事件;泛型为父类型/接口时可订阅其全部子类(isAssignableFrom匹配)。 - 注册方式:
@Component即可,Spring 启动时自动收集注入SpringDefaultEventHandler。 - 排序:同事件多个订阅按
order()升序执行。 - 异常处理:handler 抛异常后调用其
error(e);默认error重新抛出,会阻止后续 handler 执行。单个订阅失败不阻止其他订阅(除非 error 回调抛出)。
业务代码 → EventPusher.push(event)
→ DomainEventContext.push (IAsyncEvent → 异步)
→ EventTraceContext 记录 trace + 循环事件检测(命中抛 EventLoopException)
→ Spring ApplicationContext.publishEvent(new DomainEvent(event, sync, traceId))
→ SpringDefaultEventHandler.@EventListener dispatch
→ 异步:executorService.execute(() -> ApplicationHandlerUtils.handler(event))
同步:内联执行
→ 按 order() 排序,按泛型匹配执行所有 IHandler
- 同一 traceId 内,同类事件再次 push 会抛
EventLoopException(防止事件→动作→事件的无限循环)。 - 跳过检测:
EventPusher.push(event, true)(引擎内部未使用,保留给特殊情况)。
- 异步事件由框架自建固定线程池执行(
Executors.newFixedThreadPool),线程池大小来自FrameworkProperties.handlerThreadPoolSize,默认 20。 - 配置项:
codingapi.framework.handler-thread-pool-size(以实际FrameworkProperties前缀为准)。 - 注意:异步 ≠ Spring
@Async,是框架自建线程池,事件回调内的事务边界需自行处理。
配置 codingapi.framework.event.transaction.enable=true 时,框架改为装配 SpringTransactionEventHandler:使用 @TransactionalEventListener(phase = AFTER_COMMIT, fallbackExecution = true),在事务提交后才分发事件(无事务时 fallback 立即执行);否则默认装配 SpringDefaultEventHandler(发布即分发)。
DomainEventContext 在 ApplicationContext 未初始化时(如纯单元测试)静默丢弃事件。
引擎不提供任何消息发送/桥接抽象。与「通知」相关的能力:
- 抄送节点(
NotifyNode,NodeType.NOTIFY):生成抄送记录(flowRecord.notifyRecord(session)),引擎推送FlowRecordDoneEvent(抄送记录不产生待办事件)。 - 催办:
FlowUrgeService+FlowRecordUrgeEvent,通知发送由订阅方实现。 - 消息推送(站内信/IM/邮件等):自行实现
IHandler<FlowRecordXxxEvent>,在handler中对接自己的消息服务。
- 事件携带
mock标记:有 session 的场景用session.isMock()(等价于repositoryHolder instanceof MockRepositoryHolder);无 session 的服务(FlowDeleteService/FlowUrgeService/FlowRevokeService/FlowSubProcessResetService)直接用repositoryHolder instanceof MockRepositoryHolder。 - 订阅方可通过
event.isMock()区分测试/演示流量与生产流量(例如不发送真实消息)。