diff --git a/pom.xml b/pom.xml
index 093b166..1254a5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,6 +20,12 @@
lombok
1.18.34
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.11.4
+ test
+
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowAssert.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowAssert.java
deleted file mode 100644
index 878a199..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowAssert.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example;
-
-import com.lanyuanxiaoyao.flowable.model.FlowInstance;
-import com.lanyuanxiaoyao.flowable.model.FlowStatus;
-
-/**
- * 流程断言工具类
- * 用于验证流程执行状态是否符合预期
- */
-public class FlowAssert {
-
- public static void assertEquals(String message, Object expected, Object actual) {
- if (!expected.equals(actual)) {
- throw new AssertionError(String.format("%s: 期望值=%s, 实际值=%s", message, expected, actual));
- }
- }
-
- public static void assertStatus(String message, FlowStatus expectedStatus, FlowInstance instance) {
- assertEquals(message + " - 状态检查", expectedStatus, instance.getStatus());
- }
-
- public static void assertNode(String message, String expectedNode, FlowInstance instance) {
- assertEquals(message + " - 节点检查", expectedNode, instance.getCurrentNode());
- }
-
- public static void assertVariable(String message, Object expectedValue, String key, FlowInstance instance) {
- Object actualValue = instance.getContextVariables().get(key);
- assertEquals(message + " - ���量[" + key + "]检查", expectedValue, actualValue);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowExample.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowExample.java
deleted file mode 100644
index f8fc473..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowExample.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example;
-
-import com.lanyuanxiaoyao.flowable.example.node.LeaveRequestNode;
-import com.lanyuanxiaoyao.flowable.example.node.ManagerApprovalNode;
-import com.lanyuanxiaoyao.flowable.example.node.SimpleFlowNode;
-import com.lanyuanxiaoyao.flowable.example.node.LeaveSystemCheckNode;
-import com.lanyuanxiaoyao.flowable.model.Flow;
-import com.lanyuanxiaoyao.flowable.model.FlowInstance;
-import com.lanyuanxiaoyao.flowable.repository.FlowInstanceRepository;
-import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
-import com.lanyuanxiaoyao.flowable.repository.memory.MemoryFlowInstanceRepository;
-import com.lanyuanxiaoyao.flowable.repository.memory.MemoryFlowRepository;
-import com.lanyuanxiaoyao.flowable.service.FlowService;
-import com.lanyuanxiaoyao.flowable.model.FlowStatus;
-
-public class FlowExample {
- public static void main(String[] args) {
- // 1. 初始化仓储和服务
- FlowRepository flowRepository = new MemoryFlowRepository();
- FlowInstanceRepository instanceRepository = new MemoryFlowInstanceRepository();
- FlowService flowService = new FlowService(flowRepository, instanceRepository);
-
- // 2. 创建请假审批流程
- Flow leaveFlow = createLeaveFlow(flowService);
- System.out.println("创建请假流程:" + leaveFlow.getName());
- System.out.println("审批节点:" + leaveFlow.getNodes());
-
- // 3. 创建报销审批流程
- Flow expenseFlow = createExpenseFlow(flowService);
- System.out.println("\n创建报销流程:" + expenseFlow.getName());
- System.out.println("审批节点:" + expenseFlow.getNodes());
-
- // 4. 演示请假审批流程
- demonstrateLeaveFlow(flowService, leaveFlow.getId());
-
- // 5. 演示报销审批流程(带拒绝场景)
- demonstrateExpenseFlow(flowService, expenseFlow.getId());
-
- // 演示带有自定义节点操作的请假流程
- demonstrateLeaveFlowWithCustomNodes(flowService);
-
- // 演示系统节点审批流程
- demonstrateSystemNodeFlow(flowService);
- }
-
- private static Flow createLeaveFlow(FlowService flowService) {
- Flow flow = new Flow();
- flow.setName("员工请假审批流程");
- flow.setDescription("处理员工请假申请的标准流程");
- flow.addNode(new SimpleFlowNode("直属主管审批"));
- flow.addNode(new SimpleFlowNode("部门经理审批"));
- flow.addNode(new SimpleFlowNode("人力资源审批"));
- return flowService.createFlow(flow);
- }
-
- private static Flow createExpenseFlow(FlowService flowService) {
- Flow flow = new Flow();
- flow.setName("费用报销审批流程");
- flow.setDescription("处理员工报销申请的标准流程");
- flow.addNode(new SimpleFlowNode("财务初审"));
- flow.addNode(new SimpleFlowNode("部门经理审批"));
- flow.addNode(new SimpleFlowNode("财务总监审批"));
- flow.addNode(new SimpleFlowNode("出纳打款"));
- return flowService.createFlow(flow);
- }
-
- private static void demonstrateLeaveFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 开始请假审批流程演示 ===");
-
- // 启动流程实例
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 直属主管审批
- instance = flowService.approve(instance.getId());
- System.out.println("直属主管审批通过,当前节点:" + instance.getCurrentNode());
-
- // 部门经理审批
- instance = flowService.approve(instance.getId());
- System.out.println("部门经理审批通过,当前节点:" + instance.getCurrentNode());
-
- // 人力资源审批
- instance = flowService.approve(instance.getId());
- System.out.println("人力资源审批通过,流程状态:" + instance.getStatus());
- }
-
- private static void demonstrateExpenseFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 开始报销审批流程演示(带拒绝场景)===");
-
- // 启动流程实例
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 财务初审通过
- instance = flowService.approve(instance.getId());
- System.out.println("财务初审通过,当前节点:" + instance.getCurrentNode());
-
- // 部门经理拒绝
- instance = flowService.reject(instance.getId());
- System.out.println("部门经理拒绝,流程状态:" + instance.getStatus());
-
- try {
- // 尝试继续审批(应该会失败)
- flowService.approve(instance.getId());
- } catch (IllegalStateException e) {
- System.out.println("尝试继续审批被拒绝:" + e.getMessage());
- }
- }
-
- private static void demonstrateLeaveFlowWithCustomNodes(FlowService flowService) {
- System.out.println("\n=== 开始自定义节点请假流程演示 ===");
-
- // 创建流程定义
- Flow flow = new Flow();
- flow.setName("带自定义操作的请假流程");
- flow.setDescription("包含节点操作的请假流程示例");
-
- // 添加自定义节点
- flow.addNode(new LeaveRequestNode());
- flow.addNode(new ManagerApprovalNode());
-
- Flow savedFlow = flowService.createFlow(flow);
- System.out.println("创建流程:" + savedFlow.getName());
-
- // 演示通过流程
- demonstrateApproveFlow(flowService, savedFlow.getId());
-
- // 演示拒绝流程
- demonstrateRejectFlow(flowService, savedFlow.getId());
- }
-
- private static void demonstrateApproveFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 演示通过流程 ===");
-
- // 启动流程实例
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("\n启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 请假申请通过
- instance = flowService.approve(instance.getId());
- System.out.println("\n请假申请通过,当前节点:" + instance.getCurrentNode());
-
- // 经理审批通过
- instance = flowService.approve(instance.getId());
- System.out.println("\n经理审批通过,流程状态:" + instance.getStatus());
- }
-
- private static void demonstrateRejectFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 演示拒绝流程 ===");
-
- // 启动新的流程实例
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("\n启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 请假申请通过
- instance = flowService.approve(instance.getId());
- System.out.println("\n请假申请通过,当前节点:" + instance.getCurrentNode());
-
- // 经理审批拒绝
- instance = flowService.reject(instance.getId());
- System.out.println("\n经理审批拒绝,流程状态:" + instance.getStatus());
- }
-
- private static void demonstrateSystemNodeFlow(FlowService flowService) {
- System.out.println("\n=== 开始系统节点审批流程演示 ===");
-
- // 创建流程定义
- Flow flow = new Flow();
- flow.setName("带系统审核的请假流程");
- flow.setDescription("包含系统自动审核的请假流程示例");
-
- // 添加节点
- flow.addNode(new LeaveRequestNode()); // 请假申请
- flow.addNode(new LeaveSystemCheckNode(5)); // 系统审核(允许5天内自动通过)
- flow.addNode(new ManagerApprovalNode()); // 经理审批
-
- Flow savedFlow = flowService.createFlow(flow);
- System.out.println("创建流程:" + savedFlow.getName());
-
- // 演示自动通过的情况(3天请假)
- demonstrateSystemApproveFlow(flowService, savedFlow.getId());
-
- // 演示自动拒绝的情况(7天请假)
- demonstrateSystemRejectFlow(flowService, savedFlow.getId());
- }
-
- private static void demonstrateSystemApproveFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 演示系统自动通过流程 ===");
-
- // 启动流程实例(LeaveRequestNode设置3天请假)
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("\n启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 验证初始状态
- FlowAssert.assertStatus("流程启动", FlowStatus.PENDING, instance);
- FlowAssert.assertNode("流程启动", "请假申请", instance);
- FlowAssert.assertVariable("流程启动", 3, "days", instance);
- FlowAssert.assertVariable("流程启动", "年假", "reason", instance);
-
- // 请假申请通过后会自动执行系统审核
- instance = flowService.approve(instance.getId());
- System.out.println("\n请假申请通过后,当前节点:" + instance.getCurrentNode());
-
- // 验证系统审核通过
- FlowAssert.assertStatus("系统审核", FlowStatus.PENDING, instance);
- FlowAssert.assertNode("系统审核", "经理审批", instance);
- FlowAssert.assertVariable("系统审核", "系统自动通过", "systemComment", instance);
-
- // 经理最终审批
- instance = flowService.approve(instance.getId());
- System.out.println("\n经理审批通过,流程状态:" + instance.getStatus());
-
- // 验证流程完成
- FlowAssert.assertStatus("流程完成", FlowStatus.APPROVED, instance);
- FlowAssert.assertVariable("流程完成", "同意", "managerComment", instance);
- }
-
- private static void demonstrateSystemRejectFlow(FlowService flowService, String flowId) {
- System.out.println("\n=== 演示系统自动拒绝流程 ===");
-
- // 修改LeaveRequestNode中的请假天数为7天
- LeaveRequestNode.setTestDays(7);
-
- // 启动流程实例
- FlowInstance instance = flowService.startFlow(flowId);
- System.out.println("\n启动流程实例,当前节点:" + instance.getCurrentNode());
-
- // 验证初始状态
- FlowAssert.assertStatus("流程启动", FlowStatus.PENDING, instance);
- FlowAssert.assertNode("流程启动", "请假申请", instance);
- FlowAssert.assertVariable("流程启动", 7, "days", instance);
-
- // 请假申请通过后系统会自动拒绝
- instance = flowService.approve(instance.getId());
- System.out.println("\n请假申请后,流程状态:" + instance.getStatus());
-
- // 验证系统拒绝
- FlowAssert.assertStatus("系统拒绝", FlowStatus.REJECTED, instance);
- FlowAssert.assertVariable("系统拒绝", "请假天数(7)超过系统限制(5),需要额外审批", "systemComment", instance);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveRequestNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveRequestNode.java
deleted file mode 100644
index e37fa92..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveRequestNode.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example.node;
-
-import com.lanyuanxiaoyao.flowable.node.AbstractFlowNode;
-import com.lanyuanxiaoyao.flowable.model.FlowContext;
-import java.time.LocalDateTime;
-
-public class LeaveRequestNode extends AbstractFlowNode {
- // 用于测试的请假天数
- private static int testDays = 3;
-
- public LeaveRequestNode() {
- super("请假申请");
- }
-
- @Override
- public void execute(FlowContext context) {
- // 模拟设置请假信息,使用testDays而不是硬编码的值
- context.setVariable("days", testDays);
- context.setVariable("reason", "年假");
- context.setVariable("requestTime", LocalDateTime.now());
- System.out.println("执行请假申请节点:设置请假天数为" + testDays + "天,请假理由为年假");
- }
-
- @Override
- public void onApprove(FlowContext context) {
- System.out.println("请假申请提交成功");
- }
-
- @Override
- public void onReject(FlowContext context) {
- System.out.println("请假申请被撤销");
- }
-
- /**
- * 设置测试用的请假天数
- * @param days 请假天数
- */
- public static void setTestDays(int days) {
- testDays = days;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveSystemCheckNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveSystemCheckNode.java
deleted file mode 100644
index 8bf1253..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveSystemCheckNode.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example.node;
-
-import com.lanyuanxiaoyao.flowable.node.SystemFlowNode;
-import com.lanyuanxiaoyao.flowable.model.FlowContext;
-import java.time.LocalDateTime;
-
-/**
- * 请假系统自动审核节点
- * 根据请假天数自动判断是否通过
- */
-public class LeaveSystemCheckNode extends SystemFlowNode {
- private final int maxDays; // 最大允许请假天数
-
- public LeaveSystemCheckNode(int maxDays) {
- super("系统审核");
- this.maxDays = maxDays;
- }
-
- @Override
- public boolean autoApprove(FlowContext context) {
- Integer days = context.getVariable("days", Integer.class);
- return days != null && days <= maxDays;
- }
-
- @Override
- protected String getRejectionReason(FlowContext context) {
- Integer days = context.getVariable("days", Integer.class);
- return String.format("请假天数(%d)超过系统限制(%d),需要额外审批", days, maxDays);
- }
-
- @Override
- public void onApprove(FlowContext context) {
- Integer days = context.getVariable("days", Integer.class);
- String reason = context.getVariable("reason", String.class);
-
- System.out.println("执行系统审核节点:");
- System.out.println("- 请假天数:" + days);
- System.out.println("- 请假理由:" + reason);
- System.out.println("- 系统限制:" + maxDays + "天");
-
- context.setVariable("systemComment", "系统自动通过");
- context.setVariable("systemCheckTime", LocalDateTime.now());
- System.out.println("系统自动审核通过");
- }
-
- @Override
- public void onReject(FlowContext context) {
- Integer days = context.getVariable("days", Integer.class);
- String reason = context.getVariable("reason", String.class);
-
- System.out.println("执行系统审核节点:");
- System.out.println("- 请假天数:" + days);
- System.out.println("- 请假理由:" + reason);
- System.out.println("- 系统限制:" + maxDays + "天");
-
- String rejectionReason = getRejectionReason(context);
- context.setVariable("systemComment", rejectionReason);
- context.setVariable("systemCheckTime", LocalDateTime.now());
- System.out.println("系统自动审核拒绝:" + rejectionReason);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/ManagerApprovalNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/ManagerApprovalNode.java
deleted file mode 100644
index 27aa3e4..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/ManagerApprovalNode.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example.node;
-
-import com.lanyuanxiaoyao.flowable.node.AbstractFlowNode;
-import com.lanyuanxiaoyao.flowable.model.FlowContext;
-import java.time.LocalDateTime;
-
-public class ManagerApprovalNode extends AbstractFlowNode {
- public ManagerApprovalNode() {
- super("经理审批");
- }
-
- @Override
- public void execute(FlowContext context) {
- Integer days = context.getVariable("days", Integer.class);
- String reason = context.getVariable("reason", String.class);
- LocalDateTime requestTime = context.getVariable("requestTime", LocalDateTime.class);
-
- System.out.println("执行经理审批节点:");
- System.out.println("- 请假天数:" + days);
- System.out.println("- 请假理由:" + reason);
- System.out.println("- 申请时间:" + requestTime);
- }
-
- @Override
- public void onApprove(FlowContext context) {
- context.setVariable("approvalTime", LocalDateTime.now());
- context.setVariable("managerComment", "同意");
- System.out.println("经理已批准请假申请");
- System.out.println("- 审批意见:同意");
- }
-
- @Override
- public void onReject(FlowContext context) {
- context.setVariable("approvalTime", LocalDateTime.now());
- context.setVariable("managerComment", "请假天数过长,建议调整后重新提交");
- System.out.println("经理已拒绝请假申请");
- System.out.println("- 拒绝原因:请假天数过长,建议调整后重新提交");
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/SimpleFlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/SimpleFlowNode.java
deleted file mode 100644
index 4901ac6..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/example/node/SimpleFlowNode.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.lanyuanxiaoyao.flowable.example.node;
-
-import com.lanyuanxiaoyao.flowable.node.AbstractFlowNode;
-import com.lanyuanxiaoyao.flowable.model.FlowContext;
-
-public class SimpleFlowNode extends AbstractFlowNode {
- public SimpleFlowNode(String nodeId) {
- super(nodeId);
- }
-
- @Override
- public void execute(FlowContext context) {
- System.out.println("执行节点:" + getNodeId());
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java b/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java
index 205b2c8..27a0f3f 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java
@@ -1,57 +1,165 @@
package com.lanyuanxiaoyao.flowable.model;
import com.lanyuanxiaoyao.flowable.node.FlowNode;
-import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import lombok.Data;
/**
- * 流程定义类
- * 用于定义一个完整的审批流程,包含流程的基本信息和所有审批节点
+ * 流程实例类
+ * 包含流程定义和执行状态
*/
@Data
public class Flow {
- /**
- * 流程定义ID
- */
- private String id;
+ /**
+ * 流程实例ID
+ */
+ private String id;
- /**
- * 流程名称
- */
- private String name;
+ /**
+ * 流程名称
+ */
+ private String name;
- /**
- * 流程描述
- */
- private String description;
+ /**
+ * 流程描述
+ */
+ private String description;
- /**
- * 流程节点列表
- * 按照列表顺序依次执行,每个节点都是一个可执行的审批操作
- */
- private List nodes;
+ /**
+ * 流程节点列表
+ * 按照列表顺序依次执行,每个节点都是一个可执行的审批操作
+ */
+ private List nodes;
- /**
- * 创建时间
- */
- private LocalDateTime createTime;
+ /**
+ * 当前执行到的节点ID
+ */
+ private String currentNode;
- /**
- * 最后更新时间
- */
- private LocalDateTime updateTime;
+ /**
+ * 当前流程状态
+ */
+ private FlowStatus status;
- public Flow() {
- this.nodes = new ArrayList<>();
+ /**
+ * 流程上下文变量
+ * 用于存储流程执行过程中的数据,实现节点间的数据传递
+ */
+ private Map contextVariables;
+
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * 最后更新时间
+ */
+ private LocalDateTime updateTime;
+
+ public Flow() {
+ this.nodes = new ArrayList<>();
+ this.contextVariables = new HashMap<>();
+ this.status = FlowStatus.PENDING;
+ }
+
+ /**
+ * 添加一个新的流程节点
+ *
+ * @param node 要添加的流程节点
+ */
+ public void addNode(FlowNode node) {
+ nodes.add(node);
+ if (currentNode == null && !nodes.isEmpty()) {
+ currentNode = nodes.get(0).getNodeId();
}
+ }
- /**
- * 添加一个新的流程节点
- * @param node 要添加的流程节点
- */
- public void addNode(FlowNode node) {
- nodes.add(node);
+ /**
+ * 获取当前节点
+ *
+ * @return 当前节点对象
+ */
+ public FlowNode getCurrentNodeObject() {
+ return nodes.stream()
+ .filter(node -> node.getNodeId().equals(currentNode))
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("找不到当前节点"));
+ }
+
+ /**
+ * 获取下一个节点
+ *
+ * @return 下一个节点对象,如果已经是最后一个节点则返回null
+ */
+ public FlowNode getNextNode() {
+ for (int i = 0; i < nodes.size() - 1; i++) {
+ if (nodes.get(i).getNodeId().equals(currentNode)) {
+ return nodes.get(i + 1);
+ }
}
+ return null;
+ }
+
+ /**
+ * 判断当前是否是最后一个节点
+ *
+ * @return true表示是最后一个节点
+ */
+ public boolean isLastNode() {
+ return nodes.get(nodes.size() - 1).getNodeId().equals(currentNode);
+ }
+
+ /**
+ * 移动到下一个节点
+ *
+ * @throws IllegalStateException 如果已经是最后一个节点
+ */
+ public void moveToNextNode() {
+ FlowNode nextNode = getNextNode();
+ if (nextNode == null) {
+ throw new IllegalStateException("已经是最后一个节点");
+ }
+ currentNode = nextNode.getNodeId();
+ }
+
+ /**
+ * 创建流程上下文
+ */
+ public FlowContext createContext() {
+ FlowContext context = new FlowContext();
+ context.setFlowId(id);
+ context.getVariables().putAll(contextVariables);
+ return context;
+ }
+
+ /**
+ * 保存上下文变量
+ */
+ public void saveContext(FlowContext context) {
+ contextVariables.clear();
+ contextVariables.putAll(context.getVariables());
+ }
+
+ /**
+ * 创建流程实例的副本
+ * 用于从流程定义创建新的流程实例
+ */
+ public Flow createInstance() {
+ Flow instance = new Flow();
+ instance.setName(name);
+ instance.setDescription(description);
+ instance.setNodes(new ArrayList<>(nodes));
+ instance.setContextVariables(new HashMap<>(contextVariables));
+ instance.setCreateTime(LocalDateTime.now());
+ instance.setUpdateTime(LocalDateTime.now());
+ if (!nodes.isEmpty()) {
+ instance.setCurrentNode(nodes.get(0).getNodeId());
+ }
+ return instance;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowContext.java b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowContext.java
index daa0d54..13e0253 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowContext.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowContext.java
@@ -1,63 +1,44 @@
package com.lanyuanxiaoyao.flowable.model;
-import lombok.Data;
import java.util.HashMap;
import java.util.Map;
+import lombok.Data;
/**
- * 流程上下文类
- * 用于在流程节点执行过程中传递和存储数据
+ * 流程上下文
+ * 用于在节点间传递数据
*/
@Data
public class FlowContext {
- /**
- * 对应的流程定义ID
- */
- private String flowId;
+ /**
+ * 流程ID
+ */
+ private String flowId;
- /**
- * 对应的流程实例ID
- */
- private String instanceId;
+ /**
+ * 上下文变量
+ */
+ private Map variables;
- /**
- * 上下文变量存储
- * 用于在节点间传递数据
- */
- private Map variables;
+ public FlowContext() {
+ this.variables = new HashMap<>();
+ }
- public FlowContext() {
- this.variables = new HashMap<>();
+ /**
+ * 获取指定类型的变量值
+ */
+ public T getVariable(String key, Class type) {
+ Object value = variables.get(key);
+ if (value == null) {
+ return null;
}
+ return type.cast(value);
+ }
- /**
- * 设置上下文变量
- * @param key 变量名
- * @param value 变量值
- */
- public void setVariable(String key, Object value) {
- variables.put(key, value);
- }
-
- /**
- * 获取上下文变量
- * @param key 变量名
- * @return 变量值
- */
- public Object getVariable(String key) {
- return variables.get(key);
- }
-
- /**
- * 获取指定类型的上下文变量
- * @param key 变量名
- * @param type 期望的变量类型
- * @param 变量类型
- * @return 转换为指定类型的变量值
- */
- @SuppressWarnings("unchecked")
- public T getVariable(String key, Class type) {
- Object value = variables.get(key);
- return value == null ? null : (T) value;
- }
+ /**
+ * 设置变量值
+ */
+ public void setVariable(String key, Object value) {
+ variables.put(key, value);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowInstance.java b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowInstance.java
deleted file mode 100644
index 2067b6f..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowInstance.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.lanyuanxiaoyao.flowable.model;
-
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-import lombok.Data;
-
-/**
- * 流程实例类
- * 代表一个正在执行的具体流程,记录了当前的执行状态和上下文数据
- */
-@Data
-public class FlowInstance {
- /**
- * 实例ID
- */
- private String id;
-
- /**
- * 对应的流程定义ID
- */
- private String flowId;
-
- /**
- * 当前执行到的节点ID
- */
- private String currentNode;
-
- /**
- * 当前流程状态
- * @see FlowStatus
- */
- private FlowStatus status;
-
- /**
- * 创建时间
- */
- private LocalDateTime createTime;
-
- /**
- * 最后更新时间
- */
- private LocalDateTime updateTime;
-
- /**
- * 流程上下文变量
- * 用于存储流程执行过程中的数据,实现节点间的数据传递
- */
- private Map contextVariables;
-
- public FlowInstance() {
- this.contextVariables = new HashMap<>();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowStatus.java b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowStatus.java
index b245afa..2b7e90c 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowStatus.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowStatus.java
@@ -5,31 +5,31 @@ package com.lanyuanxiaoyao.flowable.model;
* 用于表示流程实例的当前状态
*/
public enum FlowStatus {
- /**
- * 进行中:流程正在执行中
- */
- PENDING("进行中"),
+ /**
+ * 进行中:流程正在执行中
+ */
+ PENDING("进行中"),
- /**
- * 已通过:流程已经完成并通过
- */
- APPROVED("已通过"),
+ /**
+ * 已通过:流程已经完成并通过
+ */
+ APPROVED("已通过"),
- /**
- * 已拒绝:流程被拒绝
- */
- REJECTED("已拒绝");
+ /**
+ * 已拒绝:流程被拒绝
+ */
+ REJECTED("已拒绝");
- /**
- * 状态的中文描述
- */
- private final String description;
+ /**
+ * 状态的中文描述
+ */
+ private final String description;
- FlowStatus(String description) {
- this.description = description;
- }
+ FlowStatus(String description) {
+ this.description = description;
+ }
- public String getDescription() {
- return description;
- }
+ public String getDescription() {
+ return description;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java
index cce1d06..1e5b0e9 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java
@@ -6,21 +6,21 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public abstract class AbstractFlowNode implements FlowNode {
- @Getter
- private final String nodeId;
+ @Getter
+ private final String nodeId;
- @Override
- public void execute(FlowContext context) {
- // 默认实现为空
- }
+ @Override
+ public void execute(FlowContext context) {
+ // 默认实现为空
+ }
- @Override
- public void onApprove(FlowContext context) {
- // 默认实现为空
- }
+ @Override
+ public void onApprove(FlowContext context) {
+ // 默认实现为空
+ }
- @Override
- public void onReject(FlowContext context) {
- // 默认实现为空
- }
+ @Override
+ public void onReject(FlowContext context) {
+ // 默认实现为空
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java
index cf76ad2..7040854 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java
@@ -7,23 +7,23 @@ import com.lanyuanxiaoyao.flowable.model.FlowContext;
* 定义节点的基本操作
*/
public interface FlowNode {
- /**
- * 获取节点ID
- */
- String getNodeId();
+ /**
+ * 获取节点ID
+ */
+ String getNodeId();
- /**
- * 节点初始化时执行的操作
- */
- void execute(FlowContext context);
+ /**
+ * 节点初始化时执行的操作
+ */
+ void execute(FlowContext context);
- /**
- * 节点通过时执行的操作
- */
- void onApprove(FlowContext context);
+ /**
+ * 节点通过时执行的操作
+ */
+ void onApprove(FlowContext context);
- /**
- * 节点拒绝时执行的操作
- */
- void onReject(FlowContext context);
+ /**
+ * 节点拒绝时执行的操作
+ */
+ void onReject(FlowContext context);
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java
index b42e30f..ff13e86 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java
@@ -7,21 +7,23 @@ import com.lanyuanxiaoyao.flowable.model.FlowContext;
* 根据预设规则自动执行审批操作
*/
public abstract class SystemFlowNode extends AbstractFlowNode {
- public SystemFlowNode(String nodeId) {
- super(nodeId);
- }
+ public SystemFlowNode(String nodeId) {
+ super(nodeId);
+ }
- /**
- * 系统自动判断是否通过
- * @param context 流程上下文
- * @return true表示通过,false表示拒绝
- */
- public abstract boolean autoApprove(FlowContext context);
+ /**
+ * 系统自动判断是否通过
+ *
+ * @param context 流程上下文
+ * @return true表示通过,false表示拒绝
+ */
+ public abstract boolean autoApprove(FlowContext context);
- /**
- * 获取拒绝原因
- * @param context 流程上下文
- * @return 拒绝原因
- */
- protected abstract String getRejectionReason(FlowContext context);
+ /**
+ * 获取拒绝原因
+ *
+ * @param context 流程上下文
+ * @return 拒绝原因
+ */
+ protected abstract String getRejectionReason(FlowContext context);
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowInstanceRepository.java b/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowInstanceRepository.java
deleted file mode 100644
index f91d674..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowInstanceRepository.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.lanyuanxiaoyao.flowable.repository;
-
-import com.lanyuanxiaoyao.flowable.model.FlowInstance;
-import java.util.List;
-
-public interface FlowInstanceRepository {
- FlowInstance save(FlowInstance instance);
- FlowInstance findById(String id);
- List findAll();
- List findByFlowId(String flowId);
- void deleteById(String id);
- FlowInstance update(FlowInstance instance);
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowRepository.java b/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowRepository.java
index 57400f0..88243b4 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowRepository.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowRepository.java
@@ -1,12 +1,11 @@
package com.lanyuanxiaoyao.flowable.repository;
import com.lanyuanxiaoyao.flowable.model.Flow;
-import java.util.List;
public interface FlowRepository {
- Flow save(Flow flow);
- Flow findById(String id);
- List findAll();
- void deleteById(String id);
- Flow update(Flow flow);
+ Flow save(Flow flow);
+
+ Flow update(Flow flow);
+
+ Flow findById(String id);
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowInstanceRepository.java b/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowInstanceRepository.java
deleted file mode 100644
index f6ed59a..0000000
--- a/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowInstanceRepository.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.lanyuanxiaoyao.flowable.repository.memory;
-
-import com.lanyuanxiaoyao.flowable.model.FlowInstance;
-import com.lanyuanxiaoyao.flowable.repository.FlowInstanceRepository;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-public class MemoryFlowInstanceRepository implements FlowInstanceRepository {
- private final Map storage = new ConcurrentHashMap<>();
-
- @Override
- public FlowInstance save(FlowInstance instance) {
- String id = UUID.randomUUID().toString();
- instance.setId(id);
- storage.put(id, instance);
- return instance;
- }
-
- @Override
- public FlowInstance findById(String id) {
- return storage.get(id);
- }
-
- @Override
- public List findAll() {
- return new ArrayList<>(storage.values());
- }
-
- @Override
- public List findByFlowId(String flowId) {
- return storage.values().stream()
- .filter(instance -> instance.getFlowId().equals(flowId))
- .collect(Collectors.toList());
- }
-
- @Override
- public void deleteById(String id) {
- storage.remove(id);
- }
-
- @Override
- public FlowInstance update(FlowInstance instance) {
- if (instance.getId() == null) {
- throw new IllegalArgumentException("Instance id cannot be null when updating");
- }
- storage.put(instance.getId(), instance);
- return instance;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowRepository.java b/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowRepository.java
index 5b13fc1..e8e9391 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowRepository.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowRepository.java
@@ -2,44 +2,33 @@ package com.lanyuanxiaoyao.flowable.repository.memory;
import com.lanyuanxiaoyao.flowable.model.Flow;
import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MemoryFlowRepository implements FlowRepository {
- private final Map storage = new ConcurrentHashMap<>();
+ private final Map flows = new ConcurrentHashMap<>();
- @Override
- public Flow save(Flow flow) {
- String id = UUID.randomUUID().toString();
- flow.setId(id);
- storage.put(id, flow);
- return flow;
+ @Override
+ public Flow save(Flow flow) {
+ if (flow.getId() == null) {
+ flow.setId(UUID.randomUUID().toString());
}
+ flows.put(flow.getId(), flow);
+ return flow;
+ }
- @Override
- public Flow findById(String id) {
- return storage.get(id);
+ @Override
+ public Flow update(Flow flow) {
+ if (flow.getId() == null || !flows.containsKey(flow.getId())) {
+ throw new IllegalArgumentException("找不到对应的流程实例");
}
+ flows.put(flow.getId(), flow);
+ return flow;
+ }
- @Override
- public List findAll() {
- return new ArrayList<>(storage.values());
- }
-
- @Override
- public void deleteById(String id) {
- storage.remove(id);
- }
-
- @Override
- public Flow update(Flow flow) {
- if (flow.getId() == null) {
- throw new IllegalArgumentException("Flow id cannot be null when updating");
- }
- storage.put(flow.getId(), flow);
- return flow;
- }
+ @Override
+ public Flow findById(String id) {
+ return flows.get(id);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java b/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java
index c094656..f847cd7 100644
--- a/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java
+++ b/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java
@@ -1,236 +1,153 @@
package com.lanyuanxiaoyao.flowable.service;
import com.lanyuanxiaoyao.flowable.model.Flow;
-import com.lanyuanxiaoyao.flowable.model.FlowInstance;
-import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
-import com.lanyuanxiaoyao.flowable.repository.FlowInstanceRepository;
-import java.time.LocalDateTime;
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import com.lanyuanxiaoyao.flowable.model.FlowStatus;
import com.lanyuanxiaoyao.flowable.model.FlowContext;
+import com.lanyuanxiaoyao.flowable.model.FlowStatus;
import com.lanyuanxiaoyao.flowable.node.FlowNode;
import com.lanyuanxiaoyao.flowable.node.SystemFlowNode;
+import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
+import java.time.LocalDateTime;
+import lombok.RequiredArgsConstructor;
+/**
+ * 流程服务类
+ * 提供流程的创建、启动、审批等核心功能
+ */
@RequiredArgsConstructor
public class FlowService {
- private final FlowRepository flowRepository;
- private final FlowInstanceRepository instanceRepository;
+ private final FlowRepository flowRepository;
- /**
- * 创建新的流程定义
- */
- public Flow createFlow(Flow flow) {
- if (flow.getNodes() == null || flow.getNodes().isEmpty()) {
- throw new IllegalArgumentException("流程节点不能为空");
- }
- flow.setCreateTime(LocalDateTime.now());
- flow.setUpdateTime(LocalDateTime.now());
- return flowRepository.save(flow);
- }
-
- /**
- * 启动一个新的流程实例
- */
- public FlowInstance startFlow(String flowId) {
- Flow flow = flowRepository.findById(flowId);
- if (flow == null) {
- throw new IllegalArgumentException("找不到对应的流程定义");
+ /**
+ * 创建新的流程定义
+ */
+ public Flow createFlow(Flow flow) {
+ validateFlowNodes(flow);
+ initializeFlow(flow);
+ return flowRepository.save(flow);
}
- FlowInstance instance = new FlowInstance();
- instance.setFlowId(flowId);
- instance.setCurrentNode(flow.getNodes().get(0).getNodeId());
- instance.setStatus(FlowStatus.PENDING);
- instance.setCreateTime(LocalDateTime.now());
- instance.setUpdateTime(LocalDateTime.now());
-
- // 保存实例
- instance = instanceRepository.save(instance);
-
- // 创建上下文
- FlowContext context = createContext(instance);
-
- // 执行第一个节点
- FlowNode firstNode = flow.getNodes().get(0);
- firstNode.execute(context);
-
- // 如果是系统节点,自动执行审批
- if (firstNode instanceof SystemFlowNode) {
- instance = handleSystemNode(instance, firstNode, context);
- }
-
- // 保存上下文变量
- saveContext(instance, context);
- return instanceRepository.update(instance);
- }
-
- /**
- * 审批通过当前节点
- */
- public FlowInstance approve(String instanceId) {
- FlowInstance instance = getAndValidateInstance(instanceId);
- if (FlowStatus.APPROVED.equals(instance.getStatus()) ||
- FlowStatus.REJECTED.equals(instance.getStatus())) {
- throw new IllegalStateException("当前流程已经结束");
+ /**
+ * 启动一个新的流程实例
+ */
+ public Flow startFlow(String flowId) {
+ Flow flowDefinition = findFlowById(flowId);
+ Flow instance = flowDefinition.createInstance();
+ instance = flowRepository.save(instance);
+ return executeNode(instance);
}
- Flow flow = flowRepository.findById(instance.getFlowId());
- List nodes = flow.getNodes();
- int currentIndex = -1;
-
- // 查找当前节点
- for (int i = 0; i < nodes.size(); i++) {
- if (nodes.get(i).getNodeId().equals(instance.getCurrentNode())) {
- currentIndex = i;
- break;
- }
- }
+ /**
+ * 审批通过当前节点
+ */
+ public Flow approve(String flowId) {
+ Flow flow = findFlowById(flowId);
+ validateFlowNotCompleted(flow);
- // 创建上下文并加载已有变量
- FlowContext context = createContext(instance);
+ FlowContext context = flow.createContext();
+ FlowNode currentNode = flow.getCurrentNodeObject();
+ currentNode.onApprove(context);
- // 执行当前节点的通过操作
- FlowNode currentNode = nodes.get(currentIndex);
- currentNode.onApprove(context);
-
- // 判断是否是最后一个节点
- if (currentIndex == nodes.size() - 1) {
- instance.setStatus(FlowStatus.APPROVED);
- } else {
- // 移动到下一个节点并执行
- FlowNode nextNode = nodes.get(currentIndex + 1);
- instance.setCurrentNode(nextNode.getNodeId());
- nextNode.execute(context);
-
- // 如果下一个是系统节点,自动执行审批
- if (nextNode instanceof SystemFlowNode) {
- instance = handleSystemNode(instance, nextNode, context);
- }
- }
-
- // 保存上下文变量
- saveContext(instance, context);
- instance.setUpdateTime(LocalDateTime.now());
- return instanceRepository.update(instance);
- }
-
- /**
- * 拒绝当前节点,结束流程
- */
- public FlowInstance reject(String instanceId) {
- FlowInstance instance = getAndValidateInstance(instanceId);
- if (FlowStatus.APPROVED.equals(instance.getStatus()) ||
- FlowStatus.REJECTED.equals(instance.getStatus())) {
- throw new IllegalStateException("当前流程已经结束");
- }
-
- // 创建上下文并加载已有变量
- FlowContext context = createContext(instance);
-
- // 执行当前节点的拒绝操作
- Flow flow = flowRepository.findById(instance.getFlowId());
- for (FlowNode node : flow.getNodes()) {
- if (node.getNodeId().equals(instance.getCurrentNode())) {
- node.onReject(context);
- break;
- }
- }
-
- instance.setStatus(FlowStatus.REJECTED);
-
- // 保存上下文变量
- saveContext(instance, context);
- instance.setUpdateTime(LocalDateTime.now());
- return instanceRepository.update(instance);
- }
-
- /**
- * 获取流程实例当前状态
- */
- public FlowInstance getFlowInstance(String instanceId) {
- return getAndValidateInstance(instanceId);
- }
-
- /**
- * 获取流程定义的所有节点
- */
- public List getFlowNodes(String flowId) {
- Flow flow = flowRepository.findById(flowId);
- if (flow == null) {
- throw new IllegalArgumentException("找不到对应的流程定义");
- }
- return flow.getNodes();
- }
-
- private FlowInstance getAndValidateInstance(String instanceId) {
- FlowInstance instance = instanceRepository.findById(instanceId);
- if (instance == null) {
- throw new IllegalArgumentException("找不到对应的流程实例");
- }
- return instance;
- }
-
- private FlowContext createContext(FlowInstance instance) {
- FlowContext context = new FlowContext();
- context.setFlowId(instance.getFlowId());
- context.setInstanceId(instance.getId());
- // 从实例中加载已保存的变量
- if (instance.getContextVariables() != null) {
- context.getVariables().putAll(instance.getContextVariables());
- }
- return context;
- }
-
- private void saveContext(FlowInstance instance, FlowContext context) {
- // 将上下文变量保存到实例中
- instance.getContextVariables().clear();
- instance.getContextVariables().putAll(context.getVariables());
- }
-
- /**
- * 处理系统节点的自动审批
- */
- private FlowInstance handleSystemNode(FlowInstance instance, FlowNode node, FlowContext context) {
- SystemFlowNode systemNode = (SystemFlowNode) node;
- if (systemNode.autoApprove(context)) {
- // 自动通过
- systemNode.onApprove(context);
- // 如果��最后一个节点,标记为完成
- if (isLastNode(instance.getFlowId(), node.getNodeId())) {
- instance.setStatus(FlowStatus.APPROVED);
+ if (flow.isLastNode()) {
+ flow.setStatus(FlowStatus.APPROVED);
+ return updateFlow(flow, context);
} else {
- // 否则移动到下一个节点
- FlowNode nextNode = getNextNode(instance.getFlowId(), node.getNodeId());
- instance.setCurrentNode(nextNode.getNodeId());
- nextNode.execute(context);
- // 如果下一个还是系统节点,继续自动处理
- if (nextNode instanceof SystemFlowNode) {
- instance = handleSystemNode(instance, nextNode, context);
+ flow = updateFlow(flow, context); // 先保存当前节点的状态
+ flow.moveToNextNode();
+ return executeNode(flow); // 执行下一个节点(可能是系统节点)
+ }
+ }
+
+ /**
+ * 拒绝当前节点,结束流程
+ */
+ public Flow reject(String flowId) {
+ Flow flow = findFlowById(flowId);
+ validateFlowNotCompleted(flow);
+
+ FlowContext context = flow.createContext();
+ flow.getCurrentNodeObject().onReject(context);
+ flow.saveContext(context); // 保存拒绝操作的上下文变量
+ flow.setStatus(FlowStatus.REJECTED);
+
+ return updateFlow(flow, context);
+ }
+
+ /**
+ * 获取流程实例
+ */
+ public Flow getFlow(String flowId) {
+ return findFlowById(flowId);
+ }
+
+ // 私有辅助方法
+
+ private void validateFlowNodes(Flow flow) {
+ if (flow.getNodes() == null || flow.getNodes().isEmpty()) {
+ throw new IllegalArgumentException("流程节点不能为空");
+ }
+ }
+
+ private void initializeFlow(Flow flow) {
+ flow.setCreateTime(LocalDateTime.now());
+ flow.setUpdateTime(LocalDateTime.now());
+ }
+
+ private Flow findFlowById(String flowId) {
+ Flow flow = flowRepository.findById(flowId);
+ if (flow == null) {
+ throw new IllegalArgumentException("找不到对应的流程");
+ }
+ return flow;
+ }
+
+ private void validateFlowNotCompleted(Flow flow) {
+ if (FlowStatus.APPROVED.equals(flow.getStatus()) ||
+ FlowStatus.REJECTED.equals(flow.getStatus())) {
+ throw new IllegalStateException("当前流程已经结束");
+ }
+ }
+
+ private Flow executeNode(Flow flow) {
+ FlowContext context = flow.createContext();
+ FlowNode currentNode = flow.getCurrentNodeObject();
+
+ // 执行当前节点
+ currentNode.execute(context);
+
+ // 如果是系统节点,自动执行审批
+ if (currentNode instanceof SystemFlowNode) {
+ return handleSystemNode(flow, currentNode, context);
+ }
+
+ return updateFlow(flow, context);
+ }
+
+ private Flow handleSystemNode(Flow flow, FlowNode node, FlowContext context) {
+ SystemFlowNode systemNode = (SystemFlowNode) node;
+
+ if (systemNode.autoApprove(context)) {
+ // 自动通过
+ systemNode.onApprove(context);
+ flow = updateFlow(flow, context); // 先保存当前状态
+
+ if (!flow.isLastNode()) {
+ flow.moveToNextNode();
+ return executeNode(flow); // 执行下一个节点
+ } else {
+ flow.setStatus(FlowStatus.APPROVED);
+ return updateFlow(flow, context);
}
- }
- } else {
- // 自动拒绝
- systemNode.onReject(context);
- instance.setStatus(FlowStatus.REJECTED);
- }
- return instance;
- }
-
- private boolean isLastNode(String flowId, String nodeId) {
- Flow flow = flowRepository.findById(flowId);
- List nodes = flow.getNodes();
- return nodes.get(nodes.size() - 1).getNodeId().equals(nodeId);
- }
-
- private FlowNode getNextNode(String flowId, String currentNodeId) {
- Flow flow = flowRepository.findById(flowId);
- List nodes = flow.getNodes();
- for (int i = 0; i < nodes.size() - 1; i++) {
- if (nodes.get(i).getNodeId().equals(currentNodeId)) {
- return nodes.get(i + 1);
+ } else {
+ // 自动��绝
+ systemNode.onReject(context);
+ flow.setStatus(FlowStatus.REJECTED);
+ return updateFlow(flow, context);
}
}
- throw new IllegalStateException("找不到下一个节点");
- }
-}
\ No newline at end of file
+
+ private Flow updateFlow(Flow flow, FlowContext context) {
+ flow.saveContext(context);
+ flow.setUpdateTime(LocalDateTime.now());
+ return flowRepository.update(flow);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java b/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java
new file mode 100644
index 0000000..53e901e
--- /dev/null
+++ b/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java
@@ -0,0 +1,213 @@
+package com.lanyuanxiaoyao.flowable;
+
+import com.lanyuanxiaoyao.flowable.model.Flow;
+import com.lanyuanxiaoyao.flowable.model.FlowStatus;
+import com.lanyuanxiaoyao.flowable.node.LeaveRequestNode;
+import com.lanyuanxiaoyao.flowable.node.LeaveSystemCheckNode;
+import com.lanyuanxiaoyao.flowable.node.ManagerApprovalNode;
+import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
+import com.lanyuanxiaoyao.flowable.repository.memory.MemoryFlowRepository;
+import com.lanyuanxiaoyao.flowable.service.FlowService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DisplayName("流程服务测试")
+class FlowServiceTest {
+ private FlowService flowService;
+ private Flow leaveFlow;
+
+ @BeforeEach
+ void setUp() {
+ FlowRepository flowRepository = new MemoryFlowRepository();
+ flowService = new FlowService(flowRepository);
+
+ // 创建请假流程定义
+ leaveFlow = new Flow();
+ leaveFlow.setName("带系统审核的请假流程");
+ leaveFlow.setDescription("包含系统自动审核的请假流程示例");
+ leaveFlow.addNode(new LeaveRequestNode());
+ leaveFlow.addNode(new LeaveSystemCheckNode(5));
+ leaveFlow.addNode(new ManagerApprovalNode());
+
+ leaveFlow = flowService.createFlow(leaveFlow);
+ }
+
+ @Nested
+ @DisplayName("流程创建和启动测试")
+ class FlowCreationTest {
+ @Test
+ @DisplayName("创建流程时节点列表为空应抛出异常")
+ void shouldThrowExceptionWhenNodesEmpty() {
+ Flow invalidFlow = new Flow();
+ invalidFlow.setName("无效流程");
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> flowService.createFlow(invalidFlow)
+ );
+
+ assertEquals("流程节点不能为空", exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("找不到流程定义时启动流程应抛出异常")
+ void shouldThrowExceptionWhenFlowNotFound() {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> flowService.startFlow("non-existent-id")
+ );
+
+ assertEquals("找不到对应的流程", exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("启动流程时应正确初始化状态")
+ void shouldInitializeStateWhenStartFlow() {
+ Flow flow = flowService.startFlow(leaveFlow.getId());
+
+ assertNotNull(flow.getId(), "流程ID不应为空");
+ assertEquals(FlowStatus.PENDING, flow.getStatus(), "初始状态应为PENDING");
+ assertEquals("请假申请", flow.getCurrentNode(), "应从第一个节点开始");
+ assertNotNull(flow.getCreateTime(), "创建时间不应为空");
+ assertNotNull(flow.getUpdateTime(), "更新时间不应为空");
+ }
+ }
+
+ @Nested
+ @DisplayName("系统节点审批流程测试")
+ class SystemNodeFlowTest {
+ @Test
+ @DisplayName("请假天数在限制内时应自动通过系统审核")
+ void shouldAutoApproveWhenLeaveDaysWithinLimit() {
+ LeaveRequestNode.setTestDays(3);
+ Flow flow = flowService.startFlow(leaveFlow.getId());
+
+ // 验证初始状态
+ assertEquals(FlowStatus.PENDING, flow.getStatus());
+ assertEquals("请假申请", flow.getCurrentNode());
+ assertEquals(3, flow.getContextVariables().get("days"));
+ assertEquals("年假", flow.getContextVariables().get("reason"));
+
+ // 提交请假申请
+ flow = flowService.approve(flow.getId());
+
+ // 验证系统审核通过
+ assertEquals(FlowStatus.PENDING, flow.getStatus());
+ assertEquals("经理审批", flow.getCurrentNode());
+ assertEquals("系统自动通过", flow.getContextVariables().get("systemComment"));
+
+ // 经理审批
+ flow = flowService.approve(flow.getId());
+
+ // 验证流程完成
+ assertEquals(FlowStatus.APPROVED, flow.getStatus());
+ assertEquals("同意", flow.getContextVariables().get("managerComment"));
+ }
+
+ @Test
+ @DisplayName("请假天数超出限制时应被系统自动拒绝")
+ void shouldAutoRejectWhenLeaveDaysExceedLimit() {
+ LeaveRequestNode.setTestDays(7);
+ Flow flow = flowService.startFlow(leaveFlow.getId());
+
+ // 验证初始状态
+ assertEquals(FlowStatus.PENDING, flow.getStatus());
+ assertEquals("请假申请", flow.getCurrentNode());
+ assertEquals(7, flow.getContextVariables().get("days"));
+
+ // 提交请假申请
+ flow = flowService.approve(flow.getId());
+
+ // 验证系统拒绝
+ assertEquals(FlowStatus.REJECTED, flow.getStatus());
+ assertEquals("请假天数(7)超过系统限制(5),需要额外审批",
+ flow.getContextVariables().get("systemComment"));
+ }
+ }
+
+ @Nested
+ @DisplayName("人工审批流程测试")
+ class ManualApprovalTest {
+ @Test
+ @DisplayName("经理应能直接拒绝请假申请")
+ void shouldAllowManagerToReject() {
+ LeaveRequestNode.setTestDays(3);
+ Flow flow = flowService.startFlow(leaveFlow.getId());
+
+ // 通过请假申请和系统审核
+ flow = flowService.approve(flow.getId());
+
+ // 经理拒绝
+ flow = flowService.reject(flow.getId());
+
+ assertEquals(FlowStatus.REJECTED, flow.getStatus());
+ assertEquals("经理审批", flow.getCurrentNode());
+ }
+
+ @Test
+ @DisplayName("已完成的流程不能再次审批")
+ void shouldNotAllowApproveCompletedFlow() {
+ LeaveRequestNode.setTestDays(3);
+ // 完成整个流程
+ Flow completedFlow = flowService.startFlow(leaveFlow.getId());
+ completedFlow = flowService.approve(completedFlow.getId());
+ final Flow finalFlow = flowService.approve(completedFlow.getId());
+
+ // 尝试再次审批
+ IllegalStateException exception = assertThrows(
+ IllegalStateException.class,
+ () -> flowService.approve(finalFlow.getId())
+ );
+
+ assertEquals("当前流程已经结束", exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("已拒绝的流程不能再次审批")
+ void shouldNotAllowApproveRejectedFlow() {
+ LeaveRequestNode.setTestDays(3);
+ // 拒绝流程
+ Flow rejectedFlow = flowService.startFlow(leaveFlow.getId());
+ final Flow finalFlow = flowService.reject(rejectedFlow.getId());
+
+ // 尝试继续审批
+ IllegalStateException exception = assertThrows(
+ IllegalStateException.class,
+ () -> flowService.approve(finalFlow.getId())
+ );
+
+ assertEquals("当前流程已经结束", exception.getMessage());
+ }
+ }
+
+ @Nested
+ @DisplayName("流程变量测试")
+ class FlowVariableTest {
+ @Test
+ @DisplayName("流程变量应在节点间正确传递")
+ void shouldPassVariablesBetweenNodes() {
+ LeaveRequestNode.setTestDays(3);
+ Flow flow = flowService.startFlow(leaveFlow.getId());
+
+ // 验证请假申请节点设置的变量
+ assertEquals(3, flow.getContextVariables().get("days"));
+ assertEquals("年假", flow.getContextVariables().get("reason"));
+
+ // 提交请假申请,验证系统审核节点的变量
+ flow = flowService.approve(flow.getId());
+ assertEquals("系统自动通过", flow.getContextVariables().get("systemComment"));
+ assertNotNull(flow.getContextVariables().get("systemCheckTime"));
+
+ // 经理审批,验证所有变量都被保留
+ flow = flowService.approve(flow.getId());
+ assertEquals(3, flow.getContextVariables().get("days"));
+ assertEquals("年假", flow.getContextVariables().get("reason"));
+ assertEquals("系统自动通过", flow.getContextVariables().get("systemComment"));
+ assertEquals("同意", flow.getContextVariables().get("managerComment"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveRequestNode.java b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveRequestNode.java
new file mode 100644
index 0000000..920be64
--- /dev/null
+++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveRequestNode.java
@@ -0,0 +1,34 @@
+package com.lanyuanxiaoyao.flowable.node;
+
+import com.lanyuanxiaoyao.flowable.model.FlowContext;
+import java.time.LocalDateTime;
+import lombok.Setter;
+
+public class LeaveRequestNode extends AbstractFlowNode {
+ // 用于测试的请假天数
+ @Setter
+ private static int testDays = 3;
+
+ public LeaveRequestNode() {
+ super("请假申请");
+ }
+
+ @Override
+ public void execute(FlowContext context) {
+ // 模拟设置请假信息,使用testDays而不是硬编码的值
+ context.setVariable("days", testDays);
+ context.setVariable("reason", "年假");
+ context.setVariable("requestTime", LocalDateTime.now());
+ System.out.println("执行请假申请节点:设置请假天数为" + testDays + "天,请假理由为年假");
+ }
+
+ @Override
+ public void onApprove(FlowContext context) {
+ System.out.println("请假申请提交成功");
+ }
+
+ @Override
+ public void onReject(FlowContext context) {
+ System.out.println("请假申请被撤销");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveSystemCheckNode.java b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveSystemCheckNode.java
new file mode 100644
index 0000000..829904c
--- /dev/null
+++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveSystemCheckNode.java
@@ -0,0 +1,60 @@
+package com.lanyuanxiaoyao.flowable.node;
+
+import com.lanyuanxiaoyao.flowable.model.FlowContext;
+import java.time.LocalDateTime;
+
+/**
+ * 请假系统自动审核节点
+ * 根据请假天数自动判断是否通过
+ */
+public class LeaveSystemCheckNode extends SystemFlowNode {
+ private final int maxDays; // 最大允许请假天数
+
+ public LeaveSystemCheckNode(int maxDays) {
+ super("系统审核");
+ this.maxDays = maxDays;
+ }
+
+ @Override
+ public boolean autoApprove(FlowContext context) {
+ Integer days = context.getVariable("days", Integer.class);
+ return days != null && days <= maxDays;
+ }
+
+ @Override
+ protected String getRejectionReason(FlowContext context) {
+ Integer days = context.getVariable("days", Integer.class);
+ return String.format("请假天数(%d)超过系统限制(%d),需要额外审批", days, maxDays);
+ }
+
+ @Override
+ public void onApprove(FlowContext context) {
+ Integer days = context.getVariable("days", Integer.class);
+ String reason = context.getVariable("reason", String.class);
+
+ System.out.println("执行系统审核节点:");
+ System.out.println("- 请假天数:" + days);
+ System.out.println("- 请假理由:" + reason);
+ System.out.println("- 系统限制:" + maxDays + "天");
+
+ context.setVariable("systemComment", "系统自动通过");
+ context.setVariable("systemCheckTime", LocalDateTime.now());
+ System.out.println("系统自动审核通过");
+ }
+
+ @Override
+ public void onReject(FlowContext context) {
+ Integer days = context.getVariable("days", Integer.class);
+ String reason = context.getVariable("reason", String.class);
+
+ System.out.println("执行系统审核节点:");
+ System.out.println("- 请假天数:" + days);
+ System.out.println("- 请假理由:" + reason);
+ System.out.println("- 系统限制:" + maxDays + "天");
+
+ String rejectionReason = getRejectionReason(context);
+ context.setVariable("systemComment", rejectionReason);
+ context.setVariable("systemCheckTime", LocalDateTime.now());
+ System.out.println("系统自动审核拒绝:" + rejectionReason);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/node/ManagerApprovalNode.java b/src/test/java/com/lanyuanxiaoyao/flowable/node/ManagerApprovalNode.java
new file mode 100644
index 0000000..9a83296
--- /dev/null
+++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/ManagerApprovalNode.java
@@ -0,0 +1,38 @@
+package com.lanyuanxiaoyao.flowable.node;
+
+import com.lanyuanxiaoyao.flowable.model.FlowContext;
+import java.time.LocalDateTime;
+
+public class ManagerApprovalNode extends AbstractFlowNode {
+ public ManagerApprovalNode() {
+ super("经理审批");
+ }
+
+ @Override
+ public void execute(FlowContext context) {
+ Integer days = context.getVariable("days", Integer.class);
+ String reason = context.getVariable("reason", String.class);
+ LocalDateTime requestTime = context.getVariable("requestTime", LocalDateTime.class);
+
+ System.out.println("执行经理审批节点:");
+ System.out.println("- 请假天数:" + days);
+ System.out.println("- 请假理由:" + reason);
+ System.out.println("- 申请时间:" + requestTime);
+ }
+
+ @Override
+ public void onApprove(FlowContext context) {
+ context.setVariable("approvalTime", LocalDateTime.now());
+ context.setVariable("managerComment", "同意");
+ System.out.println("经理已批准请假申请");
+ System.out.println("- 审批意见:同意");
+ }
+
+ @Override
+ public void onReject(FlowContext context) {
+ context.setVariable("approvalTime", LocalDateTime.now());
+ context.setVariable("managerComment", "请假天数过长,建议调整后重新提交");
+ System.out.println("经理已拒绝请假申请");
+ System.out.println("- 拒绝原因:请假天数过长,建议调整后重新提交");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java b/src/test/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java
new file mode 100644
index 0000000..2df6152
--- /dev/null
+++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java
@@ -0,0 +1,28 @@
+package com.lanyuanxiaoyao.flowable.node;
+
+import com.lanyuanxiaoyao.flowable.model.FlowContext;
+
+/**
+ * 简单流程节点
+ * 仅用于演示基本的审批流程
+ */
+public class SimpleFlowNode extends AbstractFlowNode {
+ public SimpleFlowNode(String nodeId) {
+ super(nodeId);
+ }
+
+ @Override
+ public void execute(FlowContext context) {
+ System.out.println("执行节点:" + getNodeId());
+ }
+
+ @Override
+ public void onApprove(FlowContext context) {
+ System.out.println("节点[" + getNodeId() + "]通过");
+ }
+
+ @Override
+ public void onReject(FlowContext context) {
+ System.out.println("节点[" + getNodeId() + "]拒绝");
+ }
+}
\ No newline at end of file