1
0

完成初始版本,实现基本的流程

This commit is contained in:
2024-12-25 12:37:43 +08:00
commit ee6a5c05c2
23 changed files with 1037 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
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.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;
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);
}
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());
}
}

View File

@@ -0,0 +1,30 @@
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 {
public LeaveRequestNode() {
super("请假申请");
}
@Override
public void execute(FlowContext context) {
// 模拟设置请假信息
context.setVariable("days", 3);
context.setVariable("reason", "年假");
context.setVariable("requestTime", LocalDateTime.now());
System.out.println("执行请假申请节点设置请假天数为3天请假理由为年假");
}
@Override
public void onApprove(FlowContext context) {
System.out.println("请假申请提交成功");
}
@Override
public void onReject(FlowContext context) {
System.out.println("请假申请被撤销");
}
}

View File

@@ -0,0 +1,39 @@
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("- 拒绝原因:请假天数过长,建议调整后重新提交");
}
}

View File

@@ -0,0 +1,15 @@
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());
}
}

View File

@@ -0,0 +1,57 @@
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.List;
/**
* 流程定义类
* 用于定义一个完整的审批流程,包含流程的基本信息和所有审批节点
*/
@Data
public class Flow {
/**
* 流程定义ID
*/
private String id;
/**
* 流程名称
*/
private String name;
/**
* 流程描述
*/
private String description;
/**
* 流程节点列表
* 按照列表顺序依次执行,每个节点都是一个可执行的审批操作
*/
private List<FlowNode> nodes;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 最后更新时间
*/
private LocalDateTime updateTime;
public Flow() {
this.nodes = new ArrayList<>();
}
/**
* 添加一个新的流程节点
* @param node 要添加的流程节点
*/
public void addNode(FlowNode node) {
nodes.add(node);
}
}

View File

@@ -0,0 +1,63 @@
package com.lanyuanxiaoyao.flowable.model;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 流程上下文类
* 用于在流程节点执行过程中传递和存储数据
*/
@Data
public class FlowContext {
/**
* 对应的流程定义ID
*/
private String flowId;
/**
* 对应的流程实例ID
*/
private String instanceId;
/**
* 上下文变量存储
* 用于在节点间传递数据
*/
private Map<String, Object> variables;
public FlowContext() {
this.variables = new HashMap<>();
}
/**
* 设置上下文变量
* @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 <T> 变量类型
* @return 转换为指定类型的变量值
*/
@SuppressWarnings("unchecked")
public <T> T getVariable(String key, Class<T> type) {
Object value = variables.get(key);
return value == null ? null : (T) value;
}
}

View File

@@ -0,0 +1,54 @@
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<String, Object> contextVariables;
public FlowInstance() {
this.contextVariables = new HashMap<>();
}
}

View File

@@ -0,0 +1,35 @@
package com.lanyuanxiaoyao.flowable.model;
/**
* 流程状态枚举
* 用于表示流程实例的当前状态
*/
public enum FlowStatus {
/**
* 进行中:流程正在执行中
*/
PENDING("进行中"),
/**
* 已通过:流程已经完成并通过
*/
APPROVED("已通过"),
/**
* 已拒绝:流程被拒绝
*/
REJECTED("已拒绝");
/**
* 状态的中文描述
*/
private final String description;
FlowStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@@ -0,0 +1,26 @@
package com.lanyuanxiaoyao.flowable.node;
import com.lanyuanxiaoyao.flowable.model.FlowContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public abstract class AbstractFlowNode implements FlowNode {
@Getter
private final String nodeId;
@Override
public void execute(FlowContext context) {
// 默认实现为空
}
@Override
public void onApprove(FlowContext context) {
// 默认实现为空
}
@Override
public void onReject(FlowContext context) {
// 默认实现为空
}
}

View File

@@ -0,0 +1,29 @@
package com.lanyuanxiaoyao.flowable.node;
import com.lanyuanxiaoyao.flowable.model.FlowContext;
/**
* 流程节点接口
* 定义节点的基本操作
*/
public interface FlowNode {
/**
* 获取节点ID
*/
String getNodeId();
/**
* 节点初始化时执行的操作
*/
void execute(FlowContext context);
/**
* 节点通过时执行的操作
*/
void onApprove(FlowContext context);
/**
* 节点拒绝时执行的操作
*/
void onReject(FlowContext context);
}

View File

@@ -0,0 +1,13 @@
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<FlowInstance> findAll();
List<FlowInstance> findByFlowId(String flowId);
void deleteById(String id);
FlowInstance update(FlowInstance instance);
}

View File

@@ -0,0 +1,12 @@
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<Flow> findAll();
void deleteById(String id);
Flow update(Flow flow);
}

View File

@@ -0,0 +1,53 @@
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<String, FlowInstance> 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<FlowInstance> findAll() {
return new ArrayList<>(storage.values());
}
@Override
public List<FlowInstance> 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;
}
}

View File

@@ -0,0 +1,45 @@
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<String, Flow> storage = 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 findById(String id) {
return storage.get(id);
}
@Override
public List<Flow> 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;
}
}

View File

@@ -0,0 +1,178 @@
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.node.FlowNode;
@RequiredArgsConstructor
public class FlowService {
private final FlowRepository flowRepository;
private final FlowInstanceRepository instanceRepository;
/**
* 创建新的流程定义
*/
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("找不到对应的流程定义");
}
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);
// 执行第一个节点
flow.getNodes().get(0).execute(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("当前流程已经结束");
}
Flow flow = flowRepository.findById(instance.getFlowId());
List<FlowNode> 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;
}
}
// 创建上下文并加载已有变量
FlowContext context = createContext(instance);
// 执行当前节点的通过操作
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);
}
// 保存上下文变量
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<FlowNode> 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());
}
}