重构流程引擎核心逻辑
This commit is contained in:
@@ -48,6 +48,7 @@ public class Flow {
|
|||||||
/**
|
/**
|
||||||
* 流程上下文变量
|
* 流程上下文变量
|
||||||
* 用于存储流程执行过程中的数据,实现节点间的数据传递
|
* 用于存储流程执行过程中的数据,实现节点间的数据传递
|
||||||
|
* 这些数据会随着流程实例一起持久化
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> contextVariables;
|
private Map<String, Object> contextVariables;
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ public class Flow {
|
|||||||
this.nodes = new ArrayList<>();
|
this.nodes = new ArrayList<>();
|
||||||
this.contextVariables = new HashMap<>();
|
this.contextVariables = new HashMap<>();
|
||||||
this.status = FlowStatus.PENDING;
|
this.status = FlowStatus.PENDING;
|
||||||
|
this.currentNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,9 +76,6 @@ public class Flow {
|
|||||||
*/
|
*/
|
||||||
public void addNode(FlowNode node) {
|
public void addNode(FlowNode node) {
|
||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
if (currentNode == null) {
|
|
||||||
currentNode = nodes.get(0).getNodeId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,37 +128,22 @@ public class Flow {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建流程上下文
|
* 创建流程上下文
|
||||||
|
* 从持久化的上下<E4B88A><E4B88B><EFBFBD>变量中恢复数据
|
||||||
*/
|
*/
|
||||||
public FlowContext createContext() {
|
public FlowContext createContext() {
|
||||||
FlowContext context = new FlowContext();
|
FlowContext context = new FlowContext();
|
||||||
context.setFlowId(id);
|
context.setFlowId(id);
|
||||||
context.getVariables().putAll(contextVariables);
|
// 使用新的 Map 避免直接修改存储的数据
|
||||||
|
context.setVariables(new HashMap<>(contextVariables));
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存上下文变量
|
* 保存上下文变量
|
||||||
|
* 将变量持久化到流程实例中
|
||||||
*/
|
*/
|
||||||
public void saveContext(FlowContext context) {
|
public void saveContext(FlowContext context) {
|
||||||
contextVariables.clear();
|
// 使用新的 Map 保存数据的副本
|
||||||
contextVariables.putAll(context.getVariables());
|
this.contextVariables = new HashMap<>(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,16 +4,11 @@ import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public abstract class AbstractFlowNode implements FlowNode {
|
public abstract class AbstractFlowNode implements FlowNode {
|
||||||
@Getter
|
|
||||||
private final String nodeId;
|
private final String nodeId;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute(FlowContext context) {
|
|
||||||
// 默认实现为空
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApprove(FlowContext context) {
|
public void onApprove(FlowContext context) {
|
||||||
// 默认实现为空
|
// 默认实现为空
|
||||||
|
|||||||
@@ -13,17 +13,12 @@ public interface FlowNode {
|
|||||||
String getNodeId();
|
String getNodeId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点初始化时执行的操作
|
* 处理通过操作
|
||||||
*/
|
|
||||||
void execute(FlowContext context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点通过时执行的操作
|
|
||||||
*/
|
*/
|
||||||
void onApprove(FlowContext context);
|
void onApprove(FlowContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点拒绝时执行的操作
|
* 处理拒绝操作
|
||||||
*/
|
*/
|
||||||
void onReject(FlowContext context);
|
void onReject(FlowContext context);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,27 +3,34 @@ package com.lanyuanxiaoyao.flowable.node;
|
|||||||
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统自动审批节点
|
* 系统节点基类
|
||||||
* 根据预设规则自动执行审批操作
|
* 提供自动审批功能
|
||||||
*/
|
*/
|
||||||
public abstract class SystemFlowNode extends AbstractFlowNode {
|
public abstract class SystemFlowNode extends AbstractFlowNode {
|
||||||
public SystemFlowNode(String nodeId) {
|
protected SystemFlowNode(String nodeId) {
|
||||||
super(nodeId);
|
super(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统自动判断是否通过
|
* 判断是否自动通过
|
||||||
*
|
*
|
||||||
* @param context 流程上下文
|
* @param context 流程上下文
|
||||||
* @return true表示通过,false表示拒绝
|
* @return true表示自动通过,false表示自动拒绝
|
||||||
*/
|
*/
|
||||||
public abstract boolean autoApprove(FlowContext context);
|
public abstract boolean autoApprove(FlowContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取拒绝原因
|
* 获取拒绝原因
|
||||||
*
|
*
|
||||||
* @param context 流程上下文
|
* @param context 流程上下文
|
||||||
* @return 拒绝原因
|
* @return 拒绝原因
|
||||||
*/
|
*/
|
||||||
protected abstract String getRejectionReason(FlowContext context);
|
protected abstract String getRejectionReason(FlowContext context);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReject(FlowContext context) {
|
||||||
|
// 设置拒绝原因到上下文
|
||||||
|
String rejectionReason = getRejectionReason(context);
|
||||||
|
context.setVariable("systemComment", rejectionReason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ import com.lanyuanxiaoyao.flowable.node.FlowNode;
|
|||||||
import com.lanyuanxiaoyao.flowable.node.SystemFlowNode;
|
import com.lanyuanxiaoyao.flowable.node.SystemFlowNode;
|
||||||
import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
|
import com.lanyuanxiaoyao.flowable.repository.FlowRepository;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,23 +19,40 @@ import lombok.RequiredArgsConstructor;
|
|||||||
public class FlowService {
|
public class FlowService {
|
||||||
private final FlowRepository flowRepository;
|
private final FlowRepository flowRepository;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建新的流程定义
|
|
||||||
*/
|
|
||||||
public Flow createFlow(Flow flow) {
|
|
||||||
validateFlowNodes(flow);
|
|
||||||
initializeFlow(flow);
|
|
||||||
return flowRepository.save(flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动一个新的流程实例
|
* 启动一个新的流程实例
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException 如果流程已经启动或已经结束
|
||||||
*/
|
*/
|
||||||
public Flow startFlow(String flowId) {
|
public Flow startFlow(String flowId) {
|
||||||
Flow flowDefinition = findFlowById(flowId);
|
Flow flow = findFlowById(flowId);
|
||||||
Flow instance = flowDefinition.createInstance();
|
|
||||||
instance = flowRepository.save(instance);
|
// 检查流程状态
|
||||||
return executeNode(instance);
|
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);
|
flow.setStatus(FlowStatus.APPROVED);
|
||||||
return updateFlow(flow, context);
|
return updateFlow(flow, context);
|
||||||
} else {
|
} else {
|
||||||
flow = updateFlow(flow, context); // 先保存当前节点的状态
|
flow = updateFlow(flow, context); // 先保存当前节点的<EFBFBD><EFBFBD><EFBFBD>态
|
||||||
flow.moveToNextNode();
|
flow.moveToNextNode();
|
||||||
return executeNode(flow); // 执行下一个节点(可能是系统节点)
|
return executeNode(flow); // 执行下一个节点(可能是系统节点)
|
||||||
}
|
}
|
||||||
@@ -111,9 +130,6 @@ public class FlowService {
|
|||||||
FlowContext context = flow.createContext();
|
FlowContext context = flow.createContext();
|
||||||
FlowNode currentNode = flow.getCurrentNodeObject();
|
FlowNode currentNode = flow.getCurrentNodeObject();
|
||||||
|
|
||||||
// 执行当前节点
|
|
||||||
currentNode.execute(context);
|
|
||||||
|
|
||||||
// 如果是系统节点,自动执行审批
|
// 如果是系统节点,自动执行审批
|
||||||
if (currentNode instanceof SystemFlowNode) {
|
if (currentNode instanceof SystemFlowNode) {
|
||||||
return handleSystemNode(flow, currentNode, context);
|
return handleSystemNode(flow, currentNode, context);
|
||||||
@@ -138,7 +154,7 @@ public class FlowService {
|
|||||||
return updateFlow(flow, context);
|
return updateFlow(flow, context);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 自动<EFBFBD><EFBFBD>绝
|
// 自动拒绝
|
||||||
systemNode.onReject(context);
|
systemNode.onReject(context);
|
||||||
flow.setStatus(FlowStatus.REJECTED);
|
flow.setStatus(FlowStatus.REJECTED);
|
||||||
return updateFlow(flow, context);
|
return updateFlow(flow, context);
|
||||||
|
|||||||
@@ -13,201 +13,287 @@ import org.junit.jupiter.api.DisplayName;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
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("流程服务测试")
|
@DisplayName("流程服务测试")
|
||||||
class FlowServiceTest {
|
class FlowServiceTest {
|
||||||
private FlowService flowService;
|
private FlowService flowService;
|
||||||
private Flow leaveFlow;
|
private Flow leaveFlow;
|
||||||
|
private Flow longLeaveFlow; // 新增长期请假流程
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
FlowRepository flowRepository = new MemoryFlowRepository();
|
// 初始化仓储<E4BB93><E582A8><EFBFBD>务
|
||||||
flowService = new FlowService(flowRepository);
|
FlowRepository flowRepository = new MemoryFlowRepository();
|
||||||
|
flowService = new FlowService(flowRepository);
|
||||||
|
|
||||||
// 创建请假流程定义
|
// 创建标准请假流程(3天)
|
||||||
leaveFlow = new Flow();
|
leaveFlow = new Flow();
|
||||||
leaveFlow.setName("带系统审核的请假流程");
|
leaveFlow.setName("标准请假流程");
|
||||||
leaveFlow.setDescription("包含系统自动审核的请假流程示例");
|
leaveFlow.setDescription("3天以内的请假流程");
|
||||||
leaveFlow.addNode(new LeaveRequestNode());
|
leaveFlow.addNode(new LeaveRequestNode(3, "年假"));
|
||||||
leaveFlow.addNode(new LeaveSystemCheckNode(5));
|
leaveFlow.addNode(new LeaveSystemCheckNode(5));
|
||||||
leaveFlow.addNode(new ManagerApprovalNode());
|
leaveFlow.addNode(new ManagerApprovalNode());
|
||||||
|
leaveFlow = flowRepository.save(leaveFlow);
|
||||||
leaveFlow = flowService.createFlow(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
|
@Test
|
||||||
@DisplayName("流程创建和启动测试")
|
@DisplayName("启动流程时应正确初始化状态")
|
||||||
class FlowCreationTest {
|
void shouldInitializeStateWhenStartFlow() {
|
||||||
@Test
|
// 获取流程实例验证初始状态
|
||||||
@DisplayName("创建流程时节点列表为空应抛出异常")
|
Flow flow = flowService.getFlow(leaveFlow.getId());
|
||||||
void shouldThrowExceptionWhenNodesEmpty() {
|
|
||||||
Flow invalidFlow = new Flow();
|
// 验证初始状态
|
||||||
invalidFlow.setName("无效流程");
|
assertNotNull(flow.getId(), "流程ID不应为空");
|
||||||
|
assertEquals(FlowStatus.PENDING, flow.getStatus(), "初始状态应为PENDING");
|
||||||
IllegalArgumentException exception = assertThrows(
|
assertTrue(flow.getContextVariables().isEmpty(), "上下文变量应为空");
|
||||||
IllegalArgumentException.class,
|
|
||||||
() -> flowService.createFlow(invalidFlow)
|
// 启动流程
|
||||||
);
|
flow = flowService.startFlow(leaveFlow.getId());
|
||||||
|
|
||||||
assertEquals("流程节点不能为空", exception.getMessage());
|
// 验证启动后状态
|
||||||
}
|
assertEquals(FlowStatus.PENDING, flow.getStatus(), "启动后状态应为PENDING");
|
||||||
|
assertEquals("请假申请", flow.getCurrentNode(), "应从第一个节点开始");
|
||||||
@Test
|
assertNotNull(flow.getUpdateTime(), "更新时间不应为空");
|
||||||
@DisplayName("找不到流程定义时启动流程应抛出异常")
|
// 验证第一个节点执行后的变量
|
||||||
void shouldThrowExceptionWhenFlowNotFound() {
|
assertEquals(3, flow.getContextVariables().get("days"), "应包含请假天数");
|
||||||
IllegalArgumentException exception = assertThrows(
|
assertEquals("年假", flow.getContextVariables().get("reason"), "应包含请假理由");
|
||||||
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
|
@Test
|
||||||
@DisplayName("系统节点审批流程测试")
|
@DisplayName("不能重复启动已经结束的流程")
|
||||||
class SystemNodeFlowTest {
|
void shouldNotStartCompletedFlow() {
|
||||||
@Test
|
// 先启动并完成一个流程
|
||||||
@DisplayName("请假天数在限制内时应自动通过系统审核")
|
Flow completedFlow = flowService.startFlow(leaveFlow.getId());
|
||||||
void shouldAutoApproveWhenLeaveDaysWithinLimit() {
|
completedFlow = flowService.approve(completedFlow.getId());
|
||||||
LeaveRequestNode.setTestDays(3);
|
final Flow finalFlow = flowService.approve(completedFlow.getId());
|
||||||
Flow flow = flowService.startFlow(leaveFlow.getId());
|
|
||||||
|
// 尝试重新启动
|
||||||
// 验证初始状态
|
IllegalStateException exception = assertThrows(
|
||||||
assertEquals(FlowStatus.PENDING, flow.getStatus());
|
IllegalStateException.class,
|
||||||
assertEquals("请假申请", flow.getCurrentNode());
|
() -> flowService.startFlow(finalFlow.getId())
|
||||||
assertEquals(3, flow.getContextVariables().get("days"));
|
);
|
||||||
assertEquals("年假", flow.getContextVariables().get("reason"));
|
|
||||||
|
assertEquals("流程已经启动或已经结束", exception.getMessage());
|
||||||
// 提交请假申请
|
|
||||||
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
|
@Test
|
||||||
@DisplayName("人工审批流程测试")
|
@DisplayName("不能重复启动已经拒绝的流程")
|
||||||
class ManualApprovalTest {
|
void shouldNotStartRejectedFlow() {
|
||||||
@Test
|
// 先启动并拒绝一个流程
|
||||||
@DisplayName("经理应能直接拒绝请假申请")
|
Flow rejectedFlow = flowService.startFlow(leaveFlow.getId());
|
||||||
void shouldAllowManagerToReject() {
|
final Flow finalFlow = flowService.reject(rejectedFlow.getId());
|
||||||
LeaveRequestNode.setTestDays(3);
|
|
||||||
Flow flow = flowService.startFlow(leaveFlow.getId());
|
// 尝试重新启动
|
||||||
|
IllegalStateException exception = assertThrows(
|
||||||
// 通过请假申请和系统审核
|
IllegalStateException.class,
|
||||||
flow = flowService.approve(flow.getId());
|
() -> flowService.startFlow(finalFlow.getId())
|
||||||
|
);
|
||||||
// 经理拒绝
|
|
||||||
flow = flowService.reject(flow.getId());
|
assertEquals("流程已经启动或已经结束", exception.getMessage());
|
||||||
|
|
||||||
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
|
@Test
|
||||||
@DisplayName("流程变量测试")
|
@DisplayName("不能重复启动正在进行的流程")
|
||||||
class FlowVariableTest {
|
void shouldNotStartRunningFlow() {
|
||||||
@Test
|
// 先启动流程
|
||||||
@DisplayName("流程变量应在节点间正确传递")
|
final Flow runningFlow = flowService.startFlow(leaveFlow.getId());
|
||||||
void shouldPassVariablesBetweenNodes() {
|
|
||||||
LeaveRequestNode.setTestDays(3);
|
// 尝试重新启动
|
||||||
Flow flow = flowService.startFlow(leaveFlow.getId());
|
IllegalStateException exception = assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
// 验证请假申请节点设置的变量
|
() -> flowService.startFlow(runningFlow.getId())
|
||||||
assertEquals(3, flow.getContextVariables().get("days"));
|
);
|
||||||
assertEquals("年假", flow.getContextVariables().get("reason"));
|
|
||||||
|
assertEquals("流程已经启动或已经结束", exception.getMessage());
|
||||||
// 提交请假申请,验证系统审核节点的变量
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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"), "经理审批结果应被保存");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,31 @@
|
|||||||
package com.lanyuanxiaoyao.flowable.node;
|
package com.lanyuanxiaoyao.flowable.node;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请假申请节点
|
||||||
|
* 测试用例中的示例节点,用于演示流程节点的实现
|
||||||
|
*/
|
||||||
public class LeaveRequestNode extends AbstractFlowNode {
|
public class LeaveRequestNode extends AbstractFlowNode {
|
||||||
// 用于测试的请假天数
|
private final int days;
|
||||||
@Setter
|
private final String reason;
|
||||||
private static int testDays = 3;
|
|
||||||
|
|
||||||
public LeaveRequestNode() {
|
public LeaveRequestNode(int days, String reason) {
|
||||||
super("请假申请");
|
super("请假申请");
|
||||||
}
|
this.days = days;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(FlowContext context) {
|
public void onApprove(FlowContext context) {
|
||||||
// 模拟设置请假信息,使用testDays而不是硬编码的值
|
// 设置请假信息到上下文
|
||||||
context.setVariable("days", testDays);
|
context.setVariable("days", days);
|
||||||
context.setVariable("reason", "年假");
|
context.setVariable("reason", reason);
|
||||||
context.setVariable("requestTime", LocalDateTime.now());
|
context.setVariable("submitTime", System.currentTimeMillis());
|
||||||
System.out.println("执行请假申请节点:设置请假天数为" + testDays + "天,请假理由为年假");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApprove(FlowContext context) {
|
public void onReject(FlowContext context) {
|
||||||
System.out.println("请假申请提交成功");
|
context.setVariable("rejectReason", "申请已撤销");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReject(FlowContext context) {
|
|
||||||
System.out.println("请假申请被撤销");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,34 @@
|
|||||||
package com.lanyuanxiaoyao.flowable.node;
|
package com.lanyuanxiaoyao.flowable.node;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请假系统自动审核节点
|
* 系统自动审核节点
|
||||||
* 根据请假天数自动判断是否通过
|
* 测试用例中的示例节点,用于演示系统节点的实现
|
||||||
*/
|
*/
|
||||||
public class LeaveSystemCheckNode extends SystemFlowNode {
|
public class LeaveSystemCheckNode extends SystemFlowNode {
|
||||||
private final int maxDays; // 最大允许请假天数
|
private final int maxDays;
|
||||||
|
|
||||||
public LeaveSystemCheckNode(int maxDays) {
|
public LeaveSystemCheckNode(int maxDays) {
|
||||||
super("系统审核");
|
super("系统审核");
|
||||||
this.maxDays = maxDays;
|
this.maxDays = maxDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean autoApprove(FlowContext context) {
|
public boolean autoApprove(FlowContext context) {
|
||||||
Integer days = context.getVariable("days", Integer.class);
|
int days = (int) context.getVariables().get("days");
|
||||||
return days != null && days <= maxDays;
|
return days <= maxDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRejectionReason(FlowContext context) {
|
public void onApprove(FlowContext context) {
|
||||||
Integer days = context.getVariable("days", Integer.class);
|
context.setVariable("systemComment", "系统自动通过");
|
||||||
return String.format("请假天数(%d)超过系统限制(%d),需要额外审批", days, maxDays);
|
context.setVariable("systemCheckTime", System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApprove(FlowContext context) {
|
protected String getRejectionReason(FlowContext context) {
|
||||||
Integer days = context.getVariable("days", Integer.class);
|
int days = (int) context.getVariables().get("days");
|
||||||
String reason = context.getVariable("reason", String.class);
|
return String.format("请假天数(%d)超过系统限制(%d),需要额外审批", days, maxDays);
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,25 @@
|
|||||||
package com.lanyuanxiaoyao.flowable.node;
|
package com.lanyuanxiaoyao.flowable.node;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
import com.lanyuanxiaoyao.flowable.model.FlowContext;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经理审批节点
|
||||||
|
* 测试用例中的示例节点,用于演示人工审批节点的实现
|
||||||
|
*/
|
||||||
public class ManagerApprovalNode extends AbstractFlowNode {
|
public class ManagerApprovalNode extends AbstractFlowNode {
|
||||||
public ManagerApprovalNode() {
|
public ManagerApprovalNode() {
|
||||||
super("经理审批");
|
super("经理审批");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(FlowContext context) {
|
public void onApprove(FlowContext context) {
|
||||||
Integer days = context.getVariable("days", Integer.class);
|
context.setVariable("managerComment", "同意");
|
||||||
String reason = context.getVariable("reason", String.class);
|
context.setVariable("approveTime", System.currentTimeMillis());
|
||||||
LocalDateTime requestTime = context.getVariable("requestTime", LocalDateTime.class);
|
}
|
||||||
|
|
||||||
System.out.println("执行经理审批节点:");
|
@Override
|
||||||
System.out.println("- 请假天数:" + days);
|
public void onReject(FlowContext context) {
|
||||||
System.out.println("- 请假理由:" + reason);
|
context.setVariable("managerComment", "不同意");
|
||||||
System.out.println("- 申请时间:" + requestTime);
|
context.setVariable("rejectTime", System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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("- 拒绝原因:请假天数过长,建议调整后重新提交");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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() + "]拒绝");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user