diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java b/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java index 9266dfe..0bd67ec 100644 --- a/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java +++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java @@ -48,6 +48,7 @@ public class Flow { /** * 流程上下文变量 * 用于存储流程执行过程中的数据,实现节点间的数据传递 + * 这些数据会随着流程实例一起持久化 */ private Map contextVariables; @@ -65,6 +66,7 @@ public class Flow { this.nodes = new ArrayList<>(); this.contextVariables = new HashMap<>(); this.status = FlowStatus.PENDING; + this.currentNode = null; } /** @@ -74,9 +76,6 @@ public class Flow { */ public void addNode(FlowNode node) { nodes.add(node); - if (currentNode == null) { - currentNode = nodes.get(0).getNodeId(); - } } /** @@ -129,37 +128,22 @@ public class Flow { /** * 创建流程上下文 + * 从持久化的上下���变量中恢复数据 */ public FlowContext createContext() { FlowContext context = new FlowContext(); context.setFlowId(id); - context.getVariables().putAll(contextVariables); + // 使用新的 Map 避免直接修改存储的数据 + context.setVariables(new HashMap<>(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; + // 使用新的 Map 保存数据的副本 + this.contextVariables = new HashMap<>(context.getVariables()); } } \ 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 1e5b0e9..fe54ced 100644 --- a/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java @@ -4,16 +4,11 @@ import com.lanyuanxiaoyao.flowable.model.FlowContext; import lombok.Getter; import lombok.RequiredArgsConstructor; +@Getter @RequiredArgsConstructor public abstract class AbstractFlowNode implements FlowNode { - @Getter private final String nodeId; - @Override - public void execute(FlowContext context) { - // 默认实现为空 - } - @Override public void onApprove(FlowContext context) { // 默认实现为空 diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java index 7040854..0a2d0b7 100644 --- a/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java @@ -13,17 +13,12 @@ public interface FlowNode { String getNodeId(); /** - * 节点初始化时执行的操作 - */ - void execute(FlowContext context); - - /** - * 节点通过时执行的操作 + * 处理通过操作 */ void onApprove(FlowContext context); /** - * 节点拒绝时执行的操作 + * 处理拒绝操作 */ void onReject(FlowContext context); } \ No newline at end of file diff --git a/src/main/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java b/src/main/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java new file mode 100644 index 0000000..9d49d86 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.flowable.node; + +import com.lanyuanxiaoyao.flowable.model.FlowContext; + +/** + * 简单流程节点 + * 仅用于演示基本的审批流程 + */ +public class SimpleFlowNode extends AbstractFlowNode { + public SimpleFlowNode(String nodeId) { + super(nodeId); + } +} \ 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 ff13e86..cffeda7 100644 --- a/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/SystemFlowNode.java @@ -3,27 +3,34 @@ package com.lanyuanxiaoyao.flowable.node; import com.lanyuanxiaoyao.flowable.model.FlowContext; /** - * 系统自动审批节点 - * 根据预设规则自动执行审批操作 + * 系统节点基类 + * 提供自动审批功能 */ public abstract class SystemFlowNode extends AbstractFlowNode { - public SystemFlowNode(String nodeId) { - super(nodeId); - } + protected 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); + + @Override + public void onReject(FlowContext context) { + // 设置拒绝原因到上下文 + String rejectionReason = getRejectionReason(context); + context.setVariable("systemComment", rejectionReason); + } } \ 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 f847cd7..cbf21cd 100644 --- a/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java +++ b/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java @@ -7,6 +7,8 @@ import com.lanyuanxiaoyao.flowable.node.FlowNode; import com.lanyuanxiaoyao.flowable.node.SystemFlowNode; import com.lanyuanxiaoyao.flowable.repository.FlowRepository; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; import lombok.RequiredArgsConstructor; /** @@ -17,23 +19,40 @@ import lombok.RequiredArgsConstructor; public class FlowService { private final FlowRepository flowRepository; - /** - * 创建新的流程定义 - */ - public Flow createFlow(Flow flow) { - validateFlowNodes(flow); - initializeFlow(flow); - return flowRepository.save(flow); - } - /** * 启动一个新的流程实例 + * + * @throws IllegalStateException 如果流程已经启动或已经结束 */ public Flow startFlow(String flowId) { - Flow flowDefinition = findFlowById(flowId); - Flow instance = flowDefinition.createInstance(); - instance = flowRepository.save(instance); - return executeNode(instance); + Flow flow = findFlowById(flowId); + + // 检查流程状态 + if (!FlowStatus.PENDING.equals(flow.getStatus()) || + flow.getCurrentNode() != null) { // 使用当前节点是否为空来判断是否已启动 + throw new IllegalStateException("流程已经启动或已经结束"); + } + + // 设置初始节点 + if (!flow.getNodes().isEmpty()) { + flow.setCurrentNode(flow.getNodes().get(0).getNodeId()); + } + flow.setUpdateTime(LocalDateTime.now()); + + // 保存流程实例 + flow = flowRepository.update(flow); + + // 自动提交第一个节点 + FlowContext context = flow.createContext(); + flow.getCurrentNodeObject().onApprove(context); + flow = updateFlow(flow, context); + + // 如果第一个节点是系统节点,自动执行审批 + if (flow.getCurrentNodeObject() instanceof SystemFlowNode) { + return executeNode(flow); + } + + return flow; } /** @@ -51,7 +70,7 @@ public class FlowService { flow.setStatus(FlowStatus.APPROVED); return updateFlow(flow, context); } else { - flow = updateFlow(flow, context); // 先保存当前节点的状态 + flow = updateFlow(flow, context); // 先保存当前节点的���态 flow.moveToNextNode(); return executeNode(flow); // 执行下一个节点(可能是系统节点) } @@ -111,9 +130,6 @@ public class FlowService { FlowContext context = flow.createContext(); FlowNode currentNode = flow.getCurrentNodeObject(); - // 执行当前节点 - currentNode.execute(context); - // 如果是系统节点,自动执行审批 if (currentNode instanceof SystemFlowNode) { return handleSystemNode(flow, currentNode, context); @@ -138,7 +154,7 @@ public class FlowService { return updateFlow(flow, context); } } else { - // 自动��绝 + // 自动拒绝 systemNode.onReject(context); flow.setStatus(FlowStatus.REJECTED); return updateFlow(flow, context); diff --git a/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java b/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java index 53e901e..4f70997 100644 --- a/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java +++ b/src/test/java/com/lanyuanxiaoyao/flowable/FlowServiceTest.java @@ -13,201 +13,287 @@ 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.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @DisplayName("流程服务测试") class FlowServiceTest { - private FlowService flowService; - private Flow leaveFlow; + private FlowService flowService; + private Flow leaveFlow; + private Flow longLeaveFlow; // 新增长期请假流程 - @BeforeEach - void setUp() { - FlowRepository flowRepository = new MemoryFlowRepository(); - flowService = new FlowService(flowRepository); + @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); + // 创建标准请假流程(3天) + leaveFlow = new Flow(); + leaveFlow.setName("标准请假流程"); + leaveFlow.setDescription("3天以内的请假流程"); + leaveFlow.addNode(new LeaveRequestNode(3, "年假")); + leaveFlow.addNode(new LeaveSystemCheckNode(5)); + leaveFlow.addNode(new ManagerApprovalNode()); + leaveFlow = flowRepository.save(leaveFlow); + + // 创建长期请假流程(7天) + longLeaveFlow = new Flow(); + longLeaveFlow.setName("长期请假流程"); + longLeaveFlow.setDescription("7天的请假流程"); + longLeaveFlow.addNode(new LeaveRequestNode(7, "年假")); + longLeaveFlow.addNode(new LeaveSystemCheckNode(5)); + longLeaveFlow.addNode(new ManagerApprovalNode()); + longLeaveFlow = flowRepository.save(longLeaveFlow); + } + + @Nested + @DisplayName("流程启动测试") + class FlowStartTest { + @Test + @DisplayName("找不到流程时启动应抛出异常") + void shouldThrowExceptionWhenFlowNotFound() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> flowService.startFlow("non-existent-id") + ); + + assertEquals("找不到对应的流程", exception.getMessage()); } - @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(), "更新时间不应为空"); - } + @Test + @DisplayName("启动流程时应正确初始化状态") + void shouldInitializeStateWhenStartFlow() { + // 获取流程实例验证初始状态 + Flow flow = flowService.getFlow(leaveFlow.getId()); + + // 验证初始状态 + assertNotNull(flow.getId(), "流程ID不应为空"); + assertEquals(FlowStatus.PENDING, flow.getStatus(), "初始状态应为PENDING"); + assertTrue(flow.getContextVariables().isEmpty(), "上下文变量应为空"); + + // 启动流程 + flow = flowService.startFlow(leaveFlow.getId()); + + // 验证启动后状态 + assertEquals(FlowStatus.PENDING, flow.getStatus(), "启动后状态应为PENDING"); + assertEquals("请假申请", flow.getCurrentNode(), "应从第一个节点开始"); + assertNotNull(flow.getUpdateTime(), "更新时间不应为空"); + // 验证第一个节点执行后的变量 + assertEquals(3, flow.getContextVariables().get("days"), "应包含请假天数"); + assertEquals("年假", flow.getContextVariables().get("reason"), "应包含请假理由"); } - @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")); - } + @Test + @DisplayName("不能重复启动已经结束的流程") + void shouldNotStartCompletedFlow() { + // 先启动并完成一个流程 + Flow completedFlow = flowService.startFlow(leaveFlow.getId()); + completedFlow = flowService.approve(completedFlow.getId()); + final Flow finalFlow = flowService.approve(completedFlow.getId()); + + // 尝试重新启动 + IllegalStateException exception = assertThrows( + IllegalStateException.class, + () -> flowService.startFlow(finalFlow.getId()) + ); + + assertEquals("流程已经启动或已经结束", exception.getMessage()); } - @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()); - } + @Test + @DisplayName("不能重复启动已经拒绝的流程") + void shouldNotStartRejectedFlow() { + // 先启动并拒绝一个流程 + Flow rejectedFlow = flowService.startFlow(leaveFlow.getId()); + final Flow finalFlow = flowService.reject(rejectedFlow.getId()); + + // 尝试重新启动 + IllegalStateException exception = assertThrows( + IllegalStateException.class, + () -> flowService.startFlow(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")); - } + @Test + @DisplayName("不能重复启动正在进行的流程") + void shouldNotStartRunningFlow() { + // 先启动流程 + final Flow runningFlow = flowService.startFlow(leaveFlow.getId()); + + // 尝试重新启动 + IllegalStateException exception = assertThrows( + IllegalStateException.class, + () -> flowService.startFlow(runningFlow.getId()) + ); + + assertEquals("流程已经启动或已经结束", exception.getMessage()); } + } + + @Nested + @DisplayName("系统节点审批流程测试") + class SystemNodeFlowTest { + @Test + @DisplayName("请假天数在限制内时应自动通过系统审核") + void shouldAutoApproveWhenLeaveDaysWithinLimit() { + 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() { + Flow flow = flowService.startFlow(longLeaveFlow.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() { + 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() { + 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() { + 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() { + 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")); + + // 经理审批,验证所有变量都被保留 + 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")); + } + + @Test + @DisplayName("流程变量应该在重新获取流程时保持不变") + void shouldPersistVariablesWhenReloadFlow() { + Flow flow = flowService.startFlow(leaveFlow.getId()); + + // 提交请假申请 + flow = flowService.approve(flow.getId()); + String flowId = flow.getId(); + + // 重新获取流程实例 + Flow reloadedFlow = flowService.getFlow(flowId); + + // 验证上下文变量被正确保存 + assertEquals(3, reloadedFlow.getContextVariables().get("days"), "请假天数应被保存"); + assertEquals("年假", reloadedFlow.getContextVariables().get("reason"), "请假理由应被保存"); + assertEquals("系统自动通过", reloadedFlow.getContextVariables().get("systemComment"), "系统审核结果应被保存"); + } + + @Test + @DisplayName("流程变量应该在流程结束后仍然保持") + void shouldKeepVariablesAfterFlowCompleted() { + Flow flow = flowService.startFlow(leaveFlow.getId()); + + // 完成整个流程 + flow = flowService.approve(flow.getId()); + flow = flowService.approve(flow.getId()); + String flowId = flow.getId(); + + // 重新获取已完成的流程 + Flow completedFlow = flowService.getFlow(flowId); + + // 验证所有变量都被保存 + assertEquals(FlowStatus.APPROVED, completedFlow.getStatus(), "流程状态应为已通过"); + assertEquals(3, completedFlow.getContextVariables().get("days"), "请假天数应被保存"); + assertEquals("年假", completedFlow.getContextVariables().get("reason"), "请假理由应被保存"); + assertEquals("系统自动通过", completedFlow.getContextVariables().get("systemComment"), "系统审核结果应被保存"); + assertEquals("同意", completedFlow.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 index 920be64..6ffadf1 100644 --- a/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveRequestNode.java +++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveRequestNode.java @@ -1,34 +1,31 @@ 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; + private final int days; + private final String reason; - public LeaveRequestNode() { - super("请假申请"); - } + public LeaveRequestNode(int days, String reason) { + super("请假申请"); + this.days = days; + this.reason = reason; + } - @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) { + // 设置请假信息到上下文 + context.setVariable("days", days); + context.setVariable("reason", reason); + context.setVariable("submitTime", System.currentTimeMillis()); + } - @Override - public void onApprove(FlowContext context) { - System.out.println("请假申请提交成功"); - } - - @Override - public void onReject(FlowContext context) { - System.out.println("请假申请被撤销"); - } + @Override + public void onReject(FlowContext context) { + context.setVariable("rejectReason", "申请已撤销"); + } } \ 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 index 829904c..1a9515e 100644 --- a/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveSystemCheckNode.java +++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/LeaveSystemCheckNode.java @@ -1,60 +1,34 @@ package com.lanyuanxiaoyao.flowable.node; import com.lanyuanxiaoyao.flowable.model.FlowContext; -import java.time.LocalDateTime; /** - * 请假系统自动审核节点 - * 根据请假天数自动判断是否通过 + * 系统自动审核节点 + * 测试用例中的示例节点,用于演示系统节点的实现 */ public class LeaveSystemCheckNode extends SystemFlowNode { - private final int maxDays; // 最大允许请假天数 + private final int maxDays; - public LeaveSystemCheckNode(int maxDays) { - super("系统审核"); - this.maxDays = 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 + public boolean autoApprove(FlowContext context) { + int days = (int) context.getVariables().get("days"); + return 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) { + context.setVariable("systemComment", "系统自动通过"); + context.setVariable("systemCheckTime", System.currentTimeMillis()); + } - @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); - } + @Override + protected String getRejectionReason(FlowContext context) { + int days = (int) context.getVariables().get("days"); + return String.format("请假天数(%d)超过系统限制(%d),需要额外审批", days, maxDays); + } } \ 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 index 9a83296..0a741b9 100644 --- a/src/test/java/com/lanyuanxiaoyao/flowable/node/ManagerApprovalNode.java +++ b/src/test/java/com/lanyuanxiaoyao/flowable/node/ManagerApprovalNode.java @@ -1,38 +1,25 @@ package com.lanyuanxiaoyao.flowable.node; import com.lanyuanxiaoyao.flowable.model.FlowContext; -import java.time.LocalDateTime; +/** + * 经理审批节点 + * 测试用例中的示例节点,用于演示人工审批节点的实现 + */ public class ManagerApprovalNode extends AbstractFlowNode { - public ManagerApprovalNode() { - super("经理审批"); - } + 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); + @Override + public void onApprove(FlowContext context) { + context.setVariable("managerComment", "同意"); + context.setVariable("approveTime", System.currentTimeMillis()); + } - 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("- 拒绝原因:请假天数过长,建议调整后重新提交"); - } + @Override + public void onReject(FlowContext context) { + context.setVariable("managerComment", "不同意"); + context.setVariable("rejectTime", System.currentTimeMillis()); + } } \ 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 deleted file mode 100644 index 2df6152..0000000 --- a/src/test/java/com/lanyuanxiaoyao/flowable/node/SimpleFlowNode.java +++ /dev/null @@ -1,28 +0,0 @@ -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