commit ee6a5c05c2a1c71c4370c1041f1af8fc7330e728 Author: lanyuanxiaoyao Date: Wed Dec 25 12:37:43 2024 +0800 完成初始版本,实现基本的流程 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3391a6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,135 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. + .idea/artifacts + .idea/compiler.xml + .idea/jarRepositories.xml + .idea/modules.xml + .idea/*.iml + .idea/modules + *.iml + *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..afa4cd3 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Zeppelin 忽略的文件 +/ZeppelinRemoteNotebooks/ diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml new file mode 100644 index 0000000..5df016c --- /dev/null +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..55f8480 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6774fba --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..093b166 --- /dev/null +++ b/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.lanyuanxiaoyao + flowable + 1.0-SNAPSHOT + + + 8 + 8 + UTF-8 + + + + + org.projectlombok + lombok + 1.18.34 + + + + \ 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 new file mode 100644 index 0000000..43bc4f7 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/example/FlowExample.java @@ -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()); + } +} \ 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 new file mode 100644 index 0000000..dab4fd3 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/LeaveRequestNode.java @@ -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("请假申请被撤销"); + } +} \ 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 new file mode 100644 index 0000000..27aa3e4 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/ManagerApprovalNode.java @@ -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("- 拒绝原因:请假天数过长,建议调整后重新提交"); + } +} \ 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 new file mode 100644 index 0000000..4901ac6 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/example/node/SimpleFlowNode.java @@ -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()); + } +} \ 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 new file mode 100644 index 0000000..205b2c8 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/Flow.java @@ -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 nodes; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 最后更新时间 + */ + private LocalDateTime updateTime; + + public Flow() { + this.nodes = new ArrayList<>(); + } + + /** + * 添加一个新的流程节点 + * @param node 要添加的流程节点 + */ + public void addNode(FlowNode node) { + nodes.add(node); + } +} \ 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 new file mode 100644 index 0000000..daa0d54 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowContext.java @@ -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 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 变量类型 + * @return 转换为指定类型的变量值 + */ + @SuppressWarnings("unchecked") + public T getVariable(String key, Class type) { + Object value = variables.get(key); + return value == null ? null : (T) 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 new file mode 100644 index 0000000..2067b6f --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowInstance.java @@ -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 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 new file mode 100644 index 0000000..b245afa --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/model/FlowStatus.java @@ -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; + } +} \ 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 new file mode 100644 index 0000000..cce1d06 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/AbstractFlowNode.java @@ -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) { + // 默认实现为空 + } +} \ 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 new file mode 100644 index 0000000..cf76ad2 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/node/FlowNode.java @@ -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); +} \ 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 new file mode 100644 index 0000000..f91d674 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowInstanceRepository.java @@ -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 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 new file mode 100644 index 0000000..57400f0 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/FlowRepository.java @@ -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 findAll(); + void deleteById(String id); + Flow update(Flow flow); +} \ 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 new file mode 100644 index 0000000..f6ed59a --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowInstanceRepository.java @@ -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 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 new file mode 100644 index 0000000..5b13fc1 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/repository/memory/MemoryFlowRepository.java @@ -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 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 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; + } +} \ 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 new file mode 100644 index 0000000..8d4bad7 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/flowable/service/FlowService.java @@ -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 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 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()); + } +} \ No newline at end of file