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