diff --git a/CLAUDE.md b/CLAUDE.md index a682a817..e43dc794 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,12 +72,12 @@ The workflow engine is organized into 8 layers: - `IFlowNode` - Interface defining node lifecycle methods - `BaseFlowNode` - Abstract base for all nodes, manages actions and strategies - `BaseAuditNode` - Abstract base for audit nodes (ApprovalNode, HandleNode) - - 11 node types: StartNode, EndNode, ApprovalNode, HandleNode, NotifyNode, ConditionBranchNode, ParallelBranchNode, RouterBranchNode, InclusiveBranchNode, DelayNode, TriggerNode, SubProcessNode + - 12 node types: StartNode, EndNode, ApprovalNode, HandleNode, NotifyNode, BranchNodeBranchNode, ParallelBranchNode, RouterBranchNode, InclusiveBranchNode, SubProcessNode, DelayNode, TriggerNode 3. **Action Layer** (`com.codingapi.flow.action`, `com.codingapi.flow.action.actions`) - `IFlowAction` - Interface for node actions with `copy()` method - `BaseAction` - Abstract base with `triggerNode()` for recursive traversal - - 8 action types: DefaultAction, PassAction, RejectAction, SaveAction, ReturnAction, TransferAction, AddAuditAction, DelegateAction, CustomAction + - 9 action types: DefaultAction, PassAction, RejectAction, SaveAction, ReturnAction, TransferAction, AddAuditAction, DelegateAction, CustomAction 4. **Record Layer** (`com.codingapi.flow.record`) - `FlowRecord` - Execution record with states (TODO/DONE, RUNNING/FINISH/ERROR/DELETE) @@ -100,6 +100,13 @@ The workflow engine is organized into 8 layers: - `ScriptRuntimeContext` - Groovy script execution with thread-safe design and auto-cleanup - Script types: OperatorMatchScript, OperatorLoadScript, NodeTitleScript, ConditionScript, ErrorTriggerScript, RejectActionScript +### Supporting Architectures + +- **Repository Pattern** (`com.codingapi.flow.repository`) - Abstraction for data persistence, isolates framework from implementation +- **Gateway Pattern** (`com.codingapi.flow.gateway`) - Anti-corruption layer for external system integration (operators, users) +- **Backup System** (`com.codingapi.flow.backup`) - Workflow versioning and backup management +- **Error Handling** (`com.codingapi.flow.error`) - Centralized error throwing mechanism for flow control + ### Node Lifecycle (Critical for Understanding Flow) When a node is executed, methods are called in this order: @@ -139,6 +146,8 @@ When encountering `ParallelBranchNode`: - **Chain of Responsibility**: `triggerNode()` recursive traversal, `StrategyManager` strategy iteration - **Composite**: Nodes contain multiple strategies and actions - **Copy Pattern**: `INodeStrategy.copy()`, `IFlowAction.copy()`, `BaseFlowNode.setActions()`, `BaseFlowNode.setStrategies()` +- **Repository**: Abstracts data persistence, implementations in infra layer +- **Gateway**: Anti-corruption layer for external system integration ### Strategy-Driven Node Configuration (Critical Architecture) @@ -200,6 +209,8 @@ Use framework exceptions instead of generic exceptions (IllegalArgumentException - **Custom Strategies**: Extend `BaseStrategy`, implement `INodeStrategy` - **Custom Scripts**: Use `ScriptRuntimeContext` for Groovy execution - **Event Listeners**: Listen to `FlowRecordStartEvent`, `FlowRecordTodoEvent`, `FlowRecordDoneEvent`, `FlowRecordFinishEvent` +- **Repository Implementations**: Implement repository interfaces in infra layer for persistence +- **Gateway Implementations**: Implement gateway interfaces for system integration ## Documentation References diff --git a/Design.md b/Design.md index 9c32dcef..894ddb04 100644 --- a/Design.md +++ b/Design.md @@ -17,13 +17,16 @@ - `operatorCreateScript`: 创建者匹配脚本 - `isInterfere`: 是否开启干预 - `isRevoke`: 是否开启撤销 + - `createdOperator`: 创建者ID + - `createdTime`: 创建时间 + - `schema`: 流程版本标识 - **核心方法**: - `verify()`: 验证流程定义的合法性 - `nextNodes(IFlowNode)`: 获取指定节点的后续节点 - `getStartNode()`: 获取开始节点 - `getEndNode()`: 获取结束节点 - `toJson()`: 序列化为JSON - - `formJson()`: 从JSON反序列化 + - `formJson(String)`: 从JSON反序列化(静态方法) #### WorkflowBuilder - **位置**: `com.codingapi.flow.workflow.WorkflowBuilder` @@ -70,7 +73,7 @@ - `isDone()`: 通过StrategyManager判断多人审批完成状态 - `generateCurrentRecords()`: 通过StrategyManager加载操作者并生成记录 -#### 节点类型一览 (11种) +#### 节点类型一览 (12种) | 节点类型 | 类名 | NODE_TYPE | 说明 | |---------|------|-----------|------| @@ -167,7 +170,12 @@ #### FlowAdvice - **位置**: `com.codingapi.flow.session.FlowAdvice` - **职责**: 封装审批操作的相关参数 -- **核心属性**: `advice`(审批意见), `signKey`(签名), `action`(动作), `backNode`(退回节点), `transferOperators`(转办人员) +- **核心属性**: + - `advice`: 审批意见 + - `signKey`: 签名 + - `action`: 动作类型 + - `backNode`: 退回节点(类型为 `IFlowNode`,节点对象引用) + - `transferOperators`: 转办人员列表 --- diff --git a/README.md b/README.md index c3b7867d..bde9d0be 100644 --- a/README.md +++ b/README.md @@ -25,20 +25,37 @@ flow-engine │ ├── src/main/java │ │ └── com.codingapi.flow │ │ ├── action # 操作层 +│ │ │ ├── actions # 具体操作实现 +│ │ │ └── factory # 操作工厂 +│ │ ├── backup # 流程备份 │ │ ├── builder # 构建器 │ │ ├── context # 上下文 +│ │ ├── edge # 节点连线 +│ │ ├── error # 错误处理 │ │ ├── event # 事件 -│ │ ├── exception # 异常 -│ │ ├── form # 表单 -│ │ ├── gateway # 网关 +│ │ ├── exception # 异常体系 +│ │ ├── form # 表单系统 +│ │ │ └── permission # 字段权限 +│ │ ├── gateway # 网关接口防腐层 │ │ ├── node # 节点层 -│ │ ├── operator # 操作人 +│ │ │ ├── factory # 节点工厂 +│ │ │ ├── helper # 节点辅助类 +│ │ │ ├── manager # 节点管理器 +│ │ │ └── nodes # 具体节点实现 +│ │ ├── operator # 操作人接口 │ │ ├── pojo # 数据对象 -│ │ ├── record # 记录层 -│ │ ├── script # 脚本层 +│ │ │ ├── body # 请求体 +│ │ │ └── request # 请求对象 +│ │ ├── record # 流程记录 +│ │ ├── repository # 仓储接口 +│ │ ├── script # 脚本系统 +│ │ │ ├── action # 操作脚本 +│ │ │ ├── node # 节点脚本 +│ │ │ └── runtime # 脚本运行时 +│ │ ├── service # 服务层 +│ │ │ └── impl # 服务实现 │ │ ├── session # 会话层 │ │ ├── strategy # 策略层 -│ │ ├── user # 用户 │ │ ├── utils # 工具类 │ │ └── workflow # 流程层 │ └── src/test/java # 测试代码 diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java b/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java index 5a1db55e..7a18a371 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java @@ -60,4 +60,8 @@ public int getParallelBranchTriggerCount(String parallelId) { public void addParallelTriggerCount(String parallelId) { parallelBranchRepository.addTriggerCount(parallelId); } + + public void clearParallelTriggerCount(String parallelId) { + parallelBranchRepository.clearTriggerCount(parallelId); + } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java index df2dda1e..976f01a6 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java @@ -107,7 +107,8 @@ public boolean isDone(FlowSession session) { @Override public List generateCurrentRecords(FlowSession session) { - if (this.isWaitParallelRecord(session)) { + // 是否等待并行合并节点 + if (this.isWaitRecordMargeParallelNode(session)) { return List.of(); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java index d87aacc5..374217f9 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java @@ -171,7 +171,7 @@ private void verifyDefaultConfig(){ /** * 是否等待并行节点的汇聚 */ - public boolean isWaitParallelRecord(FlowSession session) { + public boolean isWaitRecordMargeParallelNode(FlowSession session) { FlowRecord currentRecord = session.getCurrentRecord(); if (currentRecord != null && this.getId().equals(currentRecord.getParallelBranchNodeId())) { RepositoryContext.getInstance().addParallelTriggerCount(currentRecord.getParallelId()); @@ -180,6 +180,7 @@ public boolean isWaitParallelRecord(FlowSession session) { if (parallelBranchCount == parallelBranchTotal) { // 清空并行节点,防止数据继续继承到后续节点 currentRecord.clearParallel(); + RepositoryContext.getInstance().clearParallelTriggerCount(currentRecord.getParallelId()); } return parallelBranchCount != parallelBranchTotal; } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java index fb1343e0..3302ab67 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java @@ -30,7 +30,7 @@ public String getType() { @Override public List generateCurrentRecords(FlowSession session) { - if(this.isWaitParallelRecord(session)){ + if(this.isWaitRecordMargeParallelNode(session)){ return List.of(); } // 构建结束记录 diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java index b49225d8..330bd01a 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java @@ -5,4 +5,7 @@ public interface ParallelBranchRepository { int getTriggerCount(String parallelId); void addTriggerCount(String parallelId); + + void clearTriggerCount(String parallelId); + } diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java index 7ae7a041..3954ae58 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java @@ -17,4 +17,9 @@ public int getTriggerCount(String parallelId) { public void addTriggerCount(String parallelId) { this.cache.put(parallelId, this.getTriggerCount(parallelId) + 1); } + + @Override + public void clearTriggerCount(String parallelId) { + this.cache.remove(parallelId); + } }