From d54772815bc34efc76112b5c22def86d3f45faa8 Mon Sep 17 00:00:00 2001
From: 疯狂的狮子Li <15040126243@163.com>
Date: 星期二, 05 三月 2024 22:57:39 +0800
Subject: [PATCH] !491 合并flowable工作流功能 * update 优化 表字段映射于数据库保持一致 * remove 删除无用代码 * remove 删除无用代码 * fix 修复 实体类未实现序列化接口问题 * update 优化 表字段映射于数据库保持一致 * update 优化 统一sql名称 * fix 修复 接口名称编写错误 * merge dev * update 调整sql 添加抄送查询 * update 调整菜单 * update 调整sql脚本 * update 调整任务查询 添加抄送 * add 抄送任务 * remove 删除错误代码 * remove 删除无用代码 * update 调整作废,撤销等校验 * fix 修复 流程作废异常问题 * update 优化 flowable 配置到主yml文件 * update 调整 数据排序规则 * fix 修复 数据库无法自动执行建表sql问题 * update 优化 工作流id生成器保持全局统一 * add 添加附件任务查询 * add 添加审批附件上传 * update 调整bpmn文件修复驳回失败问题 * update 调整会签类型转换异常 * add 添加获取运行中流程信息,流程扩展信息,补充注解,删除无用代码 * update 调整流程转换,流程启动,上传新bpmn文件 * update 调整方法 * update 调整模型修改 * fix 修复 user与dept xml 编写错误 * remove 移除原生ui接口,增加新ui接口 * update 优化 下拉选接口数据权限 * update 优化 删除观测用日志记录 * reset 还原修复命名 * update 修复命名 * add 新增 用户、部门、角色、岗位 下拉选接口与代码实现优化 * update 调整任务办理异步时流程状态错误问题 * add 工作流用户查询 * remove 删除无用注释 添加非空校验 * update 优化获取审批记录 * update 调整事件办理 * update 调整工作流选人接口 * Merge branch 'dev' into future/flowable * update 办理调整执行顺序 * update 调整流程办理优化撤销,驳回,草稿等动作 * fix 修复子流程中设置发起人变量错误问题 * Merge branch 'dev' into future/flowable * update 调整流程执行非空校验,调整任务节点执行 * update 调整注释 * add 添加自定义任务监听策略 * !469 update-完善对模型key校验逻辑 * update-修改常量命名 * update-完善对模型key校验逻辑 * add 添加sql脚本 * Merge branch 'dev' into future/flowable * Merge branch 'dev' into future/flowable * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * add 添加SQLserve脚本 * add 添加流程监听示例 * update 调整获取审批记录 * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * update  调整请假查询 修改流程定义查看xml * update 调整流程实例删除 * update 调整sql * add 添加sql脚本 * update 调整sql * update 调整请假申请,调整菜单sql * update 调整设计器保存发起人变量,修改菜单sql * update 依赖调整 * update 调整flw依赖 * update 升级7.0后移除画图mule类型 * update 调整flw依赖 * update 移动模型设计器翻译方法 * update 调整flw依赖 * fix 修复 误删依赖 * Merge remote-tracking branch 'origin/dev' into future/flowable * remove 移除动态表单 * Merge remote-tracking branch 'origin/dev' into future/flowable * update 优化代码结构 * update 调整请假申请包结构 * Merge branch 'dev' into future/flowable * add 添加文件,调整分类查询 * Merge branch 'dev' into future/flowable * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * add bpmn文件 调整流程办理 * Merge branch 'dev' into future/flowable * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * Merge branch '5.X' into future/flowable * update 调整消息发送 * update 调整名称 * update 调整流程实例查询 * add 添加任务催办,任务改派 * fix 修复 用户注册接口校验用户名不区分租户问题 * update 还原待办任务,添加待办消息发送 * update 优化任务待办,排除非待办任务 * Merge branch '5.X' into future/flowable * update 修改流程启动后重新覆盖流程变量,删除并行流程驳回,撤销后,垃圾数据 * update 升级flowable7.0,添加业务单据删除流程信息 * Merge branch '5.X' into future/flowable * add 添加动态表单提交流程 * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * add 添加动态表单单据 * update 升级flowable到7.0.0.M2,调整工作流提交校验,调整工作流工具类 * add 新增流程定义与表单关联 * update 调整修改流程分类后更新流程分类编码 * update 调整流程定义图片预览 * update 调整人员查询 * update 优化作废,撤销等备注 * Merge branch '5.X' into future/flowable * Merge branch 'future/flowable' of https://gitee.com/dromara/RuoYi-Vue-… * fix 解决设计器选择设置流程发起人设置变量有问题 * add 添加引擎调度监听 * merge 合并5.x分支代码 * remove 移除flow-ui * update 调整日志打印 * add 添加按照业务id删除流程记录 * add 添加请假申请示例,添加流程定义文件部署,添加sql菜单 * update 移除流程表单 formConfig 属性,表单配置信息都放一起便于使用。 * update 调整菜单 * add 添加mysql工作流菜单 * update 调整获取加签人,审判记录 * update 调整流程作废 * add 添加任务完成状态 * add 添加加签,减签人员接口 * update 调整任务驳回后设置审批人 * add 添加驳回申请人 * add 添加查询当前租户所有待办,已办任务 * add 添加会签任务加签减签,添加任务作废理由 * update 调整流程实例,流程定义检索 * update 调整撤销流程申请,当前登录人单据 * add 添加办理人名称翻译 * add 添加流程流程实例,流程定义分类查询 * add 添加模型分类查询 * add 添加流程分类 * add 添加流程表单操作相关接口 * fix 修复修改流程历史流程实例错误问题 * update 调整已办任务排序,添加注释 * update 调整用户,用户组查询 * add 添加获取当前任务参与者,优化任务待办,已办 * add 添加当前登录人单据列表,添加单据状态 * update 补充任务撤销事务 * add 添加撤销流程申请 * update 优化流程实例删除 * fix 修复流程实例查询挂起状态错误 * update 优化流程办理 流程挂起抛出异常 * add 添加业务状态枚举。添加流程启动,审批,终止等状态 * update 优化流程启动 * add 添加流程实例作废,运行中流程实例删除,已完成流程实例删除 * add 添加节点信息 * 调整流程预览 * add 添加审批记录 * 还原代码 * fix 修复模型导出错误 * add 增加委托办理,调整流程启动 * add 添加转办任务 * add 添加任务拾取,任务归还,任务终止,任务委托 * fix 修复任务,流程实例分页模糊查询失效 * add 添加流程实例运行中,已结束分页查询 * add 添加通过流程实例id获取历史流程图,添加flowable配置,调整流程办理 * add 添加流程办理,流程待办,已办分页查询 * 删除无用导入 * 调整流程查询租户id * add 添加流程启动 * 添加模型人员用户,组查询 * add 添加模型部署模型校验 * 修改模型部署导出校验 * fix 修复模型画图保存时key不回显问题 * add 添加流程定义转换为模型 * 优化模型编辑校验,流程定义删除,流程定义激活挂起等 * add 添加流程定义删除,流程定义挂起激活,流程定义版本迁移 * 调整ObjectNode.put警告 * 删除无用依赖,优化模型修改,导出,部署非空校验 * 删除无用导入 * 添加流程定义分页,查看图片,查看xml * 添加模型部署,导出模型 * 修改画图账户登录信息 * 添加模型编辑key重复校验,添加租户查询,删除忽略token注解 * 添加模型新增校验 * 添加工作流模型新增,修改,查询,删除 * 【ADD】集成原生Flowable-ui * 添加workflow模块,添加flowable依赖,yml配置信息

---
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java               |   93 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java                    |   51 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java                             |   52 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java          |   37 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java     |   20 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java   |   82 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java        |   20 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml                            |    7 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java                      |   65 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java                        |   58 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java                  |  246 +
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java                  |   31 
 script/bpmn/请假流程(排他网关)-leave2.zip                                                                                  |    0 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java                 |   31 
 script/bpmn/请假流程(普通流程)-leave1.zip                                                                                  |    0 
 script/bpmn/请假流程(子流程)-leave6.zip                                                                                   |    0 
 ruoyi-admin/src/main/resources/application.yml                                                                     |   18 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java             |   27 
 script/bpmn/请假流程(会签)-leave5.zip                                                                                    |    0 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java                      |   45 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java                 |  135 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java    |  108 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java          |   90 
 ruoyi-admin/src/main/resources/application-prod.yml                                                                |    4 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java                             |   69 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java                          |  152 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java                              |  143 
 ruoyi-admin/src/main/resources/application-dev.yml                                                                 |    4 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java                          |   38 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java                    |   16 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java   |   39 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java                        |   54 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java       |  157 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java                          |  193 +
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java                              |   36 
 pom.xml                                                                                                            |   18 
 ruoyi-modules/pom.xml                                                                                              |    1 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java             |   24 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java                    |   89 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java                      |   34 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java           |   73 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java            |  113 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java          |  216 +
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java                    |   31 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java                |   21 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java              |  334 ++
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java                              |   23 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java     |  146 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java                       |   43 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java                    |   68 
 script/bpmn/请假流程(监听)-leave7.zip                                                                                    |    0 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java             |   72 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java                              |   58 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java               |  708 ++++
 script/sql/flowable.sql                                                                                            |   65 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java                      |   71 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java                   |   46 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md                                             |    3 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java                          |   40 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java                 |   37 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java                    |   16 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java                       |   43 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java                         |   70 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml                               |    7 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java                 |   11 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java                   |   95 
 script/bpmn/请假流程(包容网关)-leave4.zip                                                                                  |    0 
 script/sql/sqlserver/flowable.sql                                                                                  |  155 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java                 |   27 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java                          |   38 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java                   |   90 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java                       |   52 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java                        |   34 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java                       |   37 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java      |   61 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java           |   29 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java                        |   15 
 script/sql/postgres/flowable.sql                                                                                   |  122 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java              |   51 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java                           |  341 ++
 script/bpmn/请假流程(并行网关)-leave3.zip                                                                                  |    0 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml                            |    7 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java         |   18 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java        |   91 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java | 1120 ++++++
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java    |  695 ++++
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java                |   24 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java            |  128 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java                              |  220 +
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java             |  124 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java                         |   75 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java                       |   15 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java                          |   37 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java               |   36 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java                |  107 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml                                  |   60 
 ruoyi-admin/pom.xml                                                                                                |    6 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java                     |   33 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java                  |   92 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java                  |   60 
 ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml                                |    7 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java                       |   47 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java                       |  129 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java                     |   49 
 ruoyi-modules/ruoyi-workflow/pom.xml                                                                               |   93 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java               |  106 
 script/sql/oracle/flowable.sql                                                                                     |   93 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java         |   51 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java                             |   28 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java                 |   65 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java                |   32 
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java  |  328 ++
 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java                             |   48 
 113 files changed, 9,769 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1ce5af9..ba9ced2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,9 @@
         <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
         <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
         <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
+
+        <!--宸ヤ綔娴侀厤缃�-->
+        <flowable.version>7.0.0</flowable.version>
     </properties>
 
     <profiles>
@@ -107,6 +110,14 @@
                 <groupId>cn.hutool</groupId>
                 <artifactId>hutool-bom</artifactId>
                 <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.flowable</groupId>
+                <artifactId>flowable-bom</artifactId>
+                <version>${flowable.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
@@ -353,6 +364,13 @@
                 <version>${revision}</version>
             </dependency>
 
+            <!--  宸ヤ綔娴佹ā鍧�  -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-workflow</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 26cd023..cbf03ce 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -75,6 +75,12 @@
             <artifactId>ruoyi-demo</artifactId>
         </dependency>
 
+        <!--  宸ヤ綔娴佹ā鍧�  -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-workflow</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>de.codecentric</groupId>
             <artifactId>spring-boot-admin-starter-client</artifactId>
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 847e4c2..5589e58 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -43,7 +43,7 @@
           driverClassName: com.mysql.cj.jdbc.Driver
           # jdbc 鎵�鏈夊弬鏁伴厤缃弬鑰� https://lionli.blog.csdn.net/article/details/122018562
           # rewriteBatchedStatements=true 鎵瑰鐞嗕紭鍖� 澶у箙鎻愬崌鎵归噺鎻掑叆鏇存柊鍒犻櫎鎬ц兘(瀵规暟鎹簱鏈夋�ц兘鎹熻�� 浣跨敤鎵归噺鎿嶄綔搴旇�冭檻鎬ц兘闂)
-          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
           username: root
           password: root
         # 浠庡簱鏁版嵁婧�
@@ -51,7 +51,7 @@
           lazy: true
           type: ${spring.datasource.type}
           driverClassName: com.mysql.cj.jdbc.Driver
-          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
           username:
           password:
 #        oracle:
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index c8817aa..b4f4040 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -46,7 +46,7 @@
           driverClassName: com.mysql.cj.jdbc.Driver
           # jdbc 鎵�鏈夊弬鏁伴厤缃弬鑰� https://lionli.blog.csdn.net/article/details/122018562
           # rewriteBatchedStatements=true 鎵瑰鐞嗕紭鍖� 澶у箙鎻愬崌鎵归噺鎻掑叆鏇存柊鍒犻櫎鎬ц兘(瀵规暟鎹簱鏈夋�ц兘鎹熻�� 浣跨敤鎵归噺鎿嶄綔搴旇�冭檻鎬ц兘闂)
-          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
           username: root
           password: root
         # 浠庡簱鏁版嵁婧�
@@ -54,7 +54,7 @@
           lazy: true
           type: ${spring.datasource.type}
           driverClassName: com.mysql.cj.jdbc.Driver
-          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
           username:
           password:
 #        oracle:
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 0d334a2..a3ea740 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -266,3 +266,21 @@
   path: /resource/websocket
   # 璁剧疆璁块棶婧愬湴鍧�
   allowedOrigins: '*'
+
+--- #flowable閰嶇疆
+flowable:
+  async-executor-activate: false #鍏抽棴瀹氭椂浠诲姟JOB
+  #  灏哾atabaseSchemaUpdate璁剧疆涓簍rue銆傚綋Flowable鍙戠幇搴撲笌鏁版嵁搴撹〃缁撴瀯涓嶄竴鑷存椂锛屼細鑷姩灏嗘暟鎹簱琛ㄧ粨鏋勫崌绾ц嚦鏂扮増鏈��
+  database-schema-update: true
+  activity-font-name: 瀹嬩綋
+  label-font-name: 瀹嬩綋
+  annotation-font-name: 瀹嬩綋
+  # 鍏抽棴鍚勪釜妯″潡鐢熸垚琛紝鐩墠鍙娇鐢ㄥ伐浣滄祦鍩虹琛�
+  idm:
+    enabled: false
+  cmmn:
+    enabled: false
+  dmn:
+    enabled: false
+  app:
+    enabled: false
diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml
index 4044916..daff497 100644
--- a/ruoyi-modules/pom.xml
+++ b/ruoyi-modules/pom.xml
@@ -14,6 +14,7 @@
         <module>ruoyi-generator</module>
         <module>ruoyi-job</module>
         <module>ruoyi-system</module>
+        <module>ruoyi-workflow</module>
     </modules>
 
     <artifactId>ruoyi-modules</artifactId>
diff --git a/ruoyi-modules/ruoyi-workflow/pom.xml b/ruoyi-modules/ruoyi-workflow/pom.xml
new file mode 100644
index 0000000..f0391e3
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-modules</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>ruoyi-workflow</artifactId>
+
+    <description>
+        宸ヤ綔娴佹ā鍧�
+    </description>
+
+    <dependencies>
+
+        <!--寮曞叆flowable渚濊禆-->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-autoconfigure</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.flowable</groupId>
+                    <artifactId>flowable-spring-security</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-configurator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- 缁樺埗flowable娴佺▼鍥� -->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-image-generator</artifactId>
+        </dependency>
+
+        <!-- flowable json 杞崲 -->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-json-converter</artifactId>
+            <version>6.8.0</version>
+        </dependency>
+
+        <!-- svg杞琾ng鍥剧墖宸ュ叿-->
+        <dependency>
+            <groupId>org.apache.xmlgraphics</groupId>
+            <artifactId>batik-all</artifactId>
+            <version>1.10</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>xalan</groupId>
+                    <artifactId>xalan</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--绯荤粺妯″潡-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-websocket</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sms</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java
new file mode 100644
index 0000000..5ea262d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/annotation/FlowListenerAnnotation.java
@@ -0,0 +1,27 @@
+package org.dromara.workflow.annotation;
+
+
+import java.lang.annotation.*;
+
+/**
+ * 娴佺▼浠诲姟鐩戝惉娉ㄨВ
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface FlowListenerAnnotation {
+
+    /**
+     * 娴佺▼瀹氫箟key
+     */
+    String processDefinitionKey();
+
+    /**
+     * 鑺傜偣id
+     */
+    String taskDefId() default "";
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java
new file mode 100644
index 0000000..cc8aadd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/PageEntity.java
@@ -0,0 +1,28 @@
+package org.dromara.workflow.common;
+
+
+import lombok.Data;
+
+/**
+ * 鍒嗛〉鍙傛暟
+ *
+ * @author may
+ */
+@Data
+public class PageEntity {
+
+    /**
+     * 褰撳墠椤电爜
+     */
+    private Integer pageNum = 0;
+
+    /**
+     * 椤靛閲�
+     */
+    private Integer pageSize = 10;
+
+    public Integer getPageNum() {
+        return (pageNum - 1) * pageSize;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
new file mode 100644
index 0000000..614f9ff
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
@@ -0,0 +1,92 @@
+package org.dromara.workflow.common.constant;
+
+
+/**
+ * 宸ヤ綔娴佸父閲�
+ *
+ * @author may
+ */
+public interface FlowConstant {
+
+    String MESSAGE_CURRENT_TASK_IS_NULL = "褰撳墠浠诲姟涓嶅瓨鍦ㄦ垨浣犱笉鏄换鍔″姙鐞嗕汉锛�";
+
+    String MESSAGE_SUSPENDED = "褰撳墠浠诲姟宸叉寕璧蜂笉鍙鎵癸紒";
+
+    /**
+     * 杩炵嚎
+     */
+    String SEQUENCE_FLOW = "sequenceFlow";
+
+    /**
+     * 骞惰缃戝叧
+     */
+    String PARALLEL_GATEWAY = "parallelGateway";
+
+    /**
+     * 鎺掑畠缃戝叧
+     */
+    String EXCLUSIVE_GATEWAY = "exclusiveGateway";
+
+    /**
+     * 鍖呭惈缃戝叧
+     */
+    String INCLUSIVE_GATEWAY = "inclusiveGateway";
+
+    /**
+     * 缁撴潫鑺傜偣
+     */
+    String END_EVENT = "endEvent";
+
+
+    /**
+     * 娴佺▼濮旀淳鏍囪瘑
+     */
+    String PENDING = "PENDING";
+
+    /**
+     * 鍊欓�変汉鏍囪瘑
+     */
+    String CANDIDATE = "candidate";
+
+    /**
+     * 浼氱浠诲姟鎬绘暟
+     */
+    String NUMBER_OF_INSTANCES = "nrOfInstances";
+
+    /**
+     * 姝e湪鎵ц鐨勪細绛炬�绘暟
+     */
+    String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
+
+    /**
+     * 宸插畬鎴愮殑浼氱浠诲姟鎬绘暟
+     */
+    String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";
+
+    /**
+     * 寰幆鐨勭储寮曞�硷紝鍙互浣跨敤elementIndexVariable灞炴�т慨鏀筶oopCounter鐨勫彉閲忓悕
+     */
+    String LOOP_COUNTER = "loopCounter";
+
+    String ZIP = "ZIP";
+
+    /**
+     * 娴佺▼瀹炰緥瀵硅薄
+     */
+    String PROCESS_INSTANCE_VO = "processInstanceVo";
+
+    /**
+     * 娴佺▼鍙戣捣浜�
+     */
+    String INITIATOR = "initiator";
+
+    /**
+     * 寮�鍚烦杩囪〃杈惧紡鍙橀噺
+     */
+    String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
+
+    /**
+     * 妯″瀷鏍囪瘑key鍛藉悕瑙勮寖姝e垯琛ㄨ揪寮�
+     */
+    String MODEL_KEY_PATTERN = "^[a-zA-Z][a-zA-Z0-9_]{0,254}$";
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java
new file mode 100644
index 0000000..43204f5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/BusinessStatusEnum.java
@@ -0,0 +1,93 @@
+package org.dromara.workflow.common.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Arrays;
+
+/**
+ * 涓氬姟鐘舵�佹灇涓�
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum BusinessStatusEnum {
+    /**
+     * 宸叉挙閿�
+     */
+    CANCEL("cancel", "宸叉挙閿�"),
+    /**
+     * 鑽夌
+     */
+    DRAFT("draft", "鑽夌"),
+    /**
+     * 寰呭鏍�
+     */
+    WAITING("waiting", "寰呭鏍�"),
+    /**
+     * 宸插畬鎴�
+     */
+    FINISH("finish", "宸插畬鎴�"),
+    /**
+     * 宸蹭綔搴�
+     */
+    INVALID("invalid", "宸蹭綔搴�"),
+    /**
+     * 宸查��鍥�
+     */
+    BACK("back", "宸查��鍥�"),
+    /**
+     * 宸茬粓姝�
+     */
+    TERMINATION("termination", "宸茬粓姝�");
+
+    /**
+     * 鐘舵��
+     */
+    private final String status;
+
+    /**
+     * 鎻忚堪
+     */
+    private final String desc;
+
+    /**
+     * 鑾峰彇涓氬姟鐘舵��
+     *
+     * @param status 鐘舵��
+     */
+    public static String findByStatus(String status) {
+        if (StringUtils.isBlank(status)) {
+            return StrUtil.EMPTY;
+        }
+        return Arrays.stream(BusinessStatusEnum.values())
+            .filter(statusEnum -> statusEnum.getStatus().equals(status))
+            .findFirst()
+            .map(BusinessStatusEnum::getDesc)
+            .orElse(StrUtil.EMPTY);
+    }
+
+    /**
+     * 鍚姩娴佺▼鏍¢獙
+     *
+     * @param status 鐘舵��
+     */
+    public static void checkStartStatus(String status) {
+        if (WAITING.getStatus().equals(status)) {
+            throw new ServiceException("璇ュ崟鎹凡鎻愪氦杩囩敵璇�,姝e湪瀹℃壒涓紒");
+        } else if (FINISH.getStatus().equals(status)) {
+            throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+        } else if (INVALID.getStatus().equals(status)) {
+            throw new ServiceException("璇ュ崟鎹凡浣滃簾锛�");
+        } else if (TERMINATION.getStatus().equals(status)) {
+            throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+        } else if (StringUtils.isBlank(status)) {
+            throw new ServiceException("娴佺▼鐘舵�佷负绌猴紒");
+        }
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
new file mode 100644
index 0000000..d7ba1bf
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/MessageTypeEnum.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 娑堟伅绫诲瀷鏋氫妇
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum MessageTypeEnum {
+    /**
+     * 绔欏唴淇�
+     */
+    SYSTEM_MESSAGE("1", "绔欏唴淇�"),
+    /**
+     * 閭
+     */
+    EMAIL_MESSAGE("2", "閭"),
+    /**
+     * 鐭俊
+     */
+    SMS_MESSAGE("3", "鐭俊");
+
+    private final String code;
+
+    private final String desc;
+}
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
new file mode 100644
index 0000000..03be8dc
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java
@@ -0,0 +1,90 @@
+package org.dromara.workflow.common.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+
+/**
+ * 浠诲姟鐘舵�佹灇涓�
+ *
+ * @author may
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskStatusEnum {
+    /**
+     * 鎾ら攢
+     */
+    CANCEL("cancel", "鎾ら攢"),
+    /**
+     * 閫氳繃
+     */
+    PASS("pass", "閫氳繃"),
+    /**
+     * 寰呭鏍�
+     */
+    WAITING("waiting", "寰呭鏍�"),
+    /**
+     * 浣滃簾
+     */
+    INVALID("invalid", "浣滃簾"),
+    /**
+     * 閫�鍥�
+     */
+    BACK("back", "閫�鍥�"),
+    /**
+     * 缁堟
+     */
+    TERMINATION("termination", "缁堟"),
+    /**
+     * 杞姙
+     */
+    TRANSFER("transfer", "杞姙"),
+    /**
+     * 濮旀墭
+     */
+    PENDING("pending", "濮旀墭"),
+    /**
+     * 鎶勯��
+     */
+    COPY("copy", "鎶勯��"),
+    /**
+     * 鍔犵
+     */
+    SIGN("sign", "鍔犵"),
+    /**
+     * 鍑忕
+     */
+    SIGN_OFF("sign_off", "鍑忕");
+
+    /**
+     * 鐘舵��
+     */
+    private final String status;
+
+    /**
+     * 鎻忚堪
+     */
+    private final String desc;
+
+    /**
+     * 浠诲姟涓氬姟鐘舵��
+     *
+     * @param status 鐘舵��
+     */
+    public static String findByStatus(String status) {
+        if (StringUtils.isBlank(status)) {
+            return StrUtil.EMPTY;
+        }
+
+        return Arrays.stream(TaskStatusEnum.values())
+            .filter(statusEnum -> statusEnum.getStatus().equals(status))
+            .findFirst()
+            .map(TaskStatusEnum::getDesc)
+            .orElse(StrUtil.EMPTY);
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java
new file mode 100644
index 0000000..31e31e6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActModelController.java
@@ -0,0 +1,135 @@
+package org.dromara.workflow.controller;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.bo.ModelBo;
+import org.dromara.workflow.domain.vo.ModelVo;
+import org.dromara.workflow.service.IActModelService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.Model;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 妯″瀷绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/model")
+public class ActModelController extends BaseController {
+
+    private final RepositoryService repositoryService;
+
+    private final IActModelService actModelService;
+
+
+    /**
+     * 鍒嗛〉鏌ヨ妯″瀷
+     *
+     * @param modelBo 妯″瀷鍙傛暟
+     */
+    @GetMapping("/list")
+    public TableDataInfo<Model> page(ModelBo modelBo) {
+        return actModelService.page(modelBo);
+    }
+
+    /**
+     * 鏂板妯″瀷
+     *
+     * @param modelBo 妯″瀷璇锋眰瀵硅薄
+     */
+    @Log(title = "妯″瀷绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/save")
+    public R<Void> saveNewModel(@Validated(AddGroup.class) @RequestBody ModelBo modelBo) {
+        return toAjax(actModelService.saveNewModel(modelBo));
+    }
+
+    /**
+     * 鏌ヨ妯″瀷
+     *
+     * @param id 妯″瀷id
+     */
+    @GetMapping("/getInfo/{id}")
+    public R<ModelVo> getInfo(@NotBlank(message = "妯″瀷id涓嶈兘涓虹┖") @PathVariable String id) {
+        return R.ok(actModelService.getInfo(id));
+    }
+
+    /**
+     * 淇敼妯″瀷淇℃伅
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     */
+    @Log(title = "妯″瀷绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping(value = "/update")
+    public R<Void> update(@RequestBody ModelBo modelBo) {
+        return toAjax(actModelService.update(modelBo));
+    }
+
+    /**
+     * 缂栬緫XMl妯″瀷
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     */
+    @Log(title = "妯″瀷绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping(value = "/editModelXml")
+    public R<Void> editModel(@Validated(EditGroup.class) @RequestBody ModelBo modelBo) {
+        return toAjax(actModelService.editModelXml(modelBo));
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼妯″瀷
+     *
+     * @param ids 妯″瀷id
+     */
+    @Log(title = "妯″瀷绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @DeleteMapping("/{ids}")
+    @Transactional(rollbackFor = Exception.class)
+    public R<Void> delete(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable String[] ids) {
+        Arrays.stream(ids).parallel().forEachOrdered(repositoryService::deleteModel);
+        return R.ok();
+    }
+
+    /**
+     * 妯″瀷閮ㄧ讲
+     *
+     * @param id 妯″瀷id
+     */
+    @Log(title = "妯″瀷绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/modelDeploy/{id}")
+    public R<Void> deploy(@NotBlank(message = "妯″瀷id涓嶈兘涓虹┖") @PathVariable("id") String id) {
+        return toAjax(actModelService.modelDeploy(id));
+    }
+
+    /**
+     * 瀵煎嚭妯″瀷zip鍘嬬缉鍖�
+     *
+     * @param modelId  妯″瀷id
+     * @param response 鐩稿簲
+     */
+    @GetMapping("/export/zip/{modelId}")
+    public void exportZip(@NotEmpty(message = "妯″瀷id涓嶈兘涓虹┖") @PathVariable String modelId,
+                          HttpServletResponse response) {
+        actModelService.exportZip(modelId, response);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java
new file mode 100644
index 0000000..a19cba1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessDefinitionController.java
@@ -0,0 +1,146 @@
+package org.dromara.workflow.controller;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
+import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
+import org.dromara.workflow.service.IActProcessDefinitionService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 娴佺▼瀹氫箟绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/processDefinition")
+public class ActProcessDefinitionController extends BaseController {
+
+    private final IActProcessDefinitionService actProcessDefinitionService;
+
+    /**
+     * 鍒嗛〉鏌ヨ
+     *
+     * @param processDefinitionBo 鍙傛暟
+     */
+    @GetMapping("/list")
+    public TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo processDefinitionBo) {
+        return actProcessDefinitionService.page(processDefinitionBo);
+    }
+
+    /**
+     * 鏌ヨ鍘嗗彶娴佺▼瀹氫箟鍒楄〃
+     *
+     * @param key 娴佺▼瀹氫箟key
+     */
+    @GetMapping("/getProcessDefinitionListByKey/{key}")
+    public R<List<ProcessDefinitionVo>> getProcessDefinitionListByKey(@NotEmpty(message = "娴佺▼瀹氫箟key涓嶈兘涓虹┖") @PathVariable String key) {
+        return R.ok("鎿嶄綔鎴愬姛", actProcessDefinitionService.getProcessDefinitionListByKey(key));
+    }
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟鍥剧墖
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @GetMapping("/processDefinitionImage/{processDefinitionId}")
+    public R<String> processDefinitionImage(@PathVariable String processDefinitionId) {
+        return R.ok("鎿嶄綔鎴愬姛", actProcessDefinitionService.processDefinitionImage(processDefinitionId));
+    }
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟xml鏂囦欢
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @GetMapping("/processDefinitionXml/{processDefinitionId}")
+    public R<Map<String, Object>> getXml(@NotBlank(message = "娴佺▼瀹氫箟id涓嶈兘涓虹┖") @PathVariable String processDefinitionId) {
+        Map<String, Object> map = new HashMap<>();
+        String xmlStr = actProcessDefinitionService.processDefinitionXml(processDefinitionId);
+        map.put("xml", Arrays.asList(xmlStr.split("\n")));
+        map.put("xmlStr", xmlStr);
+        return R.ok(map);
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼瀹氫箟
+     *
+     * @param deploymentId        閮ㄧ讲id
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Log(title = "娴佺▼瀹氫箟绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @DeleteMapping("/{deploymentId}/{processDefinitionId}")
+    public R<Void> deleteDeployment(@NotBlank(message = "娴佺▼閮ㄧ讲id涓嶈兘涓虹┖") @PathVariable String deploymentId,
+                                    @NotBlank(message = "娴佺▼瀹氫箟id涓嶈兘涓虹┖") @PathVariable String processDefinitionId) {
+        return toAjax(actProcessDefinitionService.deleteDeployment(deploymentId, processDefinitionId));
+    }
+
+    /**
+     * 婵�娲绘垨鑰呮寕璧锋祦绋嬪畾涔�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Log(title = "娴佺▼瀹氫箟绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/updateProcessDefState/{processDefinitionId}")
+    public R<Void> updateProcDefState(@NotBlank(message = "娴佺▼瀹氫箟id涓嶈兘涓虹┖") @PathVariable String processDefinitionId) {
+        return toAjax(actProcessDefinitionService.updateProcessDefState(processDefinitionId));
+    }
+
+    /**
+     * 杩佺Щ娴佺▼瀹氫箟
+     *
+     * @param currentProcessDefinitionId 褰撳墠娴佺▼瀹氫箟id
+     * @param fromProcessDefinitionId    闇�瑕佽縼绉诲埌鐨勬祦绋嬪畾涔塱d
+     */
+    @Log(title = "娴佺▼瀹氫箟绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/migrationProcessDefinition/{currentProcessDefinitionId}/{fromProcessDefinitionId}")
+    public R<Void> migrationProcessDefinition(@NotBlank(message = "褰撳墠娴佺▼瀹氫箟id") @PathVariable String currentProcessDefinitionId,
+                                              @NotBlank(message = "闇�瑕佽縼绉诲埌鐨勬祦绋嬪畾涔塱d") @PathVariable String fromProcessDefinitionId) {
+        return toAjax(actProcessDefinitionService.migrationProcessDefinition(currentProcessDefinitionId, fromProcessDefinitionId));
+    }
+
+    /**
+     * 娴佺▼瀹氫箟杞崲涓烘ā鍨�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Log(title = "娴佺▼瀹氫箟绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/convertToModel/{processDefinitionId}")
+    public R<Void> convertToModel(@NotEmpty(message = "娴佺▼瀹氫箟id涓嶈兘涓虹┖") @PathVariable String processDefinitionId) {
+        return toAjax(actProcessDefinitionService.convertToModel(processDefinitionId));
+    }
+
+    /**
+     * 閫氳繃zip鎴杧ml閮ㄧ讲娴佺▼瀹氫箟
+     *
+     * @param file         鏂囦欢
+     * @param categoryCode 鍒嗙被
+     */
+    @Log(title = "娴佺▼瀹氫箟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/deployByFile")
+    public R<Void> deployByFile(@RequestParam("file") MultipartFile file, @RequestParam("categoryCode") String categoryCode) {
+        return toAjax(actProcessDefinitionService.deployByFile(file, categoryCode));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java
new file mode 100644
index 0000000..cd2c9e2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActProcessInstanceController.java
@@ -0,0 +1,157 @@
+package org.dromara.workflow.controller;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.bo.ProcessInstanceBo;
+import org.dromara.workflow.domain.bo.ProcessInvalidBo;
+import org.dromara.workflow.domain.bo.TaskUrgingBo;
+import org.dromara.workflow.domain.vo.ProcessInstanceVo;
+import org.dromara.workflow.service.IActProcessInstanceService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * 娴佺▼瀹炰緥绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/processInstance")
+public class ActProcessInstanceController extends BaseController {
+
+    private final IActProcessInstanceService actProcessInstanceService;
+
+    /**
+     * 鍒嗛〉鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚�
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @GetMapping("/getProcessInstanceRunningByPage")
+    public TableDataInfo<ProcessInstanceVo> getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo) {
+        return actProcessInstanceService.getProcessInstanceRunningByPage(processInstanceBo);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @GetMapping("/getProcessInstanceFinishByPage")
+    public TableDataInfo<ProcessInstanceVo> getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo) {
+        return actProcessInstanceService.getProcessInstanceFinishByPage(processInstanceBo);
+    }
+
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥�
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @GetMapping("/getHistoryProcessImage/{processInstanceId}")
+    public R<String> getHistoryProcessImage(@NotBlank(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String processInstanceId) {
+        return R.ok("鎿嶄綔鎴愬姛", actProcessInstanceService.getHistoryProcessImage(processInstanceId));
+    }
+
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥捐繍琛屼腑锛屽巻鍙茬瓑鑺傜偣
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @GetMapping("/getHistoryProcessList/{processInstanceId}")
+    public R<Map<String, Object>> getHistoryProcessList(@NotBlank(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String processInstanceId) {
+        return R.ok("鎿嶄綔鎴愬姛", actProcessInstanceService.getHistoryProcessList(processInstanceId));
+    }
+
+    /**
+     * 鑾峰彇瀹℃壒璁板綍
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @GetMapping("/getHistoryRecord/{processInstanceId}")
+    public R<Map<String, Object>> getHistoryRecord(@NotBlank(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String processInstanceId) {
+        return R.ok(actProcessInstanceService.getHistoryRecord(processInstanceId));
+    }
+
+    /**
+     * 浣滃簾娴佺▼瀹炰緥锛屼笉浼氬垹闄ゅ巻鍙茶褰�(鍒犻櫎杩愯涓殑瀹炰緥)
+     *
+     * @param processInvalidBo 鍙傛暟
+     */
+    @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @PostMapping("/deleteRuntimeProcessInst")
+    public R<Void> deleteRuntimeProcessInst(@Validated(AddGroup.class) @RequestBody ProcessInvalidBo processInvalidBo) {
+        return toAjax(actProcessInstanceService.deleteRuntimeProcessInst(processInvalidBo));
+    }
+
+    /**
+     * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     */
+    @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @DeleteMapping("/deleteRuntimeProcessAndHisInst/{processInstanceIds}")
+    public R<Void> deleteRuntimeProcessAndHisInst(@NotNull(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String[] processInstanceIds) {
+        return toAjax(actProcessInstanceService.deleteRuntimeProcessAndHisInst(Arrays.asList(processInstanceIds)));
+    }
+
+    /**
+     * 宸插畬鎴愮殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     */
+    @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @DeleteMapping("/deleteFinishProcessAndHisInst/{processInstanceIds}")
+    public R<Void> deleteFinishProcessAndHisInst(@NotNull(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String[] processInstanceIds) {
+        return toAjax(actProcessInstanceService.deleteFinishProcessAndHisInst(Arrays.asList(processInstanceIds)));
+    }
+
+    /**
+     * 鎾ら攢娴佺▼鐢宠
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/cancelProcessApply/{processInstanceId}")
+    public R<Void> cancelProcessApply(@NotBlank(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖") @PathVariable String processInstanceId) {
+        return toAjax(actProcessInstanceService.cancelProcessApply(processInstanceId));
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ褰撳墠鐧诲綍浜哄崟鎹�
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @GetMapping("/getCurrentSubmitByPage")
+    public TableDataInfo<ProcessInstanceVo> getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo) {
+        return actProcessInstanceService.getCurrentSubmitByPage(processInstanceBo);
+    }
+
+    /**
+     * 浠诲姟鍌姙(缁欏綋鍓嶄换鍔″姙鐞嗕汉鍙戦�佺珯鍐呬俊锛岄偖浠讹紝鐭俊绛�)
+     *
+     * @param taskUrgingBo 浠诲姟鍌姙
+     */
+    @Log(title = "娴佺▼瀹炰緥绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/taskUrging")
+    public R<Void> taskUrging(@RequestBody TaskUrgingBo taskUrgingBo) {
+        return toAjax(actProcessInstanceService.taskUrging(taskUrgingBo));
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java
new file mode 100644
index 0000000..7bf61a7
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/ActTaskController.java
@@ -0,0 +1,246 @@
+package org.dromara.workflow.controller;
+
+import cn.hutool.core.convert.Convert;
+import jakarta.validation.constraints.NotBlank;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.TaskVo;
+import org.dromara.workflow.service.IActTaskService;
+import org.dromara.workflow.utils.WorkflowUtils;
+import org.flowable.engine.TaskService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 浠诲姟绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/task")
+public class ActTaskController extends BaseController {
+
+    private final IActTaskService actTaskService;
+
+    private final TaskService taskService;
+
+
+    /**
+     * 鍚姩浠诲姟
+     *
+     * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/startWorkFlow")
+    public R<Map<String, Object>> startWorkFlow(@RequestBody StartProcessBo startProcessBo) {
+        Map<String, Object> map = actTaskService.startWorkFlow(startProcessBo);
+        return R.ok("鎻愪氦鎴愬姛", map);
+    }
+
+    /**
+     * 鍔炵悊浠诲姟
+     *
+     * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/completeTask")
+    public R<Void> completeTask(@Validated(AddGroup.class) @RequestBody CompleteTaskBo completeTaskBo) {
+        return toAjax(actTaskService.completeTask(completeTaskBo));
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @GetMapping("/getTaskWaitByPage")
+    public TableDataInfo<TaskVo> getTaskWaitByPage(TaskBo taskBo) {
+        return actTaskService.getTaskWaitByPage(taskBo);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @GetMapping("/getAllTaskWaitByPage")
+    public TableDataInfo<TaskVo> getAllTaskWaitByPage(TaskBo taskBo) {
+        return actTaskService.getAllTaskWaitByPage(taskBo);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @GetMapping("/getTaskFinishByPage")
+    public TableDataInfo<TaskVo> getTaskFinishByPage(TaskBo taskBo) {
+        return actTaskService.getTaskFinishByPage(taskBo);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @GetMapping("/getTaskCopyByPage")
+    public TableDataInfo<TaskVo> getTaskCopyByPage(TaskBo taskBo) {
+        return actTaskService.getTaskCopyByPage(taskBo);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @GetMapping("/getAllTaskFinishByPage")
+    public TableDataInfo<TaskVo> getAllTaskFinishByPage(TaskBo taskBo) {
+        return actTaskService.getAllTaskFinishByPage(taskBo);
+    }
+
+    /**
+     * 绛炬敹锛堟嬀鍙栵級浠诲姟
+     *
+     * @param taskId 浠诲姟id
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/claim/{taskId}")
+    public R<Void> claimTask(@NotBlank(message = "浠诲姟id涓嶈兘涓虹┖") @PathVariable String taskId) {
+        try {
+            taskService.claim(taskId, Convert.toStr(LoginHelper.getUserId()));
+            return R.ok();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.fail("绛炬敹浠诲姟澶辫触锛�" + e.getMessage());
+        }
+    }
+
+    /**
+     * 褰掕繕锛堟嬀鍙栫殑锛変换鍔�
+     *
+     * @param taskId 浠诲姟id
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/returnTask/{taskId}")
+    public R<Void> returnTask(@NotBlank(message = "浠诲姟id涓嶈兘涓虹┖") @PathVariable String taskId) {
+        try {
+            taskService.setAssignee(taskId, null);
+            return R.ok();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.fail("褰掕繕浠诲姟澶辫触锛�" + e.getMessage());
+        }
+    }
+
+    /**
+     * 濮旀淳浠诲姟
+     *
+     * @param delegateBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/delegateTask")
+    public R<Void> delegateTask(@Validated({AddGroup.class}) @RequestBody DelegateBo delegateBo) {
+        return toAjax(actTaskService.delegateTask(delegateBo));
+    }
+
+    /**
+     * 缁堟浠诲姟
+     *
+     * @param terminationBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.DELETE)
+    @RepeatSubmit()
+    @PostMapping("/terminationTask")
+    public R<Void> terminationTask(@RequestBody TerminationBo terminationBo) {
+        return toAjax(actTaskService.terminationTask(terminationBo));
+    }
+
+    /**
+     * 杞姙浠诲姟
+     *
+     * @param transmitBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/transferTask")
+    public R<Void> transferTask(@Validated({AddGroup.class}) @RequestBody TransmitBo transmitBo) {
+        return toAjax(actTaskService.transferTask(transmitBo));
+    }
+
+    /**
+     * 浼氱浠诲姟鍔犵
+     *
+     * @param addMultiBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/addMultiInstanceExecution")
+    public R<Void> addMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody AddMultiBo addMultiBo) {
+        return toAjax(actTaskService.addMultiInstanceExecution(addMultiBo));
+    }
+
+    /**
+     * 浼氱浠诲姟鍑忕
+     *
+     * @param deleteMultiBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/deleteMultiInstanceExecution")
+    public R<Void> deleteMultiInstanceExecution(@Validated({AddGroup.class}) @RequestBody DeleteMultiBo deleteMultiBo) {
+        return toAjax(actTaskService.deleteMultiInstanceExecution(deleteMultiBo));
+    }
+
+    /**
+     * 椹冲洖瀹℃壒
+     *
+     * @param backProcessBo 鍙傛暟
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/backProcess")
+    public R<String> backProcess(@RequestBody BackProcessBo backProcessBo) {
+        return R.ok(actTaskService.backProcess(backProcessBo));
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鐘舵��
+     *
+     * @param taskId 浠诲姟id
+     */
+    @GetMapping("/getBusinessStatus/{taskId}")
+    public R<String> getBusinessStatus(@PathVariable String taskId) {
+        return R.ok("鎿嶄綔鎴愬姛", WorkflowUtils.getBusinessStatusByTaskId(taskId));
+    }
+
+
+    /**
+     * 淇敼浠诲姟鍔炵悊浜�
+     *
+     * @param taskIds 浠诲姟id
+     * @param userId  鍔炵悊浜篿d
+     */
+    @Log(title = "浠诲姟绠$悊", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/updateAssignee/{taskIds}/{userId}")
+    public R<Void> updateAssignee(@PathVariable String[] taskIds, @PathVariable String userId) {
+        return toAjax(actTaskService.updateAssignee(taskIds, userId));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
new file mode 100644
index 0000000..cc83c28
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java
@@ -0,0 +1,107 @@
+package org.dromara.workflow.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+import org.dromara.workflow.service.ITestLeaveService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 璇峰亣
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/leave")
+public class TestLeaveController extends BaseController {
+
+    private final ITestLeaveService testLeaveService;
+
+    /**
+     * 鏌ヨ璇峰亣鍒楄〃
+     */
+    @SaCheckPermission("demo:leave:list")
+    @GetMapping("/list")
+    public TableDataInfo<TestLeaveVo> list(TestLeaveBo bo, PageQuery pageQuery) {
+        return testLeaveService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭璇峰亣鍒楄〃
+     */
+    @SaCheckPermission("demo:leave:export")
+    @Log(title = "璇峰亣", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(TestLeaveBo bo, HttpServletResponse response) {
+        List<TestLeaveVo> list = testLeaveService.queryList(bo);
+        ExcelUtil.exportExcel(list, "璇峰亣", TestLeaveVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇璇峰亣璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("demo:leave:query")
+    @GetMapping("/{id}")
+    public R<TestLeaveVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                  @PathVariable Long id) {
+        return R.ok(testLeaveService.queryById(id));
+    }
+
+    /**
+     * 鏂板璇峰亣
+     */
+    @SaCheckPermission("demo:leave:add")
+    @Log(title = "璇峰亣", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<TestLeave> add(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) {
+        return R.ok(testLeaveService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼璇峰亣
+     */
+    @SaCheckPermission("demo:leave:edit")
+    @Log(title = "璇峰亣", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<TestLeave> edit(@Validated(EditGroup.class) @RequestBody TestLeaveBo bo) {
+        return R.ok(testLeaveService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎璇峰亣
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("demo:leave:remove")
+    @Log(title = "璇峰亣", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable Long[] ids) {
+        return toAjax(testLeaveService.deleteWithValidByIds(List.of(ids)));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java
new file mode 100644
index 0000000..8dced89
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WfCategoryController.java
@@ -0,0 +1,106 @@
+package org.dromara.workflow.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.workflow.domain.bo.WfCategoryBo;
+import org.dromara.workflow.domain.vo.WfCategoryVo;
+import org.dromara.workflow.service.IWfCategoryService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被
+ *
+ * @author may
+ * @date 2023-06-28
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/category")
+public class WfCategoryController extends BaseController {
+
+    private final IWfCategoryService wfCategoryService;
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+     */
+    @SaCheckPermission("workflow:category:list")
+    @GetMapping("/list")
+    public R<List<WfCategoryVo>> list(WfCategoryBo bo) {
+        List<WfCategoryVo> list = wfCategoryService.queryList(bo);
+        return R.ok(list);
+
+    }
+
+    /**
+     * 瀵煎嚭娴佺▼鍒嗙被鍒楄〃
+     */
+    @SaCheckPermission("workflow:category:export")
+    @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(WfCategoryBo bo, HttpServletResponse response) {
+        List<WfCategoryVo> list = wfCategoryService.queryList(bo);
+        ExcelUtil.exportExcel(list, "娴佺▼鍒嗙被", WfCategoryVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍒嗙被璇︾粏淇℃伅
+     *
+     * @param id 涓婚敭
+     */
+    @SaCheckPermission("workflow:category:query")
+    @GetMapping("/{id}")
+    public R<WfCategoryVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖")
+                                   @PathVariable Long id) {
+        return R.ok(wfCategoryService.queryById(id));
+    }
+
+    /**
+     * 鏂板娴佺▼鍒嗙被
+     */
+    @SaCheckPermission("workflow:category:add")
+    @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody WfCategoryBo bo) {
+        return toAjax(wfCategoryService.insertByBo(bo));
+    }
+
+    /**
+     * 淇敼娴佺▼鍒嗙被
+     */
+    @SaCheckPermission("workflow:category:edit")
+    @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody WfCategoryBo bo) {
+        return toAjax(wfCategoryService.updateByBo(bo));
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼鍒嗙被
+     *
+     * @param ids 涓婚敭涓�
+     */
+    @SaCheckPermission("workflow:category:remove")
+    @Log(title = "娴佺▼鍒嗙被", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
+                          @PathVariable Long[] ids) {
+        return toAjax(wfCategoryService.deleteWithValidByIds(List.of(ids), true));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java
new file mode 100644
index 0000000..78f808f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/WorkflowUserController.java
@@ -0,0 +1,72 @@
+package org.dromara.workflow.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.workflow.domain.bo.SysUserMultiBo;
+import org.dromara.workflow.domain.vo.TaskVo;
+import org.dromara.workflow.service.IWorkflowUserService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+
+/**
+ * 宸ヤ綔娴佺敤鎴烽�変汉绠$悊 鎺у埗灞�
+ *
+ * @author may
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workflow/user")
+public class WorkflowUserController extends BaseController {
+
+    private final IWorkflowUserService workflowUserService;
+
+    /**
+     * 鍒嗛〉鏌ヨ宸ヤ綔娴侀�夋嫨鍔犵浜哄憳
+     *
+     * @param sysUserMultiBo 鍙傛暟
+     */
+    @GetMapping("/getWorkflowAddMultiListByPage")
+    public TableDataInfo<SysUserVo> getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo) {
+        return workflowUserService.getWorkflowAddMultiInstanceByPage(sysUserMultiBo);
+    }
+
+    /**
+     * 鏌ヨ宸ヤ綔娴侀�夋嫨鍑忕浜哄憳
+     *
+     * @param taskId 浠诲姟id
+     */
+    @GetMapping("/getWorkflowDeleteMultiInstanceList/{taskId}")
+    public R<List<TaskVo>> getWorkflowDeleteMultiInstanceList(@PathVariable String taskId) {
+        return R.ok(workflowUserService.getWorkflowDeleteMultiInstanceList(taskId));
+    }
+
+    /**
+     * 鎸夌収鐢ㄦ埛id鏌ヨ鐢ㄦ埛
+     *
+     * @param userIds 鐢ㄦ埛id
+     */
+    @GetMapping("/getUserListByIds/{userIds}")
+    public R<List<SysUserVo>> getUserListByIds(@PathVariable List<Long> userIds) {
+        return R.ok(workflowUserService.getUserListByIds(userIds));
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鐢ㄦ埛
+     *
+     * @param sysUserBo 鍙傛暟
+     * @param pageQuery 鍒嗛〉
+     */
+    @GetMapping("/getUserListByPage")
+    public TableDataInfo<SysUserVo> getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery) {
+        return workflowUserService.getUserListByPage(sysUserBo, pageQuery);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java
new file mode 100644
index 0000000..e87fb92
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiProcinst.java
@@ -0,0 +1,152 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 娴佺▼瀹炰緥瀵硅薄 act_hi_procinst
+ *
+ * @author may
+ * @date 2023-07-22
+ */
+@Data
+@TableName("act_hi_procinst")
+public class ActHiProcinst implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "ID_")
+    private String id;
+
+    /**
+     *
+     */
+    @TableField(value = "REV_")
+    private Long rev;
+
+    /**
+     *
+     */
+    @TableField(value = "PROC_INST_ID_")
+    private String procInstId;
+
+    /**
+     *
+     */
+    @TableField(value = "BUSINESS_KEY_")
+    private String businessKey;
+
+    /**
+     *
+     */
+    @TableField(value = "PROC_DEF_ID_")
+    private String procDefId;
+
+    /**
+     *
+     */
+    @TableField(value = "START_TIME_")
+    private Date startTime;
+
+    /**
+     *
+     */
+    @TableField(value = "END_TIME_")
+    private Date endTime;
+
+    /**
+     *
+     */
+    @TableField(value = "DURATION_")
+    private Long duration;
+
+    /**
+     *
+     */
+    @TableField(value = "START_USER_ID_")
+    private String startUserId;
+
+    /**
+     *
+     */
+    @TableField(value = "START_ACT_ID_")
+    private String startActId;
+
+    /**
+     *
+     */
+    @TableField(value = "END_ACT_ID_")
+    private String endActId;
+
+    /**
+     *
+     */
+    @TableField(value = "SUPER_PROCESS_INSTANCE_ID_")
+    private String superProcessInstanceId;
+
+    /**
+     *
+     */
+    @TableField(value = "DELETE_REASON_")
+    private String deleteReason;
+
+    /**
+     *
+     */
+    @TableField(value = "TENANT_ID_")
+    private String tenantId;
+
+    /**
+     *
+     */
+    @TableField(value = "NAME_")
+    private String name;
+
+    /**
+     *
+     */
+    @TableField(value = "CALLBACK_ID_")
+    private String callbackId;
+
+    /**
+     *
+     */
+    @TableField(value = "CALLBACK_TYPE_")
+    private String callbackType;
+
+    /**
+     *
+     */
+    @TableField(value = "REFERENCE_ID_")
+    private String referenceId;
+
+    /**
+     *
+     */
+    @TableField(value = "REFERENCE_TYPE_")
+    private String referenceType;
+
+    /**
+     *
+     */
+    @TableField(value = "PROPAGATED_STAGE_INST_ID_")
+    private String propagatedStageInstId;
+
+    /**
+     *
+     */
+    @TableField(value = "BUSINESS_STATUS_")
+    private String businessStatus;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java
new file mode 100644
index 0000000..578ea9c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/ActHiTaskinst.java
@@ -0,0 +1,193 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import java.io.Serial;
+
+/**
+ * 娴佺▼鍘嗗彶浠诲姟瀵硅薄 act_hi_taskinst
+ *
+ * @author gssong
+ * @date 2024-03-02
+ */
+@Data
+@TableName("act_hi_taskinst")
+public class ActHiTaskinst implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "ID_")
+    private String id;
+
+    /**
+     * 鐗堟湰
+     */
+    @TableField(value = "REV_")
+    private Long rev;
+
+    /**
+     * 娴佺▼瀹氫箟id
+     */
+    @TableField(value = "PROC_DEF_ID_")
+    private String procDefId;
+
+    /**
+     *
+     */
+    @TableField(value = "TASK_DEF_ID_")
+    private String taskDefId;
+
+    /**
+     * 浠诲姟鑺傜偣id
+     */
+    @TableField(value = "TASK_DEF_KEY_")
+    private String taskDefKey;
+
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    @TableField(value = "PROC_INST_ID_")
+    private String procInstId;
+
+    /**
+     * 娴佺▼鎵цid
+     */
+    @TableField(value = "EXECUTION_ID")
+    private String executionId;
+
+    /**
+     *
+     */
+    @TableField(value = "SCOPE_ID_")
+    private String scopeId;
+
+    /**
+     *
+     */
+    @TableField(value = "SUB_SCOPE_ID_")
+    private String subScopeId;
+
+    /**
+     * 鍏堢敤褰撳墠瀛楁鏍囪瘑鎶勯�佺被鍨�
+     */
+    @TableField(value = "SCOPE_TYPE_")
+    private String scopeType;
+
+    /**
+     *
+     */
+    @TableField(value = "SCOPE_DEFINITION_ID_")
+    private String scopeDefinitionId;
+
+    /**
+     *
+     */
+    @TableField(value = "PROPAGATED_STAGE_INST_ID_")
+    private String propagatedStageInstId;
+
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    @TableField(value = "NAME_")
+    private String name;
+
+    /**
+     * 鐖剁骇id
+     */
+    @TableField(value = "PARENT_TASK_ID_")
+    private String parentTaskId;
+
+    /**
+     * 鎻忚堪
+     */
+    @TableField(value = "DESCRIPTION_")
+    private String description;
+
+    /**
+     * 鍔炵悊浜�
+     */
+    @TableField(value = "OWNER_")
+    private String owner;
+
+    /**
+     * 鍔炵悊浜�
+     */
+    @TableField(value = "ASSIGNEE_")
+    private String assignee;
+
+    /**
+     * 寮�濮嬩簨浠�
+     */
+    @TableField(value = "START_TIME_")
+    private Date startTime;
+
+    /**
+     * 璁ら鏃堕棿
+     */
+    @TableField(value = "CLAIM_TIME_")
+    private Date claimTime;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    @TableField(value = "END_TIME_")
+    private Date endTime;
+
+    /**
+     * 鎸佺画鏃堕棿
+     */
+    @TableField(value = "DURATION_")
+    private Long duration;
+
+    /**
+     * 鍒犻櫎鍘熷洜
+     */
+    @TableField(value = "DELETE_REASON_")
+    private String deleteReason;
+
+    /**
+     * 浼樺厛绾�
+     */
+    @TableField(value = "PRIORITY_")
+    private Long priority;
+
+    /**
+     * 鍒版湡鏃堕棿
+     */
+    @TableField(value = "DUE_DATE_")
+    private Date dueDate;
+
+    /**
+     *
+     */
+    @TableField(value = "FORM_KEY_")
+    private String formKey;
+
+    /**
+     * 鍒嗙被
+     */
+    @TableField(value = "CATEGORY_")
+    private String category;
+
+    /**
+     * 鏈�鍚庝慨鏀规椂闂�
+     */
+    @TableField(value = "LAST_UPDATED_TIME_")
+    private Date lastUpdatedTime;
+
+    /**
+     * 绉熸埛id
+     */
+    @TableField(value = "TENANT_ID_")
+    private String tenantId;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java
new file mode 100644
index 0000000..0e26467
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java
@@ -0,0 +1,58 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 璇峰亣瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("test_leave")
+public class TestLeave extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 璇峰亣绫诲瀷
+     */
+    private String leaveType;
+
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    private Date startDate;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    private Date endDate;
+
+    /**
+     * 璇峰亣澶╂暟
+     */
+    private Integer leaveDays;
+
+    /**
+     * 璇峰亣鍘熷洜
+     */
+    private String remark;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java
new file mode 100644
index 0000000..94a7cf5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/WfCategory.java
@@ -0,0 +1,52 @@
+package org.dromara.workflow.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+
+/**
+ * 娴佺▼鍒嗙被瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("wf_category")
+public class WfCategory extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 鍒嗙被鍚嶇О
+     */
+    private String categoryName;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    private String categoryCode;
+
+    /**
+     * 鐖剁骇id
+     */
+    private Long parentId;
+
+    /**
+     * 鎺掑簭
+     */
+    private Long sortNum;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java
new file mode 100644
index 0000000..320ec64
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/AddMultiBo.java
@@ -0,0 +1,40 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 鍔犵鍙傛暟璇锋眰
+ *
+ * @author may
+ */
+@Data
+public class AddMultiBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟ID
+     */
+    @NotBlank(message = "浠诲姟ID涓嶈兘涓虹┖", groups = AddGroup.class)
+    private String taskId;
+
+    /**
+     * 鍔犵浜哄憳id
+     */
+    @NotEmpty(message = "鍔犵浜哄憳涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<Long> assignees;
+
+    /**
+     * 鍔犵浜哄憳鍚嶇О
+     */
+    @NotEmpty(message = "鍔犵浜哄憳涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<String> assigneeNames;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
new file mode 100644
index 0000000..7ac6b38
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
@@ -0,0 +1,43 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 椹冲洖鍙傛暟璇锋眰
+ *
+ * @author may
+ */
+@Data
+public class BackProcessBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟ID
+     */
+    @NotBlank(message = "浠诲姟ID涓嶈兘涓虹┖", groups = AddGroup.class)
+    private String taskId;
+
+    /**
+     * 娑堟伅绫诲瀷
+     */
+    private List<String> messageType;
+
+    /**
+     * 椹冲洖鐨勮妭鐐筰d(鐩墠鏈娇鐢紝鐩存帴椹冲洖鍒扮敵璇蜂汉)
+     */
+    private String targetActivityId;
+
+    /**
+     * 鍔炵悊鎰忚
+     */
+    private String message;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
new file mode 100644
index 0000000..0623905
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
@@ -0,0 +1,65 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.workflow.domain.vo.WfCopy;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍔炵悊浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class CompleteTaskBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟id
+     */
+    @NotBlank(message = "浠诲姟id涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String taskId;
+
+    /**
+     * 闄勪欢id
+     */
+    private String fileId;
+
+    /**
+     * 鎶勯�佷汉鍛�
+     */
+    private List<WfCopy> wfCopyList;
+
+    /**
+     * 娑堟伅绫诲瀷
+     */
+    private List<String> messageType;
+
+    /**
+     * 鍔炵悊鎰忚
+     */
+    private String message;
+
+    /**
+     * 娴佺▼鍙橀噺
+     */
+    private Map<String, Object> variables;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java
new file mode 100644
index 0000000..a6846a6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DelegateBo.java
@@ -0,0 +1,38 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 濮旀淳浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class DelegateBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 濮旀淳浜篿d
+     */
+    @NotBlank(message = "濮旀淳浜篿d涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String userId;
+
+    /**
+     * 濮旀淳浜哄悕绉�
+     */
+    @NotBlank(message = "濮旀淳浜哄悕绉颁笉鑳戒负绌�", groups = {AddGroup.class})
+    private String nickName;
+
+    /**
+     * 浠诲姟id
+     */
+    @NotBlank(message = "浠诲姟id涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String taskId;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java
new file mode 100644
index 0000000..e533167
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/DeleteMultiBo.java
@@ -0,0 +1,52 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 鍑忕鍙傛暟璇锋眰
+ *
+ * @author may
+ */
+@Data
+public class DeleteMultiBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟ID
+     */
+    @NotBlank(message = "浠诲姟ID涓嶈兘涓虹┖", groups = AddGroup.class)
+    private String taskId;
+
+    /**
+     * 鍑忕浜哄憳
+     */
+    @NotEmpty(message = "鍑忕浜哄憳涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<String> taskIds;
+
+    /**
+     * 鎵цid
+     */
+    @NotEmpty(message = "鎵цid涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<String> executionIds;
+
+    /**
+     * 浜哄憳id
+     */
+    @NotEmpty(message = "鍑忕浜哄憳id涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<Long> assigneeIds;
+
+    /**
+     * 浜哄憳鍚嶇О
+     */
+    @NotEmpty(message = "鍑忕浜哄憳涓嶈兘涓虹┖", groups = AddGroup.class)
+    private List<String> assigneeNames;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java
new file mode 100644
index 0000000..12efd87
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ModelBo.java
@@ -0,0 +1,69 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.workflow.common.PageEntity;
+import org.dromara.workflow.common.constant.FlowConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 妯″瀷璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ModelBo extends PageEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 妯″瀷id
+     */
+    @NotBlank(message = "妯″瀷ID涓嶈兘涓虹┖", groups = {EditGroup.class})
+    private String id;
+
+    /**
+     * 妯″瀷鍚嶇О
+     */
+    @NotBlank(message = "妯″瀷鍚嶇О涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String name;
+
+    /**
+     * 妯″瀷鏍囪瘑key
+     */
+    @NotBlank(message = "妯″瀷鏍囪瘑key涓嶈兘涓虹┖", groups = {AddGroup.class})
+    @Pattern(regexp = FlowConstant.MODEL_KEY_PATTERN, message = "妯″瀷鏍囪瘑key鍙兘瀛楃鎴栬�呬笅鍒掔嚎寮�澶�", groups = {AddGroup.class})
+    private String key;
+
+    /**
+     * 妯″瀷鍒嗙被
+     */
+    @NotBlank(message = "妯″瀷鍒嗙被涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String categoryCode;
+
+    /**
+     * 妯″瀷XML
+     */
+    @NotBlank(message = "妯″瀷XML涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String xml;
+
+    /**
+     * 妯″瀷SVG鍥剧墖
+     */
+    @NotBlank(message = "妯″瀷SVG涓嶈兘涓虹┖", groups = {EditGroup.class})
+    private String svg;
+
+    /**
+     * 澶囨敞
+     */
+    private String description;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java
new file mode 100644
index 0000000..86e9e01
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessDefinitionBo.java
@@ -0,0 +1,37 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.workflow.common.PageEntity;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 娴佺▼瀹氫箟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessDefinitionBo extends PageEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇Оkey
+     */
+    private String key;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 妯″瀷鍒嗙被
+     */
+    private String categoryCode;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java
new file mode 100644
index 0000000..ff8af23
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInstanceBo.java
@@ -0,0 +1,46 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.workflow.common.PageEntity;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 娴佺▼瀹炰緥璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessInstanceBo extends PageEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 娴佺▼key
+     */
+    private String key;
+
+    /**
+     * 浠诲姟鍙戣捣浜�
+     */
+    private String startUserId;
+
+    /**
+     * 涓氬姟id
+     */
+    private String businessKey;
+
+    /**
+     * 妯″瀷鍒嗙被
+     */
+    private String categoryCode;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java
new file mode 100644
index 0000000..35d5652
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/ProcessInvalidBo.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 娴佺▼瀹炰緥浣滃簾璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class ProcessInvalidBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    @NotBlank(message = "娴佺▼瀹炰緥id涓嶈兘涓虹┖", groups = {AddGroup.class})
+    private String processInstanceId;
+
+    /**
+     * 浣滃簾鍘熷洜
+     */
+    private String deleteReason;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
new file mode 100644
index 0000000..aff3f97
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java
@@ -0,0 +1,45 @@
+package org.dromara.workflow.domain.bo;
+
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 鍚姩娴佺▼瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class StartProcessBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓氬姟鍞竴鍊糹d
+     */
+    private String businessKey;
+
+    /**
+     * 娴佺▼鎵цkey
+     */
+    private String processKey;
+
+    /**
+     * 娴佺▼鍙橀噺锛屽墠绔細鎻愪氦涓�涓厓绱爗'entity': {涓氬姟璇︽儏鏁版嵁瀵硅薄}}
+     */
+    private Map<String, Object> variables;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java
new file mode 100644
index 0000000..b54f481
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/SysUserMultiBo.java
@@ -0,0 +1,34 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+import org.dromara.workflow.common.PageEntity;
+
+
+/**
+ * 鐢ㄦ埛鍔犵鏌ヨ
+ *
+ * @author may
+ */
+@Data
+
+public class SysUserMultiBo extends PageEntity {
+    /**
+     * 浜哄憳鍚嶇О
+     */
+    private String userName;
+
+    /**
+     * 浜哄憳鍚嶇О
+     */
+    private String nickName;
+
+    /**
+     * 閮ㄩ棬id
+     */
+    private String deptId;
+
+    /**
+     * 浠诲姟id
+     */
+    private String taskId;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java
new file mode 100644
index 0000000..cd2eead
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskBo.java
@@ -0,0 +1,36 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.workflow.common.PageEntity;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TaskBo extends PageEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String processDefinitionName;
+
+    /**
+     * 娴佺▼瀹氫箟key
+     */
+    private String processDefinitionKey;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java
new file mode 100644
index 0000000..20856ef
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TaskUrgingBo.java
@@ -0,0 +1,34 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 浠诲姟鍌姙
+ *
+ * @author may
+ */
+@Data
+public class TaskUrgingBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    private String processInstanceId;
+
+    /**
+     * 娑堟伅绫诲瀷
+     */
+    private List<String> messageType;
+
+    /**
+     * 鍌姙鍐呭锛堜负绌洪粯璁ょ郴缁熷唴缃俊鎭級
+     */
+    private String message;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java
new file mode 100644
index 0000000..61b7616
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TerminationBo.java
@@ -0,0 +1,37 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 缁堟浠诲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class TerminationBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟id
+     */
+    @NotBlank(message = "浠诲姟id涓虹┖", groups = AddGroup.class)
+    private String taskId;
+
+    /**
+     * 杞姙浜篿d
+     */
+    @NotBlank(message = "杞姙浜轰笉鑳戒负绌�", groups = AddGroup.class)
+    private String userId;
+
+    /**
+     * 瀹℃壒鎰忚
+     */
+    private String comment;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
new file mode 100644
index 0000000..e71be59
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java
@@ -0,0 +1,75 @@
+package org.dromara.workflow.domain.bo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.workflow.domain.TestLeave;
+
+import java.util.Date;
+
+/**
+ * 璇峰亣涓氬姟瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = TestLeave.class, reverseConvertGenerate = false)
+public class TestLeaveBo extends BaseEntity {
+
+    /**
+     * 涓婚敭
+     */
+    @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+    private Long id;
+
+    /**
+     * 璇峰亣绫诲瀷
+     */
+    @NotBlank(message = "璇峰亣绫诲瀷涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    private String leaveType;
+
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    @NotNull(message = "寮�濮嬫椂闂翠笉鑳戒负绌�", groups = {AddGroup.class, EditGroup.class})
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date startDate;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    @NotNull(message = "缁撴潫鏃堕棿涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date endDate;
+
+    /**
+     * 璇峰亣澶╂暟
+     */
+    @NotNull(message = "璇峰亣澶╂暟涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    private Integer leaveDays;
+
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    private Integer startLeaveDays;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    private Integer endLeaveDays;
+
+    /**
+     * 璇峰亣鍘熷洜
+     */
+    private String remark;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java
new file mode 100644
index 0000000..3eb6609
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TransmitBo.java
@@ -0,0 +1,37 @@
+package org.dromara.workflow.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.dromara.common.core.validate.AddGroup;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 缁堣浆鍔炲姟璇锋眰瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class TransmitBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟id
+     */
+    @NotBlank(message = "浠诲姟id涓虹┖", groups = AddGroup.class)
+    private String taskId;
+
+    /**
+     * 杞姙浜篿d
+     */
+    @NotBlank(message = "杞姙浜轰笉鑳戒负绌�", groups = AddGroup.class)
+    private String userId;
+
+    /**
+     * 瀹℃壒鎰忚
+     */
+    private String comment;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java
new file mode 100644
index 0000000..69608fd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/WfCategoryBo.java
@@ -0,0 +1,54 @@
+package org.dromara.workflow.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.workflow.domain.WfCategory;
+
+/**
+ * 娴佺▼鍒嗙被涓氬姟瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = WfCategory.class, reverseConvertGenerate = false)
+public class WfCategoryBo extends BaseEntity {
+
+    /**
+     * 涓婚敭
+     */
+    @NotNull(message = "涓婚敭涓嶈兘涓虹┖", groups = {EditGroup.class})
+    private Long id;
+
+    /**
+     * 鍒嗙被鍚嶇О
+     */
+    @NotBlank(message = "鍒嗙被鍚嶇О涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    private String categoryName;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @NotBlank(message = "鍒嗙被缂栫爜涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    private String categoryCode;
+
+    /**
+     * 鐖剁骇id
+     */
+    @NotNull(message = "鐖剁骇id涓嶈兘涓虹┖", groups = {AddGroup.class, EditGroup.class})
+    private Long parentId;
+
+    /**
+     * 鎺掑簭
+     */
+    private Long sortNum;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java
new file mode 100644
index 0000000..459b2d0
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ActHistoryInfoVo.java
@@ -0,0 +1,89 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.flowable.engine.task.Attachment;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹℃壒璁板綍瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class ActHistoryInfoVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * 浠诲姟id
+     */
+    private String id;
+    /**
+     * 鑺傜偣id
+     */
+    private String taskDefinitionKey;
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    private String name;
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    private String processInstanceId;
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    private Date startTime;
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    private Date endTime;
+    /**
+     * 杩愯鏃堕暱
+     */
+    private String runDuration;
+    /**
+     * 鐘舵��
+     */
+    private String status;
+    /**
+     * 鐘舵��
+     */
+    private String statusName;
+    /**
+     * 鍔炵悊浜篿d
+     */
+    private Long assignee;
+
+    /**
+     * 鍔炵悊浜哄悕绉�
+     */
+    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee")
+    private String nickName;
+
+    /**
+     * 鍔炵悊浜篿d
+     */
+    private String owner;
+
+    /**
+     * 瀹℃壒淇℃伅id
+     */
+    private String commentId;
+
+    /**
+     * 瀹℃壒淇℃伅
+     */
+    private String comment;
+
+    /**
+     * 瀹℃壒闄勪欢
+     */
+    private List<Attachment> attachmentList;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java
new file mode 100644
index 0000000..7636131
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GraphicInfoVo.java
@@ -0,0 +1,47 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鑺傜偣鍥惧舰淇℃伅
+ *
+ * @author may
+ */
+@Data
+public class GraphicInfoVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * x鍧愭爣
+     */
+    private double x;
+
+    /**
+     * y鍧愭爣
+     */
+    private double y;
+
+    /**
+     * 鑺傜偣楂樺害
+     */
+    private double height;
+
+    /**
+     * 鑺傜偣瀹藉害
+     */
+    private double width;
+
+    /**
+     * 鑺傜偣id
+     */
+    private String nodeId;
+
+    /**
+     * 鑺傜偣鍚嶇О
+     */
+    private String nodeName;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java
new file mode 100644
index 0000000..53e4bfc
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/GroupRepresentation.java
@@ -0,0 +1,27 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+/**
+ * @author Joram Barrez
+ */
+@Data
+public class GroupRepresentation {
+
+    protected String id;
+    protected String name;
+    protected String type;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java
new file mode 100644
index 0000000..b2ce811
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ModelVo.java
@@ -0,0 +1,48 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 妯″瀷瑙嗗浘瀵硅薄
+ *
+ * @author may
+ */
+@Data
+public class ModelVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 妯″瀷id
+     */
+    private String id;
+
+    /**
+     * 妯″瀷鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 妯″瀷鏍囪瘑key
+     */
+    private String key;
+
+    /**
+     * 妯″瀷鍒嗙被
+     */
+    private String categoryCode;
+
+    /**
+     * 妯″瀷XML
+     */
+    private String xml;
+
+    /**
+     * 澶囨敞
+     */
+    private String description;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java
new file mode 100644
index 0000000..b998396
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/MultiInstanceVo.java
@@ -0,0 +1,33 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 澶氬疄渚嬩俊鎭�
+ *
+ * @author may
+ */
+@Data
+public class MultiInstanceVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浼氱绫诲瀷锛堜覆琛岋紝骞惰锛�
+     */
+    private Object type;
+
+    /**
+     * 浼氱浜哄憳KEY
+     */
+    private String assignee;
+
+    /**
+     * 浼氱浜哄憳闆嗗悎KEY
+     */
+    private String assigneeList;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java
new file mode 100644
index 0000000..c5876f6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ParticipantVo.java
@@ -0,0 +1,43 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 鍙備笌鑰�
+ *
+ * @author may
+ */
+@Data
+public class ParticipantVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缁刬d锛堣鑹瞚d锛�
+     */
+    private List<Long> groupIds;
+
+    /**
+     * 鍊欓�変汉id锛堢敤鎴穒d锛� 褰撶粍id涓嶄负绌烘椂锛屽皢缁勫唴浜哄憳鏌ュ嚭鏀惧叆candidate
+     */
+    private List<Long> candidate;
+
+    /**
+     * 鍊欓�変汉鍚嶇О锛堢敤鎴峰悕绉帮級 褰撶粍id涓嶄负绌烘椂锛屽皢缁勫唴浜哄憳鏌ュ嚭鏀惧叆candidateName
+     */
+    private List<String> candidateName;
+
+    /**
+     * 鏄惁璁ら鏍囪瘑
+     * 褰撲负绌烘椂榛樿褰撳墠浠诲姟涓嶉渶瑕佽棰�
+     * 褰撲负true鏃跺綋鍓嶄换鍔¤鏄庝负鍊欓�夋ā寮忓苟涓旀湁浜哄凡缁忚棰嗕簡浠诲姟鍙互褰掕繕锛�
+     * 褰撲负false鏃跺綋鍓嶄换鍔¤鏄庝负鍊欓�夋ā寮忚浠诲姟鏈棰嗭紝
+     */
+    private Boolean claim;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java
new file mode 100644
index 0000000..52c201d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessDefinitionVo.java
@@ -0,0 +1,65 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 娴佺▼瀹氫箟瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class ProcessDefinitionVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹氫箟id
+     */
+    private String id;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 娴佺▼瀹氫箟鏍囪瘑key
+     */
+    private String key;
+
+    /**
+     * 娴佺▼瀹氫箟鐗堟湰
+     */
+    private int version;
+
+    /**
+     * 娴佺▼瀹氫箟鎸傝捣鎴栨縺娲� 1婵�娲� 2鎸傝捣
+     */
+    private int suspensionState;
+
+    /**
+     * 娴佺▼xml鍚嶇О
+     */
+    private String resourceName;
+
+    /**
+     * 娴佺▼鍥剧墖鍚嶇О
+     */
+    private String diagramResourceName;
+
+    /**
+     * 娴佺▼閮ㄧ讲id
+     */
+    private String deploymentId;
+
+    /**
+     * 娴佺▼閮ㄧ讲鏃堕棿
+     */
+    private Date deploymentTime;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java
new file mode 100644
index 0000000..3917f4f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ProcessInstanceVo.java
@@ -0,0 +1,95 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹炰緥瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class ProcessInstanceVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    private String id;
+
+    /**
+     * 娴佺▼瀹氫箟id
+     */
+    private String processDefinitionId;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String processDefinitionName;
+
+    /**
+     * 娴佺▼瀹氫箟key
+     */
+    private String processDefinitionKey;
+
+    /**
+     * 娴佺▼瀹氫箟鐗堟湰
+     */
+    private String processDefinitionVersion;
+
+    /**
+     * 閮ㄧ讲id
+     */
+    private String deploymentId;
+
+    /**
+     * 涓氬姟id
+     */
+    private String businessKey;
+
+    /**
+     * 鏄惁鎸傝捣
+     */
+    private Boolean isSuspended;
+
+    /**
+     * 绉熸埛id
+     */
+    private String tenantId;
+
+    /**
+     * 鍚姩鏃堕棿
+     */
+    private Date startTime;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    private Date endTime;
+
+    /**
+     * 鍚姩浜篿d
+     */
+    private String startUserId;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    private String businessStatus;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    private String businessStatusName;
+
+    /**
+     * 寰呭姙浠诲姟闆嗗悎
+     */
+    private List<TaskVo> taskVoList;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java
new file mode 100644
index 0000000..fad9d90
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TaskVo.java
@@ -0,0 +1,143 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.util.Date;
+
+/**
+ * 浠诲姟瑙嗗浘
+ *
+ * @author may
+ */
+@Data
+public class TaskVo {
+
+    /**
+     * 浠诲姟id
+     */
+    private String id;
+
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    private String name;
+
+    /**
+     * 鎻忚堪
+     */
+    private String description;
+
+    /**
+     * 浼樺厛绾�
+     */
+    private Integer priority;
+
+    /**
+     * 璐熻矗姝や换鍔$殑浜哄憳鐨勭敤鎴穒d
+     */
+    private String owner;
+
+    /**
+     * 鍔炵悊浜篿d
+     */
+    private Long assignee;
+
+    /**
+     * 鍔炵悊浜�
+     */
+    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assignee")
+    private String assigneeName;
+
+
+    /**
+     * 娴佺▼瀹炰緥id
+     */
+    private String processInstanceId;
+
+    /**
+     * 鎵цid
+     */
+    private String executionId;
+
+    /**
+     * 鏃犵敤
+     */
+    private String taskDefinitionId;
+
+    /**
+     * 娴佺▼瀹氫箟id
+     */
+    private String processDefinitionId;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createTime;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    private Date endTime;
+
+    /**
+     * 鑺傜偣id
+     */
+    private String taskDefinitionKey;
+
+    /**
+     * 浠诲姟鎴鏃ユ湡
+     */
+    private Date dueDate;
+
+    /**
+     * 娴佺▼绫诲埆
+     */
+    private String category;
+
+    /**
+     * 鐖剁骇浠诲姟id
+     */
+    private String parentTaskId;
+
+    /**
+     * 绉熸埛id
+     */
+    private String tenantId;
+
+    /**
+     * 璁ら鏃堕棿
+     */
+    private Date claimTime;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    private String businessStatus;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    private String businessStatusName;
+
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String processDefinitionName;
+
+    /**
+     * 娴佺▼瀹氫箟key
+     */
+    private String processDefinitionKey;
+
+    /**
+     * 鍙備笌鑰�
+     */
+    private ParticipantVo participantVo;
+
+    /**
+     * 鏄惁浼氱
+     */
+    private Boolean multiInstance;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
new file mode 100644
index 0000000..c62a356
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
@@ -0,0 +1,70 @@
+package org.dromara.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.workflow.domain.TestLeave;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 璇峰亣瑙嗗浘瀵硅薄 test_leave
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = TestLeave.class)
+public class TestLeaveVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭
+     */
+    @ExcelProperty(value = "涓婚敭")
+    private Long id;
+
+    /**
+     * 璇峰亣绫诲瀷
+     */
+    @ExcelProperty(value = "璇峰亣绫诲瀷")
+    private String leaveType;
+
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    @ExcelProperty(value = "寮�濮嬫椂闂�")
+    private Date startDate;
+
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    @ExcelProperty(value = "缁撴潫鏃堕棿")
+    private Date endDate;
+
+    /**
+     * 璇峰亣澶╂暟
+     */
+    @ExcelProperty(value = "璇峰亣澶╂暟")
+    private Integer leaveDays;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "璇峰亣鍘熷洜")
+    private String remark;
+
+    /**
+     * 娴佺▼瀹炰緥瀵硅薄
+     */
+    private ProcessInstanceVo processInstanceVo;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java
new file mode 100644
index 0000000..362f646
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCategoryVo.java
@@ -0,0 +1,58 @@
+package org.dromara.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.workflow.domain.WfCategory;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 娴佺▼鍒嗙被瑙嗗浘瀵硅薄 wf_category
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = WfCategory.class)
+public class WfCategoryVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭
+     */
+    @ExcelProperty(value = "涓婚敭")
+    private Long id;
+
+    /**
+     * 鍒嗙被鍚嶇О
+     */
+    @ExcelProperty(value = "鍒嗙被鍚嶇О")
+    private String categoryName;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @ExcelProperty(value = "鍒嗙被缂栫爜")
+    private String categoryCode;
+
+    /**
+     * 鐖剁骇id
+     */
+    @ExcelProperty(value = "鐖剁骇id")
+    private Long parentId;
+
+    /**
+     * 鎺掑簭
+     */
+    @ExcelProperty(value = "鎺掑簭")
+    private Long sortNum;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java
new file mode 100644
index 0000000..b1d41bb
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/WfCopy.java
@@ -0,0 +1,23 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 鎶勯��
+ *
+ * @author may
+ */
+@Data
+public class WfCopy {
+
+    /**
+     * 鐢ㄦ埛id
+     */
+    private Long userId;
+
+    /**
+     * 鐢ㄦ埛鍚嶇О
+     */
+    private String userName;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java
new file mode 100644
index 0000000..39fd9d3
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramCanvas.java
@@ -0,0 +1,108 @@
+package org.dromara.workflow.flowable;
+
+import org.flowable.bpmn.model.AssociationDirection;
+import org.flowable.image.impl.DefaultProcessDiagramCanvas;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.RoundRectangle2D;
+
+public class CustomDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
+    //璁剧疆楂樹寒绾跨殑棰滆壊  杩欓噷鎴戣缃垚缁胯壊
+    protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN;
+
+    public CustomDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
+        super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
+    }
+
+    /**
+     * 鐢荤嚎棰滆壊璁剧疆
+     */
+    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType,
+                               AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
+
+        Paint originalPaint = g.getPaint();
+        Stroke originalStroke = g.getStroke();
+
+        g.setPaint(CONNECTION_COLOR);
+        if (connectionType.equals("association")) {
+            g.setStroke(ASSOCIATION_STROKE);
+        } else if (highLighted) {
+            //璁剧疆绾跨殑棰滆壊
+            g.setPaint(HIGHLIGHT_SEQUENCEFLOW_COLOR);
+            g.setStroke(HIGHLIGHT_FLOW_STROKE);
+        }
+
+        for (int i = 1; i < xPoints.length; i++) {
+            Integer sourceX = xPoints[i - 1];
+            Integer sourceY = yPoints[i - 1];
+            Integer targetX = xPoints[i];
+            Integer targetY = yPoints[i];
+            Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
+            g.draw(line);
+        }
+
+        if (isDefault) {
+            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
+            drawDefaultSequenceFlowIndicator(line, scaleFactor);
+        }
+
+        if (conditional) {
+            Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
+            drawConditionalSequenceFlowIndicator(line, scaleFactor);
+        }
+
+        if (associationDirection == AssociationDirection.ONE || associationDirection == AssociationDirection.BOTH) {
+            Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
+            drawArrowHead(line, scaleFactor);
+        }
+        if (associationDirection == AssociationDirection.BOTH) {
+            Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
+            drawArrowHead(line, scaleFactor);
+        }
+        g.setPaint(originalPaint);
+        g.setStroke(originalStroke);
+    }
+
+    /**
+     * 楂樹寒鑺傜偣璁剧疆
+     */
+    public void drawHighLight(int x, int y, int width, int height) {
+        Paint originalPaint = g.getPaint();
+        Stroke originalStroke = g.getStroke();
+        //璁剧疆楂樹寒鑺傜偣鐨勯鑹�
+        g.setPaint(HIGHLIGHT_COLOR);
+        g.setStroke(THICK_TASK_BORDER_STROKE);
+
+        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
+        g.draw(rect);
+
+        g.setPaint(originalPaint);
+        g.setStroke(originalStroke);
+    }
+
+    /**
+     * @description: 楂樹寒鑺傜偣绾㈣壊
+     * @param: x
+     * @param: y
+     * @param: width
+     * @param: height
+     * @return: void
+     * @author: gssong
+     * @date: 2022/4/12
+     */
+    public void drawHighLightRed(int x, int y, int width, int height) {
+        Paint originalPaint = g.getPaint();
+        Stroke originalStroke = g.getStroke();
+        //璁剧疆楂樹寒鑺傜偣鐨勯鑹�
+        g.setPaint(Color.green);
+        g.setStroke(THICK_TASK_BORDER_STROKE);
+
+        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
+        g.draw(rect);
+
+        g.setPaint(originalPaint);
+        g.setStroke(originalStroke);
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java
new file mode 100644
index 0000000..e4793a2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/CustomDefaultProcessDiagramGenerator.java
@@ -0,0 +1,1120 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dromara.workflow.flowable;
+
+import org.flowable.bpmn.model.Event;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.image.ProcessDiagramGenerator;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.util.List;
+import java.util.*;
+
+/**
+ * Class to generate an image based the diagram interchange information in a BPMN 2.0 process.
+ *
+ * @author Joram Barrez
+ * @author Tijs Rademakers
+ * @author Zheng Ji
+ */
+public class CustomDefaultProcessDiagramGenerator implements ProcessDiagramGenerator {
+
+    protected Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions = new HashMap<>();
+    protected Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<>();
+
+    public CustomDefaultProcessDiagramGenerator() {
+        this(1.0);
+    }
+
+    // The instructions on how to draw a certain construct is
+    // created statically and stored in a map for performance.
+    public CustomDefaultProcessDiagramGenerator(final double scaleFactor) {
+        // start event
+        activityDrawInstructions.put(StartEvent.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                StartEvent startEvent = (StartEvent) flowNode;
+                if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) {
+                    EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0);
+                    if (eventDefinition instanceof TimerEventDefinition) {
+                        processDiagramCanvas.drawTimerStartEvent(graphicInfo, scaleFactor);
+                    } else if (eventDefinition instanceof ErrorEventDefinition) {
+                        processDiagramCanvas.drawErrorStartEvent(graphicInfo, scaleFactor);
+                    } else if (eventDefinition instanceof EscalationEventDefinition) {
+                        processDiagramCanvas.drawEscalationStartEvent(graphicInfo, scaleFactor);
+                    } else if (eventDefinition instanceof ConditionalEventDefinition) {
+                        processDiagramCanvas.drawConditionalStartEvent(graphicInfo, scaleFactor);
+                    } else if (eventDefinition instanceof SignalEventDefinition) {
+                        processDiagramCanvas.drawSignalStartEvent(graphicInfo, scaleFactor);
+                    } else if (eventDefinition instanceof MessageEventDefinition) {
+                        processDiagramCanvas.drawMessageStartEvent(graphicInfo, scaleFactor);
+                    } else {
+                        processDiagramCanvas.drawNoneStartEvent(graphicInfo);
+                    }
+                } else {
+                    List<ExtensionElement> eventTypeElements = startEvent.getExtensionElements().get("eventType");
+                    if (eventTypeElements != null && eventTypeElements.size() > 0) {
+                        processDiagramCanvas.drawEventRegistryStartEvent(graphicInfo, scaleFactor);
+
+                    } else {
+                        processDiagramCanvas.drawNoneStartEvent(graphicInfo);
+                    }
+                }
+            }
+        });
+
+        // signal catch
+        activityDrawInstructions.put(IntermediateCatchEvent.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode;
+                if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent.getEventDefinitions().isEmpty()) {
+
+                    if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
+                        processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
+                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
+                        processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
+                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
+                        processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
+                    } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof ConditionalEventDefinition) {
+                        processDiagramCanvas.drawCatchingConditionalEvent(flowNode.getName(), graphicInfo, true, scaleFactor);
+                    }
+                }
+            }
+        });
+
+        // signal throw
+        activityDrawInstructions.put(ThrowEvent.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                ThrowEvent throwEvent = (ThrowEvent) flowNode;
+                if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) {
+                    if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
+                        processDiagramCanvas.drawThrowingSignalEvent(graphicInfo, scaleFactor);
+                    } else if (throwEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) {
+                        processDiagramCanvas.drawThrowingEscalationEvent(graphicInfo, scaleFactor);
+                    } else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
+                        processDiagramCanvas.drawThrowingCompensateEvent(graphicInfo, scaleFactor);
+                    } else {
+                        processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
+                    }
+                } else {
+                    processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor);
+                }
+            }
+        });
+
+        // end event
+        activityDrawInstructions.put(EndEvent.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                EndEvent endEvent = (EndEvent) flowNode;
+                if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) {
+                    if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
+                        processDiagramCanvas.drawErrorEndEvent(flowNode.getName(), graphicInfo, scaleFactor);
+                    } else if (endEvent.getEventDefinitions().get(0) instanceof EscalationEventDefinition) {
+                        processDiagramCanvas.drawEscalationEndEvent(flowNode.getName(), graphicInfo, scaleFactor);
+                    } else {
+                        processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
+                    }
+                } else {
+                    processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor);
+                }
+            }
+        });
+
+        // task
+        activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // user task
+        activityDrawInstructions.put(UserTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawUserTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // script task
+        activityDrawInstructions.put(ScriptTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawScriptTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // service task
+        activityDrawInstructions.put(ServiceTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                ServiceTask serviceTask = (ServiceTask) flowNode;
+                if ("camel".equalsIgnoreCase(serviceTask.getType())) {
+                    processDiagramCanvas.drawCamelTask(serviceTask.getName(), graphicInfo, scaleFactor);
+                }else if (ServiceTask.HTTP_TASK.equalsIgnoreCase(serviceTask.getType())) {
+                    processDiagramCanvas.drawHttpTask(serviceTask.getName(), graphicInfo, scaleFactor);
+                } else if (ServiceTask.DMN_TASK.equalsIgnoreCase(serviceTask.getType())) {
+                    processDiagramCanvas.drawDMNTask(serviceTask.getName(), graphicInfo, scaleFactor);
+                } else if (ServiceTask.SHELL_TASK.equalsIgnoreCase(serviceTask.getType())) {
+                    processDiagramCanvas.drawShellTask(serviceTask.getName(), graphicInfo, scaleFactor);
+                } else {
+                    processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor);
+                }
+            }
+        });
+
+        // http service task
+        activityDrawInstructions.put(HttpServiceTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawHttpTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // receive task
+        activityDrawInstructions.put(ReceiveTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawReceiveTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // send task
+        activityDrawInstructions.put(SendTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawSendTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // manual task
+        activityDrawInstructions.put(ManualTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawManualTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // send event service task
+        activityDrawInstructions.put(SendEventServiceTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawSendEventServiceTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // external worker service task
+        activityDrawInstructions.put(ExternalWorkerServiceTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                ServiceTask serviceTask = (ServiceTask) flowNode;
+                processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // case service task
+        activityDrawInstructions.put(CaseServiceTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawCaseServiceTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // businessRuleTask task
+        activityDrawInstructions.put(BusinessRuleTask.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawBusinessRuleTask(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // exclusive gateway
+        activityDrawInstructions.put(ExclusiveGateway.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawExclusiveGateway(graphicInfo, scaleFactor);
+            }
+        });
+
+        // inclusive gateway
+        activityDrawInstructions.put(InclusiveGateway.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawInclusiveGateway(graphicInfo, scaleFactor);
+            }
+        });
+
+        // parallel gateway
+        activityDrawInstructions.put(ParallelGateway.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawParallelGateway(graphicInfo, scaleFactor);
+            }
+        });
+
+        // event based gateway
+        activityDrawInstructions.put(EventGateway.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawEventBasedGateway(graphicInfo, scaleFactor);
+            }
+        });
+
+        // Boundary timer
+        activityDrawInstructions.put(BoundaryEvent.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode;
+                if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) {
+                    EventDefinition eventDefinition = boundaryEvent.getEventDefinitions().get(0);
+                    if (eventDefinition instanceof TimerEventDefinition) {
+                        processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof ConditionalEventDefinition) {
+                        processDiagramCanvas.drawCatchingConditionalEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof ErrorEventDefinition) {
+                        processDiagramCanvas.drawCatchingErrorEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof EscalationEventDefinition) {
+                        processDiagramCanvas.drawCatchingEscalationEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof SignalEventDefinition) {
+                        processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof MessageEventDefinition) {
+                        processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+
+                    } else if (eventDefinition instanceof CompensateEventDefinition) {
+                        processDiagramCanvas.drawCatchingCompensateEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+                    }
+
+                } else {
+                    List<ExtensionElement> eventTypeElements = boundaryEvent.getExtensionElements().get("eventType");
+                    if (eventTypeElements != null && eventTypeElements.size() > 0) {
+                        processDiagramCanvas.drawCatchingEventRegistryEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor);
+                    }
+                }
+            }
+        });
+
+        // subprocess
+        activityDrawInstructions.put(SubProcess.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
+                } else {
+                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
+                }
+            }
+        });
+
+        // transaction
+        activityDrawInstructions.put(Transaction.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
+                } else {
+                    processDiagramCanvas.drawExpandedTransaction(flowNode.getName(), graphicInfo, scaleFactor);
+                }
+            }
+        });
+
+        // Event subprocess
+        activityDrawInstructions.put(EventSubProcess.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor);
+                } else {
+                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor);
+                }
+            }
+        });
+
+        // Adhoc subprocess
+        activityDrawInstructions.put(AdhocSubProcess.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                    processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
+                } else {
+                    processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor);
+                }
+            }
+        });
+
+        // call activity
+        activityDrawInstructions.put(CallActivity.class, new ActivityDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+                processDiagramCanvas.drawCollapsedCallActivity(flowNode.getName(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // text annotation
+        artifactDrawInstructions.put(TextAnnotation.class, new ArtifactDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
+                TextAnnotation textAnnotation = (TextAnnotation) artifact;
+                processDiagramCanvas.drawTextAnnotation(textAnnotation.getText(), graphicInfo, scaleFactor);
+            }
+        });
+
+        // association
+        artifactDrawInstructions.put(Association.class, new ArtifactDrawInstruction() {
+
+            @Override
+            public void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
+                Association association = (Association) artifact;
+                String sourceRef = association.getSourceRef();
+                String targetRef = association.getTargetRef();
+
+                // source and target can be instance of FlowElement or Artifact
+                BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef);
+                BaseElement targetElement = bpmnModel.getFlowElement(targetRef);
+                if (sourceElement == null) {
+                    sourceElement = bpmnModel.getArtifact(sourceRef);
+                }
+                if (targetElement == null) {
+                    targetElement = bpmnModel.getArtifact(targetRef);
+                }
+                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
+                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
+                int[] xPoints = new int[graphicInfoList.size()];
+                int[] yPoints = new int[graphicInfoList.size()];
+                for (int i = 1; i < graphicInfoList.size(); i++) {
+                    GraphicInfo graphicInfo = graphicInfoList.get(i);
+                    GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
+
+                    if (i == 1) {
+                        xPoints[0] = (int) previousGraphicInfo.getX();
+                        yPoints[0] = (int) previousGraphicInfo.getY();
+                    }
+                    xPoints[i] = (int) graphicInfo.getX();
+                    yPoints[i] = (int) graphicInfo.getY();
+                }
+
+                AssociationDirection associationDirection = association.getAssociationDirection();
+                processDiagramCanvas.drawAssociation(xPoints, yPoints, associationDirection, false, scaleFactor);
+            }
+        });
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows,
+                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows,
+            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateImage(imageType);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, 1.0, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
+                                       List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
+                                       String labelFontName, String annotationFontName, ClassLoader customClassLoader, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(),
+            activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName,
+                                       String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(),
+            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generatePngDiagram(BpmnModel bpmnModel, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generatePngDiagram(bpmnModel, 1.0, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generatePngDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public InputStream generateJpgDiagram(BpmnModel bpmnModel) {
+        return generateJpgDiagram(bpmnModel, 1.0, false);
+    }
+
+    @Override
+    public InputStream generateJpgDiagram(BpmnModel bpmnModel, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+        return generateDiagram(bpmnModel, "jpg", Collections.emptyList(), Collections.emptyList(), drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows,
+                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows,
+            activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, drawSequenceFlowNameWithNoLabelDI).generateBufferedImage(imageType);
+    }
+
+    public BufferedImage generateImage(BpmnModel bpmnModel, String imageType,
+                                       List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        return generateImage(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+    }
+
+    @Override
+    public BufferedImage generatePngImage(BpmnModel bpmnModel, double scaleFactor) {
+        return generateImage(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor, false);
+    }
+
+    protected CustomDefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,
+                                                                       List<String> highLightedActivities, List<String> highLightedFlows,
+                                                                       String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        prepareBpmnModel(bpmnModel);
+
+        CustomDefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
+
+        // Draw pool shape, if process is participant in collaboration
+        for (Pool pool : bpmnModel.getPools()) {
+            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
+            processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo, scaleFactor);
+        }
+
+        // Draw lanes
+        for (Process process : bpmnModel.getProcesses()) {
+            for (Lane lane : process.getLanes()) {
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
+                processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo, scaleFactor);
+            }
+        }
+
+        // Draw activities and their sequence-flows
+        for (Process process : bpmnModel.getProcesses()) {
+            for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) {
+                if (!isPartOfCollapsedSubProcess(flowNode, bpmnModel)) {
+                    drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+                }
+            }
+        }
+
+        // Draw artifacts
+        for (Process process : bpmnModel.getProcesses()) {
+
+            for (Artifact artifact : process.getArtifacts()) {
+                drawArtifact(processDiagramCanvas, bpmnModel, artifact);
+            }
+
+            List<SubProcess> subProcesses = process.findFlowElementsOfType(SubProcess.class, true);
+            if (subProcesses != null) {
+                for (SubProcess subProcess : subProcesses) {
+
+                    GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(subProcess.getId());
+                    if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                        continue;
+                    }
+
+                    if (!isPartOfCollapsedSubProcess(subProcess, bpmnModel)) {
+                        for (Artifact subProcessArtifact : subProcess.getArtifacts()) {
+                            drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
+                        }
+                    }
+                }
+            }
+        }
+
+        return processDiagramCanvas;
+    }
+
+    protected void prepareBpmnModel(BpmnModel bpmnModel) {
+
+        // Need to make sure all elements have positive x and y.
+        // Check all graphicInfo and update the elements accordingly
+
+        List<GraphicInfo> allGraphicInfos = new ArrayList<>();
+        if (bpmnModel.getLocationMap() != null) {
+            allGraphicInfos.addAll(bpmnModel.getLocationMap().values());
+        }
+        if (bpmnModel.getLabelLocationMap() != null) {
+            allGraphicInfos.addAll(bpmnModel.getLabelLocationMap().values());
+        }
+        if (bpmnModel.getFlowLocationMap() != null) {
+            for (List<GraphicInfo> flowGraphicInfos : bpmnModel.getFlowLocationMap().values()) {
+                allGraphicInfos.addAll(flowGraphicInfos);
+            }
+        }
+
+        if (allGraphicInfos.size() > 0) {
+
+            boolean needsTranslationX = false;
+            boolean needsTranslationY = false;
+
+            double lowestX = 0.0;
+            double lowestY = 0.0;
+
+            // Collect lowest x and y
+            for (GraphicInfo graphicInfo : allGraphicInfos) {
+
+                double x = graphicInfo.getX();
+                double y = graphicInfo.getY();
+
+                if (x < lowestX) {
+                    needsTranslationX = true;
+                    lowestX = x;
+                }
+                if (y < lowestY) {
+                    needsTranslationY = true;
+                    lowestY = y;
+                }
+
+            }
+
+            // Update all graphicInfo objects
+            if (needsTranslationX || needsTranslationY) {
+
+                double translationX = Math.abs(lowestX);
+                double translationY = Math.abs(lowestY);
+
+                for (GraphicInfo graphicInfo : allGraphicInfos) {
+                    if (needsTranslationX) {
+                        graphicInfo.setX(graphicInfo.getX() + translationX);
+                    }
+                    if (needsTranslationY) {
+                        graphicInfo.setY(graphicInfo.getY() + translationY);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    protected void drawActivity(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel,
+                                FlowNode flowNode, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) {
+
+        ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass());
+        if (drawInstruction != null) {
+
+            drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);
+
+            // Gather info on the multi instance marker
+            boolean multiInstanceSequential = false;
+            boolean multiInstanceParallel = false;
+            boolean collapsed = false;
+            if (flowNode instanceof Activity) {
+                Activity activity = (Activity) flowNode;
+                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
+                if (multiInstanceLoopCharacteristics != null) {
+                    multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
+                    multiInstanceParallel = !multiInstanceSequential;
+                }
+            }
+
+            // Gather info on the collapsed marker
+            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+            if (flowNode instanceof SubProcess) {
+                collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded();
+            } else if (flowNode instanceof CallActivity) {
+                collapsed = true;
+            }
+
+            if (scaleFactor == 1.0) {
+                // Actually draw the markers
+                processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(),
+                    multiInstanceSequential, multiInstanceParallel, collapsed);
+            }
+
+            // Draw highlighted activities
+            if (highLightedActivities.contains(flowNode.getId())) {
+                drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+            } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) {
+                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+            }
+
+        } else if (flowNode instanceof Task) {
+            activityDrawInstructions.get(Task.class).draw(processDiagramCanvas, bpmnModel, flowNode);
+
+            if (highLightedActivities.contains(flowNode.getId())) {
+                drawHighLightRed(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+            } else if (highLightedActivities.contains(Color.RED.toString() + flowNode.getId())) {
+                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+            }
+        }
+
+        // Outgoing transitions of activity
+        for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
+            boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId()));
+            String defaultFlow = null;
+            if (flowNode instanceof Activity) {
+                defaultFlow = ((Activity) flowNode).getDefaultFlow();
+            } else if (flowNode instanceof Gateway) {
+                defaultFlow = ((Gateway) flowNode).getDefaultFlow();
+            }
+
+            boolean isDefault = false;
+            if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
+                isDefault = true;
+            }
+            boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && sequenceFlow.getConditionExpression().trim().length() > 0 && !(flowNode instanceof Gateway);
+
+            String sourceRef = sequenceFlow.getSourceRef();
+            String targetRef = sequenceFlow.getTargetRef();
+            FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
+            FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
+            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
+            if (graphicInfoList != null && graphicInfoList.size() > 0) {
+                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
+                int[] xPoints = new int[graphicInfoList.size()];
+                int[] yPoints = new int[graphicInfoList.size()];
+
+                for (int i = 1; i < graphicInfoList.size(); i++) {
+                    GraphicInfo graphicInfo = graphicInfoList.get(i);
+                    GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
+
+                    if (i == 1) {
+                        xPoints[0] = (int) previousGraphicInfo.getX();
+                        yPoints[0] = (int) previousGraphicInfo.getY();
+                    }
+                    xPoints[i] = (int) graphicInfo.getX();
+                    yPoints[i] = (int) graphicInfo.getY();
+
+                }
+
+                processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor);
+
+                // Draw sequenceflow label
+                GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
+                if (labelGraphicInfo != null) {
+                    processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false);
+                } else {
+                    if (drawSequenceFlowNameWithNoLabelDI) {
+                        GraphicInfo lineCenter = getLineCenter(graphicInfoList);
+                        processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false);
+                    }
+
+                }
+            }
+        }
+
+        // Nested elements
+        if (flowNode instanceof FlowElementsContainer) {
+            for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
+                if (nestedFlowElement instanceof FlowNode && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) {
+                    drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement,
+                        highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method makes coordinates of connection flow better.
+     *
+     * @param processDiagramCanvas
+     * @param bpmnModel
+     * @param sourceElement
+     * @param targetElement
+     * @param graphicInfoList
+     * @return
+     */
+    protected static List<GraphicInfo> connectionPerfectionizer(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, BaseElement sourceElement, BaseElement targetElement, List<GraphicInfo> graphicInfoList) {
+        GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
+        GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId());
+
+        CustomDefaultProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
+        CustomDefaultProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement);
+
+        return processDiagramCanvas.connectionPerfectionizer(sourceShapeType, targetShapeType, sourceGraphicInfo, targetGraphicInfo, graphicInfoList);
+    }
+
+    /**
+     * This method returns shape type of base element.<br>
+     * Each element can be presented as rectangle, rhombus, or ellipse.
+     *
+     * @param baseElement
+     * @return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE
+     */
+    protected static CustomDefaultProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) {
+        if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) {
+            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
+        } else if (baseElement instanceof Gateway) {
+            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
+        } else if (baseElement instanceof Event) {
+            return CustomDefaultProcessDiagramCanvas.SHAPE_TYPE.Ellipse;
+        } else {
+            // unknown source element, just do not correct coordinates
+        }
+        return null;
+    }
+
+    protected static GraphicInfo getLineCenter(List<GraphicInfo> graphicInfoList) {
+        GraphicInfo gi = new GraphicInfo();
+
+        int[] xPoints = new int[graphicInfoList.size()];
+        int[] yPoints = new int[graphicInfoList.size()];
+
+        double length = 0;
+        double[] lengths = new double[graphicInfoList.size()];
+        lengths[0] = 0;
+        double m;
+        for (int i = 1; i < graphicInfoList.size(); i++) {
+            GraphicInfo graphicInfo = graphicInfoList.get(i);
+            GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);
+
+            if (i == 1) {
+                xPoints[0] = (int) previousGraphicInfo.getX();
+                yPoints[0] = (int) previousGraphicInfo.getY();
+            }
+            xPoints[i] = (int) graphicInfo.getX();
+            yPoints[i] = (int) graphicInfo.getY();
+
+            length += Math.sqrt(
+                Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(), 2) +
+                    Math.pow((int) graphicInfo.getY() - (int) previousGraphicInfo.getY(), 2));
+            lengths[i] = length;
+        }
+        m = length / 2;
+        int p1 = 0;
+        int p2 = 1;
+        for (int i = 1; i < lengths.length; i++) {
+            double len = lengths[i];
+            p1 = i - 1;
+            p2 = i;
+            if (len > m) {
+                break;
+            }
+        }
+
+        GraphicInfo graphicInfo1 = graphicInfoList.get(p1);
+        GraphicInfo graphicInfo2 = graphicInfoList.get(p2);
+
+        double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX();
+        double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY();
+        double OB = lengths[p2] - lengths[p1];
+        double ob = m - lengths[p1];
+        double ab = AB * ob / OB;
+        double oa = OA * ob / OB;
+
+        double mx = graphicInfo1.getX() + ab;
+        double my = graphicInfo1.getY() + oa;
+
+        gi.setX(mx);
+        gi.setY(my);
+        return gi;
+    }
+
+    protected void drawArtifact(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) {
+
+        ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass());
+        if (drawInstruction != null) {
+            drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact);
+        }
+    }
+
+    private static void drawHighLight(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
+        processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
+
+    }
+
+    private static void drawHighLightRed(CustomDefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
+        processDiagramCanvas.drawHighLightRed((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
+
+    }
+
+    protected static CustomDefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType,
+                                                                                String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
+
+        // We need to calculate maximum values to know how big the image will be in its entirety
+        double minX = Double.MAX_VALUE;
+        double maxX = 0;
+        double minY = Double.MAX_VALUE;
+        double maxY = 0;
+
+        for (Pool pool : bpmnModel.getPools()) {
+            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
+            minX = graphicInfo.getX();
+            maxX = graphicInfo.getX() + graphicInfo.getWidth();
+            minY = graphicInfo.getY();
+            maxY = graphicInfo.getY() + graphicInfo.getHeight();
+        }
+
+        List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
+        for (FlowNode flowNode : flowNodes) {
+
+            GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
+
+            // width
+            if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
+                maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
+            }
+            if (flowNodeGraphicInfo.getX() < minX) {
+                minX = flowNodeGraphicInfo.getX();
+            }
+            // height
+            if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
+                maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
+            }
+            if (flowNodeGraphicInfo.getY() < minY) {
+                minY = flowNodeGraphicInfo.getY();
+            }
+
+            for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
+                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
+                if (graphicInfoList != null) {
+                    for (GraphicInfo graphicInfo : graphicInfoList) {
+                        // width
+                        if (graphicInfo.getX() > maxX) {
+                            maxX = graphicInfo.getX();
+                        }
+                        if (graphicInfo.getX() < minX) {
+                            minX = graphicInfo.getX();
+                        }
+                        // height
+                        if (graphicInfo.getY() > maxY) {
+                            maxY = graphicInfo.getY();
+                        }
+                        if (graphicInfo.getY() < minY) {
+                            minY = graphicInfo.getY();
+                        }
+                    }
+                }
+            }
+        }
+
+        List<Artifact> artifacts = gatherAllArtifacts(bpmnModel);
+        for (Artifact artifact : artifacts) {
+
+            GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
+
+            if (artifactGraphicInfo != null) {
+                // width
+                if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) {
+                    maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth();
+                }
+                if (artifactGraphicInfo.getX() < minX) {
+                    minX = artifactGraphicInfo.getX();
+                }
+                // height
+                if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) {
+                    maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight();
+                }
+                if (artifactGraphicInfo.getY() < minY) {
+                    minY = artifactGraphicInfo.getY();
+                }
+            }
+
+            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
+            if (graphicInfoList != null) {
+                for (GraphicInfo graphicInfo : graphicInfoList) {
+                    // width
+                    if (graphicInfo.getX() > maxX) {
+                        maxX = graphicInfo.getX();
+                    }
+                    if (graphicInfo.getX() < minX) {
+                        minX = graphicInfo.getX();
+                    }
+                    // height
+                    if (graphicInfo.getY() > maxY) {
+                        maxY = graphicInfo.getY();
+                    }
+                    if (graphicInfo.getY() < minY) {
+                        minY = graphicInfo.getY();
+                    }
+                }
+            }
+        }
+
+        int nrOfLanes = 0;
+        for (Process process : bpmnModel.getProcesses()) {
+            for (Lane l : process.getLanes()) {
+
+                nrOfLanes++;
+
+                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
+                // // width
+                if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
+                    maxX = graphicInfo.getX() + graphicInfo.getWidth();
+                }
+                if (graphicInfo.getX() < minX) {
+                    minX = graphicInfo.getX();
+                }
+                // height
+                if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
+                    maxY = graphicInfo.getY() + graphicInfo.getHeight();
+                }
+                if (graphicInfo.getY() < minY) {
+                    minY = graphicInfo.getY();
+                }
+            }
+        }
+
+        // Special case, see https://activiti.atlassian.net/browse/ACT-1431
+        if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) {
+            // Nothing to show
+            minX = 0;
+            minY = 0;
+        }
+
+        return new CustomDefaultProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY,
+            imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
+    }
+
+    protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) {
+        List<Artifact> artifacts = new ArrayList<>();
+        for (Process process : bpmnModel.getProcesses()) {
+            artifacts.addAll(process.getArtifacts());
+        }
+        return artifacts;
+    }
+
+    protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
+        List<FlowNode> flowNodes = new ArrayList<>();
+        for (Process process : bpmnModel.getProcesses()) {
+            flowNodes.addAll(gatherAllFlowNodes(process));
+        }
+        return flowNodes;
+    }
+
+    protected static List<FlowNode> gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) {
+        List<FlowNode> flowNodes = new ArrayList<>();
+        for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
+            if (flowElement instanceof FlowNode) {
+                flowNodes.add((FlowNode) flowElement);
+            }
+            if (flowElement instanceof FlowElementsContainer) {
+                flowNodes.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement));
+            }
+        }
+        return flowNodes;
+    }
+
+    protected boolean isPartOfCollapsedSubProcess(FlowElement flowElement, BpmnModel model) {
+        SubProcess subProcess = flowElement.getSubProcess();
+        if (subProcess != null) {
+            GraphicInfo graphicInfo = model.getGraphicInfo(subProcess.getId());
+            if (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
+                return true;
+            }
+
+            return isPartOfCollapsedSubProcess(subProcess, model);
+        }
+
+        return false;
+    }
+
+    public Map<Class<? extends BaseElement>, ActivityDrawInstruction> getActivityDrawInstructions() {
+        return activityDrawInstructions;
+    }
+
+    public void setActivityDrawInstructions(
+        Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions) {
+        this.activityDrawInstructions = activityDrawInstructions;
+    }
+
+    public Map<Class<? extends BaseElement>, ArtifactDrawInstruction> getArtifactDrawInstructions() {
+        return artifactDrawInstructions;
+    }
+
+    public void setArtifactDrawInstructions(
+        Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions) {
+        this.artifactDrawInstructions = artifactDrawInstructions;
+    }
+
+    protected interface ActivityDrawInstruction {
+        void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode);
+    }
+
+    protected interface ArtifactDrawInstruction {
+        void draw(CustomDefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java
new file mode 100644
index 0000000..3cdfcf4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AddSequenceMultiInstanceCmd.java
@@ -0,0 +1,61 @@
+package org.dromara.workflow.flowable.cmd;
+
+import cn.hutool.core.collection.CollUtil;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES;
+
+/**
+ * 涓茶鍔犵
+ *
+ * @author may
+ */
+public class AddSequenceMultiInstanceCmd implements Command<Void> {
+
+    /**
+     * 鎵цid
+     */
+    private final String executionId;
+
+    /**
+     * 浼氱浜哄憳闆嗗悎KEY
+     */
+    private final String assigneeList;
+
+    /**
+     * 鍔犵浜哄憳
+     */
+    private final List<Long> assignees;
+
+    public AddSequenceMultiInstanceCmd(String executionId, String assigneeList, List<Long> assignees) {
+        this.executionId = executionId;
+        this.assigneeList = assigneeList;
+        this.assignees = assignees;
+    }
+
+    @Override
+    public Void execute(CommandContext commandContext) {
+        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
+        ExecutionEntity entity = executionEntityManager.findById(executionId);
+        // 澶氬疄渚嬩换鍔℃�绘暟鍔� assignees.size()
+        if (entity.getVariable(NUMBER_OF_INSTANCES) instanceof Integer nrOfInstances) {
+            entity.setVariable(NUMBER_OF_INSTANCES, nrOfInstances + assignees.size());
+        }
+        // 璁剧疆娴佺▼鍙橀噺
+        if (entity.getVariable(assigneeList) instanceof List<?> userIds) {
+            CollUtil.addAll(userIds, assignees);
+            Map<String, Object> variables = new HashMap<>(16);
+            variables.put(assigneeList, userIds);
+            entity.setVariables(variables);
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java
new file mode 100644
index 0000000..fc33504
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/AttachmentCmd.java
@@ -0,0 +1,68 @@
+package org.dromara.workflow.flowable.cmd;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.service.ISysOssService;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.AttachmentEntity;
+import org.flowable.engine.impl.persistence.entity.AttachmentEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 涓茶鍔犵
+ *
+ * @author 闄勪欢涓婁紶
+ */
+public class AttachmentCmd implements Command<Boolean> {
+
+    private final String fileId;
+
+    private final String taskId;
+
+    private final String processInstanceId;
+
+    public AttachmentCmd(String fileId, String taskId, String processInstanceId) {
+        this.fileId = fileId;
+        this.taskId = taskId;
+        this.processInstanceId = processInstanceId;
+    }
+
+    @Override
+    public Boolean execute(CommandContext commandContext) {
+        try {
+            if (StringUtils.isNotBlank(fileId)) {
+                List<Long> fileIds = StreamUtils.toList(Arrays.asList(fileId.split(StrUtil.COMMA)), Long::valueOf);
+                List<SysOssVo> sysOssVos = SpringUtils.getBean(ISysOssService.class).listByIds(fileIds);
+                if (CollUtil.isNotEmpty(sysOssVos)) {
+                    for (SysOssVo sysOssVo : sysOssVos) {
+                        AttachmentEntityManager attachmentEntityManager = CommandContextUtil.getAttachmentEntityManager();
+                        AttachmentEntity attachmentEntity = attachmentEntityManager.create();
+                        attachmentEntity.setRevision(1);
+                        attachmentEntity.setUserId(LoginHelper.getUserId().toString());
+                        attachmentEntity.setName(sysOssVo.getOriginalName());
+                        attachmentEntity.setDescription(sysOssVo.getOriginalName());
+                        attachmentEntity.setType(sysOssVo.getFileSuffix());
+                        attachmentEntity.setTaskId(taskId);
+                        attachmentEntity.setProcessInstanceId(processInstanceId);
+                        attachmentEntity.setContentId(sysOssVo.getOssId().toString());
+                        attachmentEntity.setTime(new Date());
+                        attachmentEntityManager.insert(attachmentEntity);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return true;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java
new file mode 100644
index 0000000..215d310
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteExecutionCmd.java
@@ -0,0 +1,36 @@
+package org.dromara.workflow.flowable.cmd;
+
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.io.Serializable;
+
+/**
+ * 鍒犻櫎鎵ц鏁版嵁
+ *
+ * @author may
+ */
+public class DeleteExecutionCmd implements Command<Void>, Serializable {
+
+    /**
+     * 鎵цid
+     */
+    private final String executionId;
+
+    public DeleteExecutionCmd(String executionId) {
+        this.executionId = executionId;
+    }
+
+    @Override
+    public Void execute(CommandContext commandContext) {
+        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
+        ExecutionEntity entity = executionEntityManager.findById(executionId);
+        if (entity != null) {
+            executionEntityManager.deleteExecutionAndRelatedData(entity, "", false, false);
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java
new file mode 100644
index 0000000..6773eef
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java
@@ -0,0 +1,82 @@
+package org.dromara.workflow.flowable.cmd;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.LOOP_COUNTER;
+import static org.dromara.workflow.common.constant.FlowConstant.NUMBER_OF_INSTANCES;
+
+
+/**
+ * 涓茶鍑忕
+ *
+ * @author may
+ */
+@AllArgsConstructor
+public class DeleteSequenceMultiInstanceCmd implements Command<Void> {
+
+    /**
+     * 褰撳墠鑺傜偣瀹℃壒浜哄憳id
+     */
+    private final String currentUserId;
+
+    /**
+     * 鎵цid
+     */
+    private final String executionId;
+
+    /**
+     * 浼氱浜哄憳闆嗗悎KEY
+     */
+    private final String assigneeList;
+
+    /**
+     * 鍑忕浜哄憳
+     */
+    private final List<Long> assignees;
+
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Void execute(CommandContext commandContext) {
+        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
+        ExecutionEntity entity = executionEntityManager.findById(executionId);
+        // 璁剧疆娴佺▼鍙橀噺
+        List<Long> userIds = new ArrayList<>();
+        List<Object> variable = (List<Object>) entity.getVariable(assigneeList);
+        for (Object o : variable) {
+            userIds.add(Long.valueOf(o.toString()));
+        }
+        List<Long> userIdList = new ArrayList<>();
+        userIds.forEach(e -> {
+            Long userId = assignees.stream().filter(id -> ObjectUtil.equals(id, e)).findFirst().orElse(null);
+            if (userId == null) {
+                userIdList.add(e);
+            }
+        });
+        // 褰撳墠浠诲姟鎵ц浣嶇疆
+        int loopCounterIndex = -1;
+        for (int i = 0; i < userIdList.size(); i++) {
+            Long userId = userIdList.get(i);
+            if (currentUserId.equals(userId.toString())) {
+                loopCounterIndex = i;
+            }
+        }
+        Map<String, Object> variables = new HashMap<>(16);
+        variables.put(NUMBER_OF_INSTANCES, userIdList.size());
+        variables.put(assigneeList, userIdList);
+        variables.put(LOOP_COUNTER, loopCounterIndex);
+        entity.setVariables(variables);
+        return null;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java
new file mode 100644
index 0000000..1f3088b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/ExecutionChildByExecutionIdCmd.java
@@ -0,0 +1,39 @@
+package org.dromara.workflow.flowable.cmd;
+
+import org.dromara.common.core.utils.StreamUtils;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 鑾峰彇骞惰缃戝叧鎵ц鍚庝繚鐣欑殑鎵ц瀹炰緥鏁版嵁
+ *
+ * @author may
+ */
+public class ExecutionChildByExecutionIdCmd implements Command<List<ExecutionEntity>>, Serializable {
+
+    /**
+     * 褰撳墠浠诲姟鎵ц瀹炰緥id
+     */
+    private final String executionId;
+
+    public ExecutionChildByExecutionIdCmd(String executionId) {
+        this.executionId = executionId;
+    }
+
+    @Override
+    public List<ExecutionEntity> execute(CommandContext commandContext) {
+        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
+        // 鑾峰彇褰撳墠鎵ц鏁版嵁
+        ExecutionEntity executionEntity = executionEntityManager.findById(executionId);
+        // 閫氳繃褰撳墠鎵ц鏁版嵁鐨勭埗鎵ц锛屾煡璇㈡墍鏈夊瓙鎵ц鏁版嵁
+        List<ExecutionEntity> allChildrenExecution =
+            executionEntityManager.collectChildren(executionEntity.getParent());
+        return StreamUtils.filter(allChildrenExecution, e -> !e.isActive());
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java
new file mode 100644
index 0000000..3ba120a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateBusinessStatusCmd.java
@@ -0,0 +1,37 @@
+package org.dromara.workflow.flowable.cmd;
+
+import org.dromara.common.core.exception.ServiceException;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
+import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntityManager;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+/**
+ * 淇敼娴佺▼鐘舵��
+ *
+ * @author may
+ */
+public class UpdateBusinessStatusCmd implements Command<Boolean> {
+
+    private final String processInstanceId;
+    private final String status;
+
+    public UpdateBusinessStatusCmd(String processInstanceId, String status) {
+        this.processInstanceId = processInstanceId;
+        this.status = status;
+    }
+
+    @Override
+    public Boolean execute(CommandContext commandContext) {
+        try {
+            HistoricProcessInstanceEntityManager manager = CommandContextUtil.getHistoricProcessInstanceEntityManager();
+            HistoricProcessInstanceEntity processInstance = manager.findById(processInstanceId);
+            processInstance.setBusinessStatus(status);
+            manager.update(processInstance);
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java
new file mode 100644
index 0000000..42f6d1c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/UpdateHiTaskInstCmd.java
@@ -0,0 +1,51 @@
+package org.dromara.workflow.flowable.cmd;
+
+import org.dromara.common.core.exception.ServiceException;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.engine.impl.util.CommandContextUtil;
+import org.flowable.task.service.HistoricTaskService;
+import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
+
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * 淇敼娴佺▼鍘嗗彶
+ *
+ * @author may
+ */
+public class UpdateHiTaskInstCmd implements Command<Boolean> {
+
+    private final List<String> taskIds;
+
+    private final String processDefinitionId;
+
+    private final String processInstanceId;
+
+    public UpdateHiTaskInstCmd(List<String> taskIds, String processDefinitionId, String processInstanceId) {
+        this.taskIds = taskIds;
+        this.processDefinitionId = processDefinitionId;
+        this.processInstanceId = processInstanceId;
+    }
+
+    @Override
+    public Boolean execute(CommandContext commandContext) {
+        try {
+            HistoricTaskService historicTaskService = CommandContextUtil.getHistoricTaskService();
+            for (String taskId : taskIds) {
+                HistoricTaskInstanceEntity historicTask = historicTaskService.getHistoricTask(taskId);
+                if (historicTask != null) {
+                    historicTask.setProcessDefinitionId(processDefinitionId);
+                    historicTask.setProcessInstanceId(processInstanceId);
+                    historicTask.setCreateTime(new Date());
+                    CommandContextUtil.getHistoricTaskService().updateHistoricTask(historicTask, true);
+                }
+            }
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java
new file mode 100644
index 0000000..95233d1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/FlowableConfig.java
@@ -0,0 +1,32 @@
+package org.dromara.workflow.flowable.config;
+
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import org.flowable.common.engine.impl.cfg.IdGenerator;
+import org.flowable.spring.SpringProcessEngineConfiguration;
+import org.flowable.spring.boot.EngineConfigurationConfigurer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+
+
+/**
+ * flowable閰嶇疆
+ *
+ * @author may
+ */
+@Configuration
+public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
+
+    @Autowired
+    private GlobalFlowableListener globalFlowableListener;
+    @Autowired
+    private IdentifierGenerator identifierGenerator;
+
+    @Override
+    public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
+        processEngineConfiguration.setIdGenerator(() -> identifierGenerator.nextId(null).toString());
+        processEngineConfiguration.setEventListeners(Collections.singletonList(globalFlowableListener));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java
new file mode 100644
index 0000000..2ff46a9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/config/GlobalFlowableListener.java
@@ -0,0 +1,91 @@
+package org.dromara.workflow.flowable.config;
+
+import cn.hutool.core.collection.CollUtil;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.flowable.bpmn.model.BoundaryEvent;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.common.engine.api.delegate.event.*;
+import org.flowable.common.engine.impl.cfg.TransactionState;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.runtime.Execution;
+import org.flowable.engine.task.Comment;
+import org.flowable.task.api.Task;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+
+/**
+ * 寮曟搸璋冨害鐩戝惉
+ *
+ * @author may
+ */
+@Component
+public class GlobalFlowableListener implements FlowableEventListener {
+
+    @Autowired
+    @Lazy
+    private TaskService taskService;
+
+    @Autowired
+    @Lazy
+    private RuntimeService runtimeService;
+
+    @Autowired
+    @Lazy
+    private RepositoryService repositoryService;
+
+    @Override
+    public void onEvent(FlowableEvent flowableEvent) {
+        if (flowableEvent instanceof FlowableEngineEvent flowableEngineEvent) {
+            FlowableEngineEventType engineEventType = (FlowableEngineEventType) flowableEvent.getType();
+            switch (engineEventType) {
+                case JOB_EXECUTION_SUCCESS -> jobExecutionSuccess((FlowableEngineEntityEvent) flowableEngineEvent);
+            }
+        }
+    }
+
+    @Override
+    public boolean isFailOnException() {
+        return true;
+    }
+
+    @Override
+    public boolean isFireOnTransactionLifecycleEvent() {
+        return false;
+    }
+
+    @Override
+    public String getOnTransaction() {
+        return TransactionState.COMMITTED.name();
+    }
+
+    /**
+     * 澶勭悊杈圭晫瀹氭椂浜嬩欢鑷姩瀹℃壒璁板綍
+     *
+     * @param event 浜嬩欢
+     */
+    protected void jobExecutionSuccess(FlowableEngineEntityEvent event) {
+        Execution execution = runtimeService.createExecutionQuery().executionId(event.getExecutionId()).singleResult();
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(event.getProcessDefinitionId());
+        FlowElement flowElement = bpmnModel.getFlowElement(execution.getActivityId());
+        if (flowElement instanceof BoundaryEvent) {
+            String attachedToRefId = ((BoundaryEvent) flowElement).getAttachedToRefId();
+            List<Execution> list = runtimeService.createExecutionQuery().activityId(attachedToRefId).list();
+            for (Execution ex : list) {
+                Task task = taskService.createTaskQuery().executionId(ex.getId()).singleResult();
+                if (task != null) {
+                    List<Comment> taskComments = taskService.getTaskComments(task.getId());
+                    if (CollUtil.isEmpty(taskComments)) {
+                        taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), "瓒呮椂鑷姩瀹℃壒锛�");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java
new file mode 100644
index 0000000..9da5776
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowEventStrategy.java
@@ -0,0 +1,73 @@
+package org.dromara.workflow.flowable.strategy;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.workflow.annotation.FlowListenerAnnotation;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 娴佺▼浠诲姟鐩戝惉绛栫暐
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+@Component
+public class FlowEventStrategy implements BeanPostProcessor {
+
+    private final Map<String, FlowTaskEventHandler> flowTaskEventHandlers = new HashMap<>();
+    private final Map<String, FlowProcessEventHandler> flowProcessEventHandlers = new HashMap<>();
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        if (bean instanceof FlowTaskEventHandler) {
+            FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class);
+            if (null != annotation) {
+                if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isNotBlank(annotation.taskDefId())) {
+                    String id = annotation.processDefinitionKey() + "_" + annotation.taskDefId();
+                    if (!flowTaskEventHandlers.containsKey(id)) {
+                        flowTaskEventHandlers.put(id, (FlowTaskEventHandler) bean);
+                    }
+                }
+            }
+        }
+        if (bean instanceof FlowProcessEventHandler) {
+            FlowListenerAnnotation annotation = bean.getClass().getAnnotation(FlowListenerAnnotation.class);
+            if (null != annotation) {
+                if (StringUtils.isNotBlank(annotation.processDefinitionKey()) && StringUtils.isBlank(annotation.taskDefId())) {
+                    if (!flowProcessEventHandlers.containsKey(annotation.processDefinitionKey())) {
+                        flowProcessEventHandlers.put(annotation.processDefinitionKey(), (FlowProcessEventHandler) bean);
+                    }
+                }
+            }
+        }
+        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
+    }
+
+    /**
+     * 鑾峰彇鍙墽琛宐ean
+     *
+     * @param key key
+     */
+    public FlowTaskEventHandler getTaskHandler(String key) {
+        if (!flowTaskEventHandlers.containsKey(key)) {
+            return null;
+        }
+        return flowTaskEventHandlers.get(key);
+    }
+
+    /**
+     * 鑾峰彇鍙墽琛宐ean
+     *
+     * @param key key
+     */
+    public FlowProcessEventHandler getProcessHandler(String key) {
+        if (!flowProcessEventHandlers.containsKey(key)) {
+            return null;
+        }
+        return flowProcessEventHandlers.get(key);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java
new file mode 100644
index 0000000..4af2c47
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowProcessEventHandler.java
@@ -0,0 +1,20 @@
+package org.dromara.workflow.flowable.strategy;
+
+
+/**
+ * 娴佺▼鐩戝惉
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+public interface FlowProcessEventHandler {
+
+    /**
+     * 鎵ц鍔炵悊浠诲姟鐩戝惉
+     *
+     * @param businessKey 涓氬姟id
+     * @param status      鐘舵��
+     * @param submit      褰撲负true鏃朵负鐢宠浜鸿妭鐐瑰姙鐞�
+     */
+    void handleProcess(String businessKey, String status, boolean submit);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java
new file mode 100644
index 0000000..4cf9d89
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/strategy/FlowTaskEventHandler.java
@@ -0,0 +1,20 @@
+package org.dromara.workflow.flowable.strategy;
+
+import org.flowable.task.api.Task;
+
+/**
+ * 娴佺▼浠诲姟鐩戝惉
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+public interface FlowTaskEventHandler {
+
+    /**
+     * 鎵ц鍔炵悊浠诲姟鐩戝惉
+     *
+     * @param task        浠诲姟
+     * @param businessKey 涓氬姟id
+     */
+    void handleTask(Task task, String businessKey);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java
new file mode 100644
index 0000000..dbb32ae
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomProcessHandler.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.workflow.annotation.FlowListenerAnnotation;
+import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鑷畾涔夌洃鍚祴璇�
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+@Slf4j
+@Component
+@FlowListenerAnnotation(processDefinitionKey = "leave1")
+public class TestCustomProcessHandler implements FlowProcessEventHandler {
+
+
+    @Override
+    public void handleProcess(String businessKey, String status, boolean submit) {
+        log.info("涓氬姟ID:" + businessKey + ",鐘舵��:" + status);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java
new file mode 100644
index 0000000..fb04cd0
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestCustomTaskHandler.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.workflow.annotation.FlowListenerAnnotation;
+import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler;
+import org.flowable.task.api.Task;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鑷畾涔夌洃鍚祴璇�
+ *
+ * @author may
+ * @date 2023-12-27
+ */
+@Slf4j
+@Component
+@FlowListenerAnnotation(processDefinitionKey = "leave1", taskDefId = "Activity_14633hx")
+public class TestCustomTaskHandler implements FlowTaskEventHandler {
+
+    @Override
+    public void handleTask(Task task, String businessKey) {
+        log.info("浠诲姟鍚嶇О:" + task.getName() + ",涓氬姟ID:" + businessKey);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java
new file mode 100644
index 0000000..1a75414
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveExecutionListener.java
@@ -0,0 +1,29 @@
+package org.dromara.workflow.listener;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.ExecutionListener;
+import org.flowable.task.api.Task;
+import org.springframework.stereotype.Component;
+
+/**
+ * 娴佺▼瀹炰緥鐩戝惉娴嬭瘯
+ *
+ * @author may
+ * @date 2023-12-12
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Component("testLeaveExecutionListener")
+public class TestLeaveExecutionListener implements ExecutionListener {
+
+    private final TaskService taskService;
+
+    @Override
+    public void notify(DelegateExecution execution) {
+        Task task = taskService.createTaskQuery().executionId(execution.getId()).singleResult();
+        log.info("鎵ц鐩戝惉銆�" + task.getName() + "銆�");
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java
new file mode 100644
index 0000000..5c62417
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/TestLeaveTaskListener.java
@@ -0,0 +1,21 @@
+package org.dromara.workflow.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.flowable.task.service.delegate.TaskListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 娴佺▼浠诲姟鐩戝惉娴嬭瘯
+ *
+ * @author may
+ * @date 2023-12-12
+ */
+@Slf4j
+@Component("testLeaveTaskListener")
+public class TestLeaveTaskListener implements TaskListener {
+    @Override
+    public void notify(DelegateTask delegateTask) {
+        log.info("鎵ц鐩戝惉銆�" + delegateTask.getName() + "銆�");
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java
new file mode 100644
index 0000000..a3a41c9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiProcinstMapper.java
@@ -0,0 +1,16 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.ActHiProcinst;
+
+/**
+ * 娴佺▼瀹炰緥Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-22
+ */
+@InterceptorIgnore(tenantLine = "true")
+public interface ActHiProcinstMapper extends BaseMapperPlus<ActHiProcinst, ActHiProcinst> {
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java
new file mode 100644
index 0000000..f8bb260
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActHiTaskinstMapper.java
@@ -0,0 +1,16 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.dromara.workflow.domain.ActHiTaskinst;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 娴佺▼鍘嗗彶浠诲姟Mapper鎺ュ彛
+ *
+ * @author gssong
+ * @date 2024-03-02
+ */
+@InterceptorIgnore(tenantLine = "true")
+public interface ActHiTaskinstMapper extends BaseMapperPlus<ActHiTaskinst, ActHiTaskinst> {
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java
new file mode 100644
index 0000000..e7094f5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/ActTaskMapper.java
@@ -0,0 +1,38 @@
+package org.dromara.workflow.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.vo.TaskVo;
+
+
+/**
+ * 浠诲姟淇℃伅Mapper鎺ュ彛
+ *
+ * @author gssong
+ * @date 2024-03-02
+ */
+@InterceptorIgnore(tenantLine = "true")
+public interface ActTaskMapper extends BaseMapperPlus<TaskVo, TaskVo> {
+    /**
+     * 鑾峰彇寰呭姙淇℃伅
+     *
+     * @param page         鍒嗛〉
+     * @param queryWrapper 鏉′欢
+     * @return 缁撴灉
+     */
+    Page<TaskVo> getTaskWaitByPage(@Param("page") Page<TaskVo> page, @Param(Constants.WRAPPER) Wrapper<TaskVo> queryWrapper);
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+     *
+     * @param page         鍒嗛〉
+     * @param queryWrapper 鏉′欢
+     * @return 缁撴灉
+     */
+    Page<TaskVo> getTaskCopyByPage(@Param("page") Page<TaskVo> page, @Param(Constants.WRAPPER) QueryWrapper<TaskVo> queryWrapper);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java
new file mode 100644
index 0000000..cd1edba
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/TestLeaveMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.workflow.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+
+/**
+ * 璇峰亣Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+public interface TestLeaveMapper extends BaseMapperPlus<TestLeave, TestLeaveVo> {
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java
new file mode 100644
index 0000000..98aea02
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/WfCategoryMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.workflow.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.WfCategory;
+import org.dromara.workflow.domain.vo.WfCategoryVo;
+
+/**
+ * 娴佺▼鍒嗙被Mapper鎺ュ彛
+ *
+ * @author may
+ * @date 2023-06-27
+ */
+public interface WfCategoryMapper extends BaseMapperPlus<WfCategory, WfCategoryVo> {
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java
new file mode 100644
index 0000000..e802c69
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiProcinstService.java
@@ -0,0 +1,31 @@
+package org.dromara.workflow.service;
+
+
+import org.dromara.workflow.domain.ActHiProcinst;
+
+import java.util.List;
+
+/**
+ * 娴佺▼瀹炰緥Service鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-22
+ */
+public interface IActHiProcinstService {
+
+    /**
+     * 鎸夌収涓氬姟id鏌ヨ
+     *
+     * @param businessKeys 涓氬姟id
+     * @return 缁撴灉
+     */
+    List<ActHiProcinst> selectByBusinessKeyIn(List<String> businessKeys);
+
+    /**
+     * 鎸夌収涓氬姟id鏌ヨ
+     *
+     * @param businessKey 涓氬姟id
+     * @return 缁撴灉
+     */
+    ActHiProcinst selectByBusinessKey(String businessKey);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java
new file mode 100644
index 0000000..d71e93f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActHiTaskinstService.java
@@ -0,0 +1,11 @@
+package org.dromara.workflow.service;
+
+
+/**
+ * 娴佺▼鍘嗗彶浠诲姟Service鎺ュ彛
+ *
+ * @author gssong
+ * @date 2024-03-02
+ */
+public interface IActHiTaskinstService {
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java
new file mode 100644
index 0000000..c7f6c18
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActModelService.java
@@ -0,0 +1,71 @@
+package org.dromara.workflow.service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.ModelBo;
+import org.dromara.workflow.domain.vo.ModelVo;
+import org.flowable.engine.repository.Model;
+
+
+/**
+ * 妯″瀷绠$悊 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IActModelService {
+    /**
+     * 鍒嗛〉鏌ヨ妯″瀷
+     *
+     * @param modelBo 妯″瀷鍙傛暟
+     * @return 杩斿洖鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<Model> page(ModelBo modelBo);
+
+    /**
+     * 鏂板妯″瀷
+     *
+     * @param modelBo 妯″瀷璇锋眰瀵硅薄
+     * @return 缁撴灉
+     */
+    boolean saveNewModel(ModelBo modelBo);
+
+    /**
+     * 鏌ヨ妯″瀷
+     *
+     * @param modelId 妯″瀷id
+     * @return 妯″瀷鏁版嵁
+     */
+    ModelVo getInfo(String modelId);
+
+    /**
+     * 淇敼妯″瀷淇℃伅
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     * @return 缁撴灉
+     */
+    boolean update(ModelBo modelBo);
+
+    /**
+     * 缂栬緫妯″瀷XML
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     * @return 缁撴灉
+     */
+    boolean editModelXml(ModelBo modelBo);
+
+    /**
+     * 妯″瀷閮ㄧ讲
+     *
+     * @param id 妯″瀷id
+     * @return 缁撴灉
+     */
+    boolean modelDeploy(String id);
+
+    /**
+     * 瀵煎嚭妯″瀷zip鍘嬬缉鍖�
+     *
+     * @param modelId  妯″瀷id
+     * @param response 鐩稿簲
+     */
+    void exportZip(String modelId, HttpServletResponse response);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java
new file mode 100644
index 0000000..0fd94c8
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessDefinitionService.java
@@ -0,0 +1,90 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
+import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 娴佺▼瀹氫箟 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IActProcessDefinitionService {
+    /**
+     * 鍒嗛〉鏌ヨ
+     *
+     * @param processDefinitionBo 鍙傛暟
+     * @return 杩斿洖鍒嗛〉鍒楄〃
+     */
+    TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo processDefinitionBo);
+
+    /**
+     * 鏌ヨ鍘嗗彶娴佺▼瀹氫箟鍒楄〃
+     *
+     * @param key 娴佺▼瀹氫箟key
+     * @return 缁撴灉
+     */
+    List<ProcessDefinitionVo> getProcessDefinitionListByKey(String key);
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟鍥剧墖
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @return 缁撴灉
+     */
+    String processDefinitionImage(String processDefinitionId);
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟xml鏂囦欢
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @return 缁撴灉
+     */
+    String processDefinitionXml(String processDefinitionId);
+
+    /**
+     * 鍒犻櫎娴佺▼瀹氫箟
+     *
+     * @param deploymentId        閮ㄧ讲id
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @return 缁撴灉
+     */
+    boolean deleteDeployment(String deploymentId, String processDefinitionId);
+
+    /**
+     * 婵�娲绘垨鑰呮寕璧锋祦绋嬪畾涔�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @return 缁撴灉
+     */
+    boolean updateProcessDefState(String processDefinitionId);
+
+    /**
+     * 杩佺Щ娴佺▼瀹氫箟
+     *
+     * @param currentProcessDefinitionId 褰撳墠娴佺▼瀹氫箟id
+     * @param fromProcessDefinitionId    闇�瑕佽縼绉诲埌鐨勬祦绋嬪畾涔塱d
+     * @return 缁撴灉
+     */
+    boolean migrationProcessDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId);
+
+    /**
+     * 娴佺▼瀹氫箟杞崲涓烘ā鍨�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @return 缁撴灉
+     */
+    boolean convertToModel(String processDefinitionId);
+
+    /**
+     * 閫氳繃zip鎴杧ml閮ㄧ讲娴佺▼瀹氫箟
+     *
+     * @param file         鏂囦欢
+     * @param categoryCode 鍒嗙被
+     * @return 缁撴灉
+     */
+    boolean deployByFile(MultipartFile file, String categoryCode);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java
new file mode 100644
index 0000000..c8af5a4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActProcessInstanceService.java
@@ -0,0 +1,113 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.ProcessInstanceBo;
+import org.dromara.workflow.domain.bo.ProcessInvalidBo;
+import org.dromara.workflow.domain.bo.TaskUrgingBo;
+import org.dromara.workflow.domain.vo.ProcessInstanceVo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼瀹炰緥 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IActProcessInstanceService {
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥�
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    String getHistoryProcessImage(String processInstanceId);
+
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥捐繍琛屼腑锛屽巻鍙茬瓑鑺傜偣
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    Map<String, Object> getHistoryProcessList(String processInstanceId);
+
+    /**
+     * 鍒嗛〉鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚�
+     *
+     * @param processInstanceBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<ProcessInstanceVo> getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo);
+
+    /**
+     * 鍒嗛〉鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥
+     *
+     * @param processInstanceBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<ProcessInstanceVo> getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo);
+
+    /**
+     * 鑾峰彇瀹℃壒璁板綍
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    Map<String, Object> getHistoryRecord(String processInstanceId);
+
+    /**
+     * 浣滃簾娴佺▼瀹炰緥锛屼笉浼氬垹闄ゅ巻鍙茶褰�(鍒犻櫎杩愯涓殑瀹炰緥)
+     *
+     * @param processInvalidBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean deleteRuntimeProcessInst(ProcessInvalidBo processInvalidBo);
+
+    /**
+     * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    boolean deleteRuntimeProcessAndHisInst(List<String> processInstanceIds);
+
+    /**
+     * 鎸夌収涓氬姟id鍒犻櫎 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param businessKeys 涓氬姟id
+     * @return 缁撴灉
+     */
+    boolean deleteRuntimeProcessAndHisInstByBusinessKeys(List<String> businessKeys);
+
+    /**
+     * 宸插畬鎴愮殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    boolean deleteFinishProcessAndHisInst(List<String> processInstanceIds);
+
+    /**
+     * 鎾ら攢娴佺▼鐢宠
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     * @return 缁撴灉
+     */
+    boolean cancelProcessApply(String processInstanceId);
+
+    /**
+     * 鍒嗛〉鏌ヨ褰撳墠鐧诲綍浜哄崟鎹�
+     *
+     * @param processInstanceBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<ProcessInstanceVo> getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo);
+
+    /**
+     * 浠诲姟鍌姙(缁欏綋鍓嶄换鍔″姙鐞嗕汉鍙戦�佺珯鍐呬俊锛岄偖浠讹紝鐭俊绛�)
+     *
+     * @param taskUrgingBo 浠诲姟鍌姙
+     * @return 缁撴灉
+     */
+    boolean taskUrging(TaskUrgingBo taskUrgingBo);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java
new file mode 100644
index 0000000..b5596a5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IActTaskService.java
@@ -0,0 +1,129 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.TaskVo;
+
+import java.util.Map;
+
+/**
+ * 浠诲姟 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IActTaskService {
+    /**
+     * 鍚姩浠诲姟
+     *
+     * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+     * @return 缁撴灉
+     */
+    Map<String, Object> startWorkFlow(StartProcessBo startProcessBo);
+
+
+    /**
+     * 鍔炵悊浠诲姟
+     *
+     * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean completeTask(CompleteTaskBo completeTaskBo);
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<TaskVo> getTaskWaitByPage(TaskBo taskBo);
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<TaskVo> getAllTaskWaitByPage(TaskBo taskBo);
+
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<TaskVo> getTaskFinishByPage(TaskBo taskBo);
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+     *
+     * @param taskBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<TaskVo> getTaskCopyByPage(TaskBo taskBo);
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<TaskVo> getAllTaskFinishByPage(TaskBo taskBo);
+
+    /**
+     * 濮旀淳浠诲姟
+     *
+     * @param delegateBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean delegateTask(DelegateBo delegateBo);
+
+    /**
+     * 缁堟浠诲姟
+     *
+     * @param terminationBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean terminationTask(TerminationBo terminationBo);
+
+    /**
+     * 杞姙浠诲姟
+     *
+     * @param transmitBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean transferTask(TransmitBo transmitBo);
+
+    /**
+     * 浼氱浠诲姟鍔犵
+     *
+     * @param addMultiBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean addMultiInstanceExecution(AddMultiBo addMultiBo);
+
+    /**
+     * 浼氱浠诲姟鍑忕
+     *
+     * @param deleteMultiBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo);
+
+    /**
+     * 椹冲洖瀹℃壒
+     *
+     * @param backProcessBo 鍙傛暟
+     * @return 娴佺▼瀹炰緥id
+     */
+    String backProcess(BackProcessBo backProcessBo);
+
+    /**
+     * 淇敼浠诲姟鍔炵悊浜�
+     *
+     * @param taskIds 浠诲姟id
+     * @param userId  鍔炵悊浜篿d
+     * @return 缁撴灉
+     */
+    boolean updateAssignee(String[] taskIds, String userId);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
new file mode 100644
index 0000000..606b255
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java
@@ -0,0 +1,49 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 璇峰亣Service鎺ュ彛
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+public interface ITestLeaveService {
+
+    /**
+     * 鏌ヨ璇峰亣
+     */
+    TestLeaveVo queryById(Long id);
+
+    /**
+     * 鏌ヨ璇峰亣鍒楄〃
+     */
+    TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ璇峰亣鍒楄〃
+     */
+    List<TestLeaveVo> queryList(TestLeaveBo bo);
+
+    /**
+     * 鏂板璇峰亣
+     */
+    TestLeave insertByBo(TestLeaveBo bo);
+
+    /**
+     * 淇敼璇峰亣
+     */
+    TestLeave updateByBo(TestLeaveBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄よ鍋囦俊鎭�
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java
new file mode 100644
index 0000000..acf0aa2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWfCategoryService.java
@@ -0,0 +1,51 @@
+package org.dromara.workflow.service;
+
+import org.dromara.workflow.domain.WfCategory;
+import org.dromara.workflow.domain.bo.WfCategoryBo;
+import org.dromara.workflow.domain.vo.WfCategoryVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被Service鎺ュ彛
+ *
+ * @author may
+ * @date 2023-06-28
+ */
+public interface IWfCategoryService {
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被
+     */
+    WfCategoryVo queryById(Long id);
+
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+     */
+    List<WfCategoryVo> queryList(WfCategoryBo bo);
+
+    /**
+     * 鏂板娴佺▼鍒嗙被
+     */
+    Boolean insertByBo(WfCategoryBo bo);
+
+    /**
+     * 淇敼娴佺▼鍒嗙被
+     */
+    Boolean updateByBo(WfCategoryBo bo);
+
+    /**
+     * 鏍¢獙骞舵壒閲忓垹闄ゆ祦绋嬪垎绫讳俊鎭�
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 鎸夌収绫诲埆缂栫爜鏌ヨ
+     *
+     * @param categoryCode 鍒嗙被姣斿悧
+     * @return 缁撴灉
+     */
+    WfCategory queryByCategoryCode(String categoryCode);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java
new file mode 100644
index 0000000..3c91572
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IWorkflowUserService.java
@@ -0,0 +1,60 @@
+package org.dromara.workflow.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.workflow.domain.bo.SysUserMultiBo;
+import org.dromara.workflow.domain.vo.TaskVo;
+
+import java.util.List;
+
+/**
+ * 宸ヤ綔娴佺敤鎴烽�変汉绠$悊 鏈嶅姟灞�
+ *
+ * @author may
+ */
+public interface IWorkflowUserService {
+
+    /**
+     * 鍒嗛〉鏌ヨ宸ヤ綔娴侀�夋嫨鍔犵浜哄憳
+     *
+     * @param sysUserMultiBo 鍙傛暟
+     * @return 缁撴灉
+     */
+    TableDataInfo<SysUserVo> getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo);
+
+    /**
+     * 鏌ヨ宸ヤ綔娴侀�夋嫨鍑忕浜哄憳
+     *
+     * @param taskId 浠诲姟id
+     * @return 缁撴灉
+     */
+    List<TaskVo> getWorkflowDeleteMultiInstanceList(String taskId);
+
+    /**
+     * 鎸夌収鐢ㄦ埛id鏌ヨ鐢ㄦ埛
+     *
+     * @param userIds 鐢ㄦ埛id
+     * @return 缁撴灉
+     */
+    List<SysUserVo> getUserListByIds(List<Long> userIds);
+
+    /**
+     * 鎸夌収瑙掕壊id鏌ヨ鍏宠仈鐢ㄦ埛id
+     *
+     * @param roleIds 瑙掕壊id
+     * @return 缁撴灉
+     */
+    List<SysUserRole> getUserRoleListByRoleIds(List<Long> roleIds);
+
+    /**
+     * 鍒嗛〉鏌ヨ鐢ㄦ埛
+     *
+     * @param sysUserBo 鍙傛暟
+     * @param pageQuery 鍒嗛〉
+     * @return 缁撴灉
+     */
+    TableDataInfo<SysUserVo> getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery);
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java
new file mode 100644
index 0000000..06d607b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiProcinstServiceImpl.java
@@ -0,0 +1,51 @@
+package org.dromara.workflow.service.impl;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.workflow.domain.ActHiProcinst;
+import org.dromara.workflow.mapper.ActHiProcinstMapper;
+import org.dromara.workflow.service.IActHiProcinstService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+
+/**
+ * 娴佺▼瀹炰緥Service涓氬姟灞傚鐞�
+ *
+ * @author may
+ * @date 2023-07-22
+ */
+@RequiredArgsConstructor
+@Service
+public class ActHiProcinstServiceImpl implements IActHiProcinstService {
+
+    private final ActHiProcinstMapper baseMapper;
+
+    /**
+     * 鎸夌収涓氬姟id鏌ヨ
+     *
+     * @param businessKeys 涓氬姟id
+     */
+    @Override
+    public List<ActHiProcinst> selectByBusinessKeyIn(List<String> businessKeys) {
+        return baseMapper.selectList(new LambdaQueryWrapper<ActHiProcinst>()
+            .in(ActHiProcinst::getBusinessKey, businessKeys)
+            .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId()));
+    }
+
+    /**
+     * 鎸夌収涓氬姟id鏌ヨ
+     *
+     * @param businessKey 涓氬姟id
+     */
+    @Override
+    public ActHiProcinst selectByBusinessKey(String businessKey) {
+        return baseMapper.selectOne(new LambdaQueryWrapper<ActHiProcinst>()
+            .eq(ActHiProcinst::getBusinessKey, businessKey)
+            .eq(TenantHelper.isEnable(), ActHiProcinst::getTenantId, TenantHelper.getTenantId()));
+
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java
new file mode 100644
index 0000000..7faabc2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActHiTaskinstServiceImpl.java
@@ -0,0 +1,18 @@
+package org.dromara.workflow.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.dromara.workflow.service.IActHiTaskinstService;
+
+
+/**
+ * 娴佺▼鍘嗗彶浠诲姟Service涓氬姟灞傚鐞�
+ *
+ * @author gssong
+ * @date 2024-03-02
+ */
+@RequiredArgsConstructor
+@Service
+public class ActHiTaskinstServiceImpl implements IActHiTaskinstService {
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java
new file mode 100644
index 0000000..01bb12e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java
@@ -0,0 +1,334 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ZipUtil;
+import cn.hutool.json.JSONUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.bo.ModelBo;
+import org.dromara.workflow.domain.vo.ModelVo;
+import org.dromara.workflow.service.IActModelService;
+import org.dromara.workflow.utils.ModelUtils;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ModelQuery;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.validation.ValidationError;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 妯″瀷绠$悊 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@RequiredArgsConstructor
+@Service
+public class ActModelServiceImpl implements IActModelService {
+
+    private final RepositoryService repositoryService;
+
+    /**
+     * 鍒嗛〉鏌ヨ妯″瀷
+     *
+     * @param modelBo 妯″瀷鍙傛暟
+     * @return 杩斿洖鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<Model> page(ModelBo modelBo) {
+        ModelQuery query = repositoryService.createModelQuery();
+        query.modelTenantId(TenantHelper.getTenantId());
+        if (StringUtils.isNotEmpty(modelBo.getName())) {
+            query.modelNameLike("%" + modelBo.getName() + "%");
+        }
+        if (StringUtils.isNotEmpty(modelBo.getKey())) {
+            query.modelKey(modelBo.getKey());
+        }
+        if (StringUtils.isNotEmpty(modelBo.getCategoryCode())) {
+            query.modelCategory(modelBo.getCategoryCode());
+        }
+        query.orderByLastUpdateTime().desc();
+        // 鍒涘缓鏃堕棿闄嶅簭鎺掑垪
+        query.orderByCreateTime().desc();
+        // 鍒嗛〉鏌ヨ
+        List<Model> modelList = query.listPage(modelBo.getPageNum(), modelBo.getPageSize());
+        // 鎬昏褰曟暟
+        long total = query.count();
+        return new TableDataInfo<>(modelList, total);
+    }
+
+    /**
+     * 鏂板妯″瀷
+     *
+     * @param modelBo 妯″瀷璇锋眰瀵硅薄
+     * @return 缁撴灉
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveNewModel(ModelBo modelBo) {
+        try {
+            int version = 0;
+            String key = modelBo.getKey();
+            String name = modelBo.getName();
+            String description = modelBo.getDescription();
+            String categoryCode = modelBo.getCategoryCode();
+            String xml = modelBo.getXml();
+            Model checkModel = repositoryService.createModelQuery().modelKey(key).modelTenantId(TenantHelper.getTenantId()).singleResult();
+            if (ObjectUtil.isNotNull(checkModel)) {
+                throw new ServiceException("妯″瀷key宸插瓨鍦紒");
+            }
+            //鍒濆绌虹殑妯″瀷
+            Model model = repositoryService.newModel();
+            model.setKey(key);
+            model.setName(name);
+            model.setVersion(version);
+            model.setCategory(categoryCode);
+            model.setMetaInfo(description);
+            model.setTenantId(TenantHelper.getTenantId());
+            //淇濆瓨鍒濆鍖栫殑妯″瀷鍩烘湰淇℃伅鏁版嵁
+            repositoryService.saveModel(model);
+            repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml));
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 鏌ヨ妯″瀷
+     *
+     * @param id 妯″瀷id
+     * @return 妯″瀷鏁版嵁
+     */
+    @Override
+    public ModelVo getInfo(String id) {
+        ModelVo modelVo = new ModelVo();
+        Model model = repositoryService.getModel(id);
+        if (model != null) {
+            try {
+                byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
+                modelVo.setXml(StrUtil.utf8Str(modelEditorSource));
+                modelVo.setId(model.getId());
+                modelVo.setKey(model.getKey());
+                modelVo.setName(model.getName());
+                modelVo.setCategoryCode(model.getCategory());
+                modelVo.setDescription(model.getMetaInfo());
+                return modelVo;
+            } catch (Exception e) {
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        return modelVo;
+    }
+
+    /**
+     * 淇敼妯″瀷淇℃伅
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     * @return 缁撴灉
+     */
+    @Override
+    public boolean update(ModelBo modelBo) {
+        try {
+            Model model = repositoryService.getModel(modelBo.getId());
+            List<Model> list = repositoryService.createModelQuery().modelTenantId(TenantHelper.getTenantId()).modelKey(modelBo.getKey()).list();
+            list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> {
+                throw new ServiceException("妯″瀷KEY宸插瓨鍦紒");
+            });
+            model.setCategory(modelBo.getCategoryCode());
+            model.setMetaInfo(modelBo.getDescription());
+            repositoryService.saveModel(model);
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
+
+    /**
+     * 缂栬緫妯″瀷XML
+     *
+     * @param modelBo 妯″瀷鏁版嵁
+     * @return 缁撴灉
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean editModelXml(ModelBo modelBo) {
+        try {
+            String xml = modelBo.getXml();
+            String svg = modelBo.getSvg();
+            String modelId = modelBo.getId();
+            String key = modelBo.getKey();
+            String name = modelBo.getName();
+            BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xml);
+            ModelUtils.checkBpmnModel(bpmnModel);
+            Model model = repositoryService.getModel(modelId);
+            List<Model> list = repositoryService.createModelQuery().modelTenantId(TenantHelper.getTenantId()).modelKey(key).list();
+            list.stream().filter(e -> !e.getId().equals(model.getId())).findFirst().ifPresent(e -> {
+                throw new ServiceException("妯″瀷KEY宸插瓨鍦紒");
+            });
+            // 鏍¢獙key鍛藉悕瑙勮寖
+            if (!Validator.isMatchRegex(FlowConstant.MODEL_KEY_PATTERN, key)) {
+                throw new ServiceException("妯″瀷鏍囪瘑KEY鍙兘瀛楃鎴栬�呬笅鍒掔嚎寮�澶达紒");
+            }
+            model.setKey(key);
+            model.setName(name);
+            model.setVersion(model.getVersion() + 1);
+            repositoryService.saveModel(model);
+            repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(xml));
+            // 杞崲鍥剧墖
+            InputStream svgStream = new ByteArrayInputStream(StrUtil.utf8Bytes(svg));
+            TranscoderInput input = new TranscoderInput(svgStream);
+
+            PNGTranscoder transcoder = new PNGTranscoder();
+            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+            TranscoderOutput output = new TranscoderOutput(outStream);
+
+            transcoder.transcode(input, output);
+            final byte[] result = outStream.toByteArray();
+            repositoryService.addModelEditorSourceExtra(model.getId(), result);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 妯″瀷閮ㄧ讲
+     *
+     * @param id 妯″瀷id
+     * @return 缁撴灉
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean modelDeploy(String id) {
+        try {
+            // 鏌ヨ娴佺▼瀹氫箟妯″瀷xml
+            byte[] xmlBytes = repositoryService.getModelEditorSource(id);
+            if (ArrayUtil.isEmpty(xmlBytes)) {
+                throw new ServiceException("妯″瀷鏁版嵁涓虹┖锛岃鍏堣璁℃祦绋嬪畾涔夋ā鍨嬶紝鍐嶈繘琛岄儴缃诧紒");
+            }
+            if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString()))) {
+                byte[] bytes = ModelUtils.bpmnJsonToXmlBytes(xmlBytes);
+                if (ArrayUtil.isEmpty(bytes)) {
+                    throw new ServiceException("妯″瀷涓嶈兘涓虹┖锛岃鑷冲皯璁捐涓�鏉′富绾挎祦绋嬶紒");
+                }
+            }
+            BpmnModel bpmnModel = ModelUtils.xmlToBpmnModel(xmlBytes);
+            // 鏍¢獙妯″瀷
+            ModelUtils.checkBpmnModel(bpmnModel);
+            List<ValidationError> validationErrors = repositoryService.validateProcess(bpmnModel);
+            if (CollUtil.isNotEmpty(validationErrors)) {
+                String errorMsg = validationErrors.stream().map(ValidationError::getProblem).distinct().collect(Collectors.joining(","));
+                throw new ServiceException(errorMsg);
+            }
+            // 鏌ヨ妯″瀷鐨勫熀鏈俊鎭�
+            Model model = repositoryService.getModel(id);
+            // xml璧勬簮鐨勫悕绉� 锛屽搴攁ct_ge_bytearray琛ㄤ腑鐨刵ame_瀛楁
+            String processName = model.getName() + ".bpmn20.xml";
+            // 璋冪敤閮ㄧ讲鐩稿叧鐨刟pi鏂规硶杩涜閮ㄧ讲娴佺▼瀹氫箟
+            Deployment deployment = repositoryService.createDeployment()
+                // 閮ㄧ讲鍚嶇О
+                .name(model.getName())
+                // 閮ㄧ讲鏍囪瘑key
+                .key(model.getKey())
+                // 閮ㄧ讲娴佺▼鍒嗙被
+                .category(model.getCategory())
+                // bpmn20.xml璧勬簮
+                .addBytes(processName, xmlBytes)
+                // 绉熸埛id
+                .tenantId(TenantHelper.getTenantId())
+                .deploy();
+
+            // 鏇存柊 閮ㄧ讲id 鍒版祦绋嬪畾涔夋ā鍨嬫暟鎹〃涓�
+            model.setDeploymentId(deployment.getId());
+            repositoryService.saveModel(model);
+            // 鏇存柊鍒嗙被
+            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
+            repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 瀵煎嚭妯″瀷zip鍘嬬缉鍖�
+     *
+     * @param modelId  妯″瀷id
+     * @param response 鐩稿簲
+     */
+    @Override
+    public void exportZip(String modelId, HttpServletResponse response) {
+        ZipOutputStream zos = null;
+        try {
+            zos = ZipUtil.getZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8);
+            // 鍘嬬缉鍖呮枃浠跺悕
+            String zipName = "妯″瀷涓嶅瓨鍦�";
+            // 鏌ヨ妯″瀷鍩烘湰淇℃伅
+            Model model = repositoryService.getModel(modelId);
+            byte[] xmlBytes = repositoryService.getModelEditorSource(modelId);
+            if (ObjectUtil.isNotNull(model)) {
+                if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString())) && ArrayUtil.isEmpty(ModelUtils.bpmnJsonToXmlBytes(xmlBytes))) {
+                    zipName = "妯″瀷涓嶈兘涓虹┖锛岃鑷冲皯璁捐涓�鏉′富绾挎祦绋嬶紒";
+                    zos.putNextEntry(new ZipEntry(zipName + ".txt"));
+                    zos.write(zipName.getBytes(StandardCharsets.UTF_8));
+                } else if (ArrayUtil.isEmpty(xmlBytes)) {
+                    zipName = "妯″瀷鏁版嵁涓虹┖锛岃鍏堣璁℃祦绋嬪畾涔夋ā鍨嬶紝鍐嶈繘琛岄儴缃诧紒";
+                    zos.putNextEntry(new ZipEntry(zipName + ".txt"));
+                    zos.write(zipName.getBytes(StandardCharsets.UTF_8));
+                } else {
+                    String fileName = model.getName() + "-" + model.getKey();
+                    // 鍘嬬缉鍖呮枃浠跺悕
+                    zipName = fileName + ".zip";
+                    // 灏唜ml娣诲姞鍒板帇缂╁寘涓�(鎸囧畾xml鏂囦欢鍚嶏細璇峰亣娴佺▼.bpmn20.xml
+                    zos.putNextEntry(new ZipEntry(fileName + ".bpmn20.xml"));
+                    zos.write(xmlBytes);
+                }
+            }
+            response.setHeader("Content-Disposition",
+                "attachment; filename=" + URLEncoder.encode(zipName, StandardCharsets.UTF_8) + ".zip");
+            // 鍒峰嚭鍝嶅簲娴�
+            response.flushBuffer();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (zos != null) {
+                try {
+                    zos.closeEntry();
+                    zos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java
new file mode 100644
index 0000000..fda10d3
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessDefinitionServiceImpl.java
@@ -0,0 +1,328 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.io.IOUtils;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.domain.WfCategory;
+import org.dromara.workflow.domain.bo.ProcessDefinitionBo;
+import org.dromara.workflow.domain.vo.ProcessDefinitionVo;
+import org.dromara.workflow.service.IActProcessDefinitionService;
+import org.dromara.workflow.service.IWfCategoryService;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.ProcessMigrationService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.impl.bpmn.deployer.ResourceNameUtil;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.repository.ProcessDefinitionQuery;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipInputStream;
+
+/**
+ * 娴佺▼瀹氫箟 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@RequiredArgsConstructor
+@Service
+public class ActProcessDefinitionServiceImpl implements IActProcessDefinitionService {
+
+    private final RepositoryService repositoryService;
+
+    private final HistoryService historyService;
+
+    private final ProcessMigrationService processMigrationService;
+
+    private final IWfCategoryService wfCategoryService;
+
+    /**
+     * 鍒嗛〉鏌ヨ
+     *
+     * @param processDefinitionBo 鍙傛暟
+     * @return 杩斿洖鍒嗛〉鍒楄〃
+     */
+    @Override
+    public TableDataInfo<ProcessDefinitionVo> page(ProcessDefinitionBo processDefinitionBo) {
+        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
+        query.processDefinitionTenantId(TenantHelper.getTenantId());
+        if (StringUtils.isNotEmpty(processDefinitionBo.getKey())) {
+            query.processDefinitionKey(processDefinitionBo.getKey());
+        }
+        if (StringUtils.isNotEmpty(processDefinitionBo.getCategoryCode())) {
+            query.processDefinitionCategory(processDefinitionBo.getCategoryCode());
+        }
+        if (StringUtils.isNotEmpty(processDefinitionBo.getName())) {
+            query.processDefinitionNameLike("%" + processDefinitionBo.getName() + "%");
+        }
+        query.orderByDeploymentId().desc();
+        // 鍒嗛〉鏌ヨ
+        List<ProcessDefinitionVo> processDefinitionVoList = new ArrayList<>();
+        List<ProcessDefinition> definitionList = query.latestVersion().listPage(processDefinitionBo.getPageNum(), processDefinitionBo.getPageSize());
+        List<Deployment> deploymentList = null;
+        if (CollUtil.isNotEmpty(definitionList)) {
+            List<String> deploymentIds = StreamUtils.toList(definitionList, ProcessDefinition::getDeploymentId);
+            deploymentList = repositoryService.createDeploymentQuery().deploymentIds(deploymentIds).list();
+        }
+        for (ProcessDefinition processDefinition : definitionList) {
+            ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class);
+            if (CollUtil.isNotEmpty(deploymentList)) {
+                // 閮ㄧ讲鏃堕棿
+                deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> {
+                    processDefinitionVo.setDeploymentTime(e.getDeploymentTime());
+                });
+            }
+            processDefinitionVoList.add(processDefinitionVo);
+        }
+        // 鎬昏褰曟暟
+        long total = query.count();
+
+        return new TableDataInfo<>(processDefinitionVoList, total);
+    }
+
+    /**
+     * 鏌ヨ鍘嗗彶娴佺▼瀹氫箟鍒楄〃
+     *
+     * @param key 娴佺▼瀹氫箟key
+     */
+    @Override
+    public List<ProcessDefinitionVo> getProcessDefinitionListByKey(String key) {
+        List<ProcessDefinitionVo> processDefinitionVoList = new ArrayList<>();
+        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
+        List<ProcessDefinition> definitionList = query.processDefinitionTenantId(TenantHelper.getTenantId()).processDefinitionKey(key).list();
+        List<Deployment> deploymentList = null;
+        if (CollUtil.isNotEmpty(definitionList)) {
+            List<String> deploymentIds = definitionList.stream().map(ProcessDefinition::getDeploymentId).collect(Collectors.toList());
+            deploymentList = repositoryService.createDeploymentQuery()
+                .deploymentIds(deploymentIds).list();
+        }
+        for (ProcessDefinition processDefinition : definitionList) {
+            ProcessDefinitionVo processDefinitionVo = BeanUtil.toBean(processDefinition, ProcessDefinitionVo.class);
+            if (CollUtil.isNotEmpty(deploymentList)) {
+                // 閮ㄧ讲鏃堕棿
+                deploymentList.stream().filter(e -> e.getId().equals(processDefinition.getDeploymentId())).findFirst().ifPresent(e -> {
+                    processDefinitionVo.setDeploymentTime(e.getDeploymentTime());
+                });
+            }
+            processDefinitionVoList.add(processDefinitionVo);
+        }
+        return CollectionUtil.reverse(processDefinitionVoList);
+    }
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟鍥剧墖
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @SneakyThrows
+    @Override
+    public String processDefinitionImage(String processDefinitionId) {
+        InputStream inputStream = repositoryService.getProcessDiagram(processDefinitionId);
+        return Base64.encode(IOUtils.toByteArray(inputStream));
+    }
+
+    /**
+     * 鏌ョ湅娴佺▼瀹氫箟xml鏂囦欢
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Override
+    public String processDefinitionXml(String processDefinitionId) {
+        StringBuilder xml = new StringBuilder();
+        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
+        InputStream inputStream;
+        try {
+            inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
+            xml.append(IOUtils.toString(inputStream, StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return xml.toString();
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼瀹氫箟
+     *
+     * @param deploymentId        閮ㄧ讲id
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Override
+    public boolean deleteDeployment(String deploymentId, String processDefinitionId) {
+        try {
+            List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
+                .processDefinitionId(processDefinitionId).list();
+            if (CollectionUtil.isNotEmpty(taskInstanceList)) {
+                throw new ServiceException("褰撳墠娴佺▼瀹氫箟宸茶浣跨敤涓嶅彲鍒犻櫎锛�");
+            }
+            //鍒犻櫎娴佺▼瀹氫箟
+            repositoryService.deleteDeployment(deploymentId);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 婵�娲绘垨鑰呮寕璧锋祦绋嬪畾涔�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Override
+    public boolean updateProcessDefState(String processDefinitionId) {
+        try {
+            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionId(processDefinitionId).processDefinitionTenantId(TenantHelper.getTenantId()).singleResult();
+            //灏嗗綋鍓嶄负鎸傝捣鐘舵�佹洿鏂颁负婵�娲荤姸鎬�
+            //鍙傛暟璇存槑锛氬弬鏁�1锛氭祦绋嬪畾涔塱d,鍙傛暟2锛氭槸鍚︽縺娲伙紙true鏄惁绾ц仈瀵瑰簲娴佺▼瀹炰緥锛屾縺娲讳簡鍒欏搴旀祦绋嬪疄渚嬮兘鍙互瀹℃壒锛夛紝
+            //鍙傛暟3锛氫粈涔堟椂鍊欐縺娲伙紝濡傛灉涓簄ull鍒欑珛鍗虫縺娲伙紝濡傛灉涓哄叿浣撴椂闂村垯鍒拌揪姝ゆ椂闂村悗婵�娲�
+            if (processDefinition.isSuspended()) {
+                repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
+            } else {
+                repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException("鎿嶄綔澶辫触:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 杩佺Щ娴佺▼瀹氫箟
+     *
+     * @param currentProcessDefinitionId 褰撳墠娴佺▼瀹氫箟id
+     * @param fromProcessDefinitionId    闇�瑕佽縼绉诲埌鐨勬祦绋嬪畾涔塱d
+     */
+
+    @Override
+    public boolean migrationProcessDefinition(String currentProcessDefinitionId, String fromProcessDefinitionId) {
+        try {
+            // 杩佺Щ楠岃瘉
+            boolean migrationValid = processMigrationService.createProcessInstanceMigrationBuilder()
+                .migrateToProcessDefinition(currentProcessDefinitionId)
+                .validateMigrationOfProcessInstances(fromProcessDefinitionId)
+                .isMigrationValid();
+            if (!migrationValid) {
+                throw new ServiceException("娴佺▼瀹氫箟宸紓杩囧ぇ鏃犳硶杩佺Щ锛岃淇敼娴佺▼鍥�");
+            }
+            // 宸茬粨鏉熺殑娴佺▼瀹炰緥涓嶄細杩佺Щ
+            processMigrationService.createProcessInstanceMigrationBuilder()
+                .migrateToProcessDefinition(currentProcessDefinitionId)
+                .migrateProcessInstances(fromProcessDefinitionId);
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 娴佺▼瀹氫箟杞崲涓烘ā鍨�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    @Override
+    public boolean convertToModel(String processDefinitionId) {
+        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+            .processDefinitionId(processDefinitionId).singleResult();
+        InputStream inputStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), pd.getResourceName());
+        Model model = repositoryService.createModelQuery().modelKey(pd.getKey()).modelTenantId(TenantHelper.getTenantId()).singleResult();
+        try {
+            if (ObjectUtil.isNotNull(model)) {
+                repositoryService.addModelEditorSource(model.getId(), IoUtil.readBytes(inputStream));
+            } else {
+                Model modelData = repositoryService.newModel();
+                modelData.setKey(pd.getKey());
+                modelData.setName(pd.getName());
+                modelData.setTenantId(pd.getTenantId());
+                repositoryService.saveModel(modelData);
+                repositoryService.addModelEditorSource(modelData.getId(), IoUtil.readBytes(inputStream));
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 閫氳繃zip鎴杧ml閮ㄧ讲娴佺▼瀹氫箟
+     *
+     * @param file         鏂囦欢
+     * @param categoryCode 鍒嗙被
+     */
+    @Override
+    public boolean deployByFile(MultipartFile file, String categoryCode) {
+        try {
+            WfCategory wfCategory = wfCategoryService.queryByCategoryCode(categoryCode);
+            if (wfCategory == null) {
+                throw new ServiceException("娴佺▼鍒嗙被涓嶅瓨鍦�");
+            }
+            // 鏂囦欢鍚� = 娴佺▼鍚嶇О-娴佺▼key
+            String filename = file.getOriginalFilename();
+            assert filename != null;
+            String[] splitFilename = filename.substring(0, filename.lastIndexOf(".")).split("-");
+            if (splitFilename.length < 2) {
+                throw new ServiceException("娴佺▼鍒嗙被涓嶈兘涓虹┖(鏂囦欢鍚� = 娴佺▼鍚嶇О-娴佺▼key)");
+            }
+            //娴佺▼鍚嶇О
+            String processName = splitFilename[0];
+            //娴佺▼key
+            String processKey = splitFilename[1];
+            // 鏂囦欢鍚庣紑鍚�
+            String suffix = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase();
+            InputStream inputStream = file.getInputStream();
+            Deployment deployment;
+            if (FlowConstant.ZIP.equals(suffix)) {
+                deployment = repositoryService.createDeployment()
+                    .tenantId(TenantHelper.getTenantId())
+                    .addZipInputStream(new ZipInputStream(inputStream)).name(processName).key(processKey).category(categoryCode).deploy();
+            } else {
+                String[] list = ResourceNameUtil.BPMN_RESOURCE_SUFFIXES;
+                boolean flag = false;
+                for (String str : list) {
+                    if (filename.contains(str)) {
+                        flag = true;
+                        break;
+                    }
+                }
+                if (flag) {
+                    deployment = repositoryService.createDeployment()
+                        .tenantId(TenantHelper.getTenantId())
+                        .addInputStream(filename, inputStream).name(processName).key(processKey).category(categoryCode).deploy();
+                } else {
+                    throw new ServiceException("鏂囦欢绫诲瀷涓婁紶閿欒锛�");
+                }
+            }
+            // 鏇存柊鍒嗙被
+            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
+            repositoryService.setProcessDefinitionCategory(definition.getId(), categoryCode);
+
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new ServiceException("閮ㄧ讲澶辫触" + e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
new file mode 100644
index 0000000..b195cc7
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
@@ -0,0 +1,695 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.BusinessStatusEnum;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.ActHiProcinst;
+import org.dromara.workflow.domain.bo.ProcessInstanceBo;
+import org.dromara.workflow.domain.bo.ProcessInvalidBo;
+import org.dromara.workflow.domain.bo.TaskUrgingBo;
+import org.dromara.workflow.domain.vo.ActHistoryInfoVo;
+import org.dromara.workflow.domain.vo.GraphicInfoVo;
+import org.dromara.workflow.domain.vo.ProcessInstanceVo;
+import org.dromara.workflow.domain.vo.TaskVo;
+import org.dromara.workflow.flowable.CustomDefaultProcessDiagramGenerator;
+import org.dromara.workflow.flowable.strategy.FlowEventStrategy;
+import org.dromara.workflow.flowable.cmd.DeleteExecutionCmd;
+import org.dromara.workflow.flowable.cmd.ExecutionChildByExecutionIdCmd;
+import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
+import org.dromara.workflow.service.IActHiProcinstService;
+import org.dromara.workflow.service.IActProcessInstanceService;
+import org.dromara.workflow.utils.WorkflowUtils;
+import org.flowable.bpmn.model.*;
+import org.flowable.engine.*;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.history.HistoricProcessInstanceQuery;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.engine.runtime.ProcessInstanceQuery;
+import org.flowable.engine.task.Attachment;
+import org.flowable.engine.task.Comment;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.awt.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * 娴佺▼瀹炰緥 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ActProcessInstanceServiceImpl implements IActProcessInstanceService {
+
+    private final RepositoryService repositoryService;
+    private final RuntimeService runtimeService;
+    private final HistoryService historyService;
+    private final TaskService taskService;
+    private final IActHiProcinstService actHiProcinstService;
+    private final ManagementService managementService;
+    private final FlowEventStrategy flowEventStrategy;
+
+    @Value("${flowable.activity-font-name}")
+    private String activityFontName;
+
+    @Value("${flowable.label-font-name}")
+    private String labelFontName;
+
+    @Value("${flowable.annotation-font-name}")
+    private String annotationFontName;
+
+    /**
+     * 鍒嗛〉鏌ヨ姝e湪杩愯鐨勬祦绋嬪疄渚�
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<ProcessInstanceVo> getProcessInstanceRunningByPage(ProcessInstanceBo processInstanceBo) {
+        List<ProcessInstanceVo> list = new ArrayList<>();
+        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
+        query.processInstanceTenantId(TenantHelper.getTenantId());
+        if (StringUtils.isNotBlank(processInstanceBo.getName())) {
+            query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getKey())) {
+            query.processDefinitionKey(processInstanceBo.getKey());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getStartUserId())) {
+            query.startedBy(processInstanceBo.getStartUserId());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) {
+            query.processInstanceBusinessKey(processInstanceBo.getBusinessKey());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) {
+            query.processDefinitionCategory(processInstanceBo.getCategoryCode());
+        }
+        query.orderByStartTime().desc();
+        List<ProcessInstance> processInstances = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize());
+        for (ProcessInstance processInstance : processInstances) {
+            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class);
+            processInstanceVo.setIsSuspended(processInstance.isSuspended());
+            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus()));
+            list.add(processInstanceVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ宸茬粨鏉熺殑娴佺▼瀹炰緥
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<ProcessInstanceVo> getProcessInstanceFinishByPage(ProcessInstanceBo processInstanceBo) {
+        List<ProcessInstanceVo> list = new ArrayList<>();
+        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().finished()
+            .orderByProcessInstanceEndTime().desc();
+        query.processInstanceTenantId(TenantHelper.getTenantId());
+        if (StringUtils.isNotEmpty(processInstanceBo.getName())) {
+            query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getKey())) {
+            query.processDefinitionKey(processInstanceBo.getKey());
+        }
+        if (StringUtils.isNotEmpty(processInstanceBo.getStartUserId())) {
+            query.startedBy(processInstanceBo.getStartUserId());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) {
+            query.processInstanceBusinessKey(processInstanceBo.getBusinessKey());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) {
+            query.processDefinitionCategory(processInstanceBo.getCategoryCode());
+        }
+        List<HistoricProcessInstance> historicProcessInstances = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize());
+        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
+            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(historicProcessInstance, ProcessInstanceVo.class);
+            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(historicProcessInstance.getBusinessStatus()));
+            list.add(processInstanceVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥�
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @SneakyThrows
+    @Override
+    public String getHistoryProcessImage(String processInstanceId) {
+        String processDefinitionId;
+        // 鑾峰彇褰撳墠鐨勬祦绋嬪疄渚�
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+        // 濡傛灉娴佺▼宸茬粡缁撴潫锛屽垯寰楀埌缁撴潫鑺傜偣
+        if (Objects.isNull(processInstance)) {
+            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+            processDefinitionId = pi.getProcessDefinitionId();
+        } else {
+            // 鏍规嵁娴佺▼瀹炰緥ID鑾峰緱褰撳墠澶勪簬娲诲姩鐘舵�佺殑ActivityId鍚堥泦
+            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+            processDefinitionId = pi.getProcessDefinitionId();
+        }
+
+        // 鑾峰緱娲诲姩鐨勮妭鐐�
+        List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
+
+        List<String> highLightedFlows = new ArrayList<>();
+        List<String> highLightedNodes = new ArrayList<>();
+        //楂樹寒
+        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
+            if (FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType())) {
+                //楂樹寒绾�
+                highLightedFlows.add(tempActivity.getActivityId());
+            } else {
+                //楂樹寒鑺傜偣
+                if (tempActivity.getEndTime() == null) {
+                    highLightedNodes.add(Color.RED.toString() + tempActivity.getActivityId());
+                } else {
+                    highLightedNodes.add(tempActivity.getActivityId());
+                }
+            }
+        }
+        List<String> highLightedNodeList = new ArrayList<>();
+        //杩愯涓殑鑺傜偣
+        List<String> redNodeCollect = StreamUtils.filter(highLightedNodes, e -> e.contains(Color.RED.toString()));
+        //鎺掗櫎涓庤繍琛屼腑鐩稿悓鐨勮妭鐐�
+        for (String nodeId : highLightedNodes) {
+            if (!nodeId.contains(Color.RED.toString()) && !redNodeCollect.contains(Color.RED + nodeId)) {
+                highLightedNodeList.add(nodeId);
+            }
+        }
+        highLightedNodeList.addAll(redNodeCollect);
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+        CustomDefaultProcessDiagramGenerator diagramGenerator = new CustomDefaultProcessDiagramGenerator();
+        InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodeList, highLightedFlows, activityFontName, labelFontName, annotationFontName, null, 1.0, true);
+        return Base64.encode(IOUtils.toByteArray(inputStream));
+    }
+
+    /**
+     * 閫氳繃娴佺▼瀹炰緥id鑾峰彇鍘嗗彶娴佺▼鍥捐繍琛屼腑锛屽巻鍙茬瓑鑺傜偣
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @Override
+    public Map<String, Object> getHistoryProcessList(String processInstanceId) {
+        Map<String, Object> map = new HashMap<>();
+        List<Map<String, Object>> taskList = new ArrayList<>();
+        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+        StringBuilder xml = new StringBuilder();
+        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
+        // 鑾峰彇鑺傜偣
+        List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
+        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
+            Map<String, Object> task = new HashMap<>();
+            if (!FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType()) &&
+                !FlowConstant.PARALLEL_GATEWAY.equals(tempActivity.getActivityType()) &&
+                !FlowConstant.EXCLUSIVE_GATEWAY.equals(tempActivity.getActivityType()) &&
+                !FlowConstant.INCLUSIVE_GATEWAY.equals(tempActivity.getActivityType())
+            ) {
+                task.put("key", tempActivity.getActivityId());
+                task.put("completed", tempActivity.getEndTime() != null);
+                task.put("activityType", tempActivity.getActivityType());
+                taskList.add(task);
+            }
+        }
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+        if (processInstance != null) {
+            taskList = taskList.stream().filter(e -> !e.get("activityType").equals(FlowConstant.END_EVENT)).collect(Collectors.toList());
+        }
+        //鏌ヨ鍑鸿繍琛屼腑鑺傜偣
+        List<Map<String, Object>> runtimeNodeList = taskList.stream().filter(e -> !(Boolean) e.get("completed")).collect(Collectors.toList());
+        if (CollUtil.isNotEmpty(runtimeNodeList)) {
+            Iterator<Map<String, Object>> iterator = taskList.iterator();
+            while (iterator.hasNext()) {
+                Map<String, Object> next = iterator.next();
+                runtimeNodeList.stream().filter(t -> t.get("key").equals(next.get("key")) && (Boolean) next.get("completed")).findFirst().ifPresent(t -> iterator.remove());
+            }
+        }
+        map.put("taskList", taskList);
+        List<ActHistoryInfoVo> historyTaskList = getHistoryTaskList(processInstanceId);
+        map.put("historyList", historyTaskList);
+        InputStream inputStream;
+        try {
+            inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
+            xml.append(IOUtils.toString(inputStream, String.valueOf(StandardCharsets.UTF_8)));
+            map.put("xml", xml.toString());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return map;
+    }
+
+    /**
+     * 鑾峰彇鍘嗗彶浠诲姟鑺傜偣淇℃伅
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    private List<ActHistoryInfoVo> getHistoryTaskList(String processInstanceId) {
+        //鏌ヨ浠诲姟鍔炵悊璁板綍
+        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByHistoricTaskInstanceEndTime().desc().list();
+        list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed());
+        List<ActHistoryInfoVo> actHistoryInfoVoList = new ArrayList<>();
+        for (HistoricTaskInstance historicTaskInstance : list) {
+            ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo();
+            BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo);
+            actHistoryInfoVo.setStatus(actHistoryInfoVo.getEndTime() == null ? "寰呭鐞�" : "宸插鐞�");
+            if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) {
+                actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis()));
+            }
+            actHistoryInfoVoList.add(actHistoryInfoVo);
+        }
+        List<ActHistoryInfoVo> historyInfoVoList = new ArrayList<>();
+        Map<String, List<ActHistoryInfoVo>> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey);
+        for (Map.Entry<String, List<ActHistoryInfoVo>> entry : groupByKey.entrySet()) {
+            ActHistoryInfoVo historyInfoVo = new ActHistoryInfoVo();
+            BeanUtils.copyProperties(entry.getValue().get(0), historyInfoVo);
+            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst()
+                .ifPresent(e -> {
+                    historyInfoVo.setStatus("寰呭鐞�");
+                    historyInfoVo.setStartTime(e.getStartTime());
+                    historyInfoVo.setEndTime(null);
+                    historyInfoVo.setRunDuration(null);
+                });
+            historyInfoVoList.add(historyInfoVo);
+        }
+        return historyInfoVoList;
+    }
+
+    /**
+     * 鑾峰彇瀹℃壒璁板綍
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @Override
+    public Map<String, Object> getHistoryRecord(String processInstanceId) {
+        Map<String, Object> map = new HashMap<>();
+        // 鏌ヨ浠诲姟鍔炵悊璁板綍
+        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
+            .processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).orderByHistoricTaskInstanceEndTime().desc().list();
+        list = StreamUtils.sorted(list, Comparator.comparing(HistoricTaskInstance::getEndTime, Comparator.nullsFirst(Date::compareTo)).reversed());
+        List<ActHistoryInfoVo> actHistoryInfoVoList = new ArrayList<>();
+        List<Comment> processInstanceComments = taskService.getProcessInstanceComments(processInstanceId);
+        //闄勪欢
+        List<Attachment> attachmentList = taskService.getProcessInstanceAttachments(processInstanceId);
+        for (HistoricTaskInstance historicTaskInstance : list) {
+            ActHistoryInfoVo actHistoryInfoVo = new ActHistoryInfoVo();
+            BeanUtils.copyProperties(historicTaskInstance, actHistoryInfoVo);
+            if (actHistoryInfoVo.getEndTime() == null) {
+                actHistoryInfoVo.setStatus(TaskStatusEnum.WAITING.getStatus());
+                actHistoryInfoVo.setStatusName(TaskStatusEnum.WAITING.getDesc());
+            }
+            if (CollUtil.isNotEmpty(processInstanceComments)) {
+                processInstanceComments.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).findFirst().ifPresent(e -> {
+                    actHistoryInfoVo.setComment(e.getFullMessage());
+                    actHistoryInfoVo.setStatus(e.getType());
+                    actHistoryInfoVo.setStatusName(TaskStatusEnum.findByStatus(e.getType()));
+                });
+            }
+            if (ObjectUtil.isNotEmpty(historicTaskInstance.getDurationInMillis())) {
+                actHistoryInfoVo.setRunDuration(getDuration(historicTaskInstance.getDurationInMillis()));
+            }
+            try {
+                actHistoryInfoVo.setAssignee(StringUtils.isNotBlank(historicTaskInstance.getAssignee()) ? Long.valueOf(historicTaskInstance.getAssignee()) : null);
+            } catch (NumberFormatException ignored) {
+                log.warn("褰撳墠浠诲姟銆恵}銆�,鍔炵悊浜鸿浆鎹汉鍛業D銆恵}銆戝紓甯革紒", historicTaskInstance.getName(), historicTaskInstance.getAssignee());
+            }
+            //闄勪欢
+            if (CollUtil.isNotEmpty(attachmentList)) {
+                List<Attachment> attachments = attachmentList.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).collect(Collectors.toList());
+                if (CollUtil.isNotEmpty(attachments)) {
+                    actHistoryInfoVo.setAttachmentList(attachments);
+                }
+            }
+            actHistoryInfoVoList.add(actHistoryInfoVo);
+        }
+        List<ActHistoryInfoVo> collect = new ArrayList<>();
+        // 寰呭姙鐞�
+        List<ActHistoryInfoVo> waitingTask = StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() == null);
+        // 宸插姙鐞�
+        List<ActHistoryInfoVo> finishTask = StreamUtils.filter(actHistoryInfoVoList, e -> e.getEndTime() != null);
+        collect.addAll(waitingTask);
+        collect.addAll(finishTask);
+        // 瀹℃壒璁板綍
+        map.put("historyRecordList", collect);
+        List<ActHistoryInfoVo> nodeInfoList = new ArrayList<>();
+        Map<String, List<ActHistoryInfoVo>> groupByKey = StreamUtils.groupByKey(actHistoryInfoVoList, ActHistoryInfoVo::getTaskDefinitionKey);
+        for (Map.Entry<String, List<ActHistoryInfoVo>> entry : groupByKey.entrySet()) {
+            ActHistoryInfoVo actHistoryInfoVo = BeanUtil.toBean(entry.getValue().get(0), ActHistoryInfoVo.class);
+            String nickName = entry.getValue().stream().filter(e -> StringUtils.isNotBlank(e.getNickName()) && e.getEndTime() == null).map(ActHistoryInfoVo::getNickName).toList().stream().distinct().collect(Collectors.joining(StringUtils.SEPARATOR));
+            if (StringUtils.isNotBlank(nickName)) {
+                actHistoryInfoVo.setNickName(nickName);
+            }
+            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() != null).findFirst()
+                .ifPresent(e -> {
+                    actHistoryInfoVo.setStatus("宸插鐞�");
+                    actHistoryInfoVo.setStartTime(e.getStartTime());
+                });
+            actHistoryInfoVoList.stream().filter(e -> e.getTaskDefinitionKey().equals(entry.getKey()) && e.getEndTime() == null).findFirst()
+                .ifPresent(e -> {
+                    actHistoryInfoVo.setStatus("寰呭鐞�");
+                    actHistoryInfoVo.setStartTime(e.getStartTime());
+                    actHistoryInfoVo.setEndTime(null);
+                    actHistoryInfoVo.setRunDuration(null);
+                });
+            nodeInfoList.add(actHistoryInfoVo);
+        }
+        // 鑺傜偣淇℃伅
+        map.put("nodeListInfo", nodeInfoList);
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(list.get(0).getProcessDefinitionId());
+        List<GraphicInfoVo> graphicInfoVos = new ArrayList<>();
+        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
+        //鑺傜偣鍥惧舰淇℃伅
+        buildGraphicInfo(flowElements, graphicInfoVos, bpmnModel);
+        map.put("graphicInfoVos", graphicInfoVos);
+        return map;
+    }
+
+    /**
+     * 鏋勫缓鑺傜偣鍥惧舰淇℃伅
+     *
+     * @param flowElements 鑺傜偣
+     */
+    private static void buildGraphicInfo(Collection<FlowElement> flowElements, List<GraphicInfoVo> graphicInfoVos, BpmnModel bpmnModel) {
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof SubProcess) {
+                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
+                buildGraphicInfo(subFlowElements, graphicInfoVos, bpmnModel);
+            } else {
+                if (flowElement instanceof UserTask) {
+                    GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowElement.getId());
+                    GraphicInfoVo graphicInfoVo = BeanUtil.toBean(graphicInfo, GraphicInfoVo.class);
+                    graphicInfoVo.setNodeId(flowElement.getId());
+                    graphicInfoVo.setNodeName(flowElement.getName());
+                    graphicInfoVos.add(graphicInfoVo);
+                }
+            }
+        }
+    }
+
+    /**
+     * 浠诲姟瀹屾垚鏃堕棿澶勭悊
+     *
+     * @param time 鏃堕棿
+     */
+    private String getDuration(long time) {
+
+        long day = time / (24 * 60 * 60 * 1000);
+        long hour = (time / (60 * 60 * 1000) - day * 24);
+        long minute = ((time / (60 * 1000)) - day * 24 * 60 - hour * 60);
+        long second = (time / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);
+
+        if (day > 0) {
+            return day + "澶�" + hour + "灏忔椂" + minute + "鍒嗛挓";
+        }
+        if (hour > 0) {
+            return hour + "灏忔椂" + minute + "鍒嗛挓";
+        }
+        if (minute > 0) {
+            return minute + "鍒嗛挓";
+        }
+        if (second > 0) {
+            return second + "绉�";
+        } else {
+            return 0 + "绉�";
+        }
+    }
+
+    /**
+     * 浣滃簾娴佺▼瀹炰緥锛屼笉浼氬垹闄ゅ巻鍙茶褰�(鍒犻櫎杩愯涓殑瀹炰緥)
+     *
+     * @param processInvalidBo 鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteRuntimeProcessInst(ProcessInvalidBo processInvalidBo) {
+        try {
+            List<Task> list = taskService.createTaskQuery().processInstanceId(processInvalidBo.getProcessInstanceId())
+                .taskTenantId(TenantHelper.getTenantId()).list();
+            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
+            if (CollUtil.isNotEmpty(subTasks)) {
+                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
+            }
+            String deleteReason = LoginHelper.getLoginUser().getNickname() + "浣滃簾浜嗗綋鍓嶇敵璇凤紒";
+            if (StringUtils.isNotBlank(processInvalidBo.getDeleteReason())) {
+                deleteReason = LoginHelper.getLoginUser().getNickname() + "浣滃簾鐞嗙敱:" + processInvalidBo.getDeleteReason();
+            }
+            for (Task task : StreamUtils.filter(list, e -> StringUtils.isBlank(e.getParentTaskId()))) {
+                taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.INVALID.getStatus(), deleteReason);
+            }
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceId(processInvalidBo.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+            if (ObjectUtil.isNotEmpty(historicProcessInstance) && BusinessStatusEnum.FINISH.getStatus().equals(historicProcessInstance.getBusinessStatus())) {
+                throw new ServiceException("璇ュ崟鎹凡瀹屾垚鐢宠锛�");
+            }
+            runtimeService.updateBusinessStatus(processInvalidBo.getProcessInstanceId(), BusinessStatusEnum.INVALID.getStatus());
+            runtimeService.deleteProcessInstance(processInvalidBo.getProcessInstanceId(), deleteReason);
+            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey());
+            if (processHandler != null) {
+                processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.INVALID.getStatus(), false);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteRuntimeProcessAndHisInst(List<String> processInstanceIds) {
+        try {
+            // 1.鍒犻櫎杩愯涓祦绋嬪疄渚�
+            List<Task> list = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds)
+                .taskTenantId(TenantHelper.getTenantId()).list();
+            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
+            if (CollUtil.isNotEmpty(subTasks)) {
+                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
+            }
+            runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "鍒犻櫎浜嗗綋鍓嶆祦绋嬬敵璇�");
+            // 2.鍒犻櫎鍘嗗彶璁板綍
+            List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceTenantId(TenantHelper.getTenantId()).processInstanceIds(new HashSet<>(processInstanceIds)).list();
+            if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) {
+                historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 鎸夌収涓氬姟id鍒犻櫎 杩愯涓殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param businessKeys 涓氬姟id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteRuntimeProcessAndHisInstByBusinessKeys(List<String> businessKeys) {
+        try {
+            // 1.鍒犻櫎杩愯涓祦绋嬪疄渚�
+            List<ActHiProcinst> actHiProcinsts = actHiProcinstService.selectByBusinessKeyIn(businessKeys);
+            if (CollUtil.isEmpty(actHiProcinsts)) {
+                log.warn("褰撳墠涓氬姟ID:{}鏌ヨ鍒版祦绋嬪疄渚嬩负绌猴紒", businessKeys);
+                return false;
+            }
+            List<String> processInstanceIds = StreamUtils.toList(actHiProcinsts, ActHiProcinst::getId);
+            List<Task> list = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds)
+                .taskTenantId(TenantHelper.getTenantId()).list();
+            List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
+            if (CollUtil.isNotEmpty(subTasks)) {
+                subTasks.forEach(e -> taskService.deleteTask(e.getId()));
+            }
+            runtimeService.bulkDeleteProcessInstances(processInstanceIds, LoginHelper.getUserId() + "鍒犻櫎浜嗗綋鍓嶆祦绋嬬敵璇�");
+            // 2.鍒犻櫎鍘嗗彶璁板綍
+            List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceTenantId(TenantHelper.getTenantId()).processInstanceIds(new HashSet<>(processInstanceIds)).list();
+            if (ObjectUtil.isNotEmpty(historicProcessInstanceList)) {
+                historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 宸插畬鎴愮殑瀹炰緥 鍒犻櫎绋嬪疄渚嬶紝鍒犻櫎鍘嗗彶璁板綍锛屽垹闄や笟鍔′笌娴佺▼鍏宠仈淇℃伅
+     *
+     * @param processInstanceIds 娴佺▼瀹炰緥id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteFinishProcessAndHisInst(List<String> processInstanceIds) {
+        try {
+            historyService.bulkDeleteHistoricProcessInstances(processInstanceIds);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 鎾ら攢娴佺▼鐢宠
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean cancelProcessApply(String processInstanceId) {
+        try {
+            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+                .processInstanceId(processInstanceId).processInstanceTenantId(TenantHelper.getTenantId()).startedBy(String.valueOf(LoginHelper.getUserId())).singleResult();
+            if (ObjectUtil.isNull(processInstance)) {
+                throw new ServiceException("鎮ㄤ笉鏄祦绋嬪彂璧蜂汉,鎾ら攢澶辫触!");
+            }
+            if (processInstance.isSuspended()) {
+                throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+            }
+            if (BusinessStatusEnum.CANCEL.getStatus().equals(processInstance.getBusinessStatus())) {
+                throw new ServiceException("璇ュ崟鎹凡鎾ら攢锛�");
+            }
+            List<Task> taskList = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(processInstanceId).list();
+            for (Task task : taskList) {
+                taskService.setAssignee(task.getId(), String.valueOf(LoginHelper.getUserId()));
+                taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.CANCEL.getStatus(), LoginHelper.getLoginUser().getNickname() + "锛氭挙閿�鐢宠");
+            }
+            HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().finished().orderByHistoricTaskInstanceEndTime().asc().list().get(0);
+            List<String> nodeIds = StreamUtils.toList(taskList, Task::getTaskDefinitionKey);
+            runtimeService.createChangeActivityStateBuilder()
+                .processInstanceId(processInstanceId)
+                .moveActivityIdsToSingleActivityId(nodeIds, historicTaskInstance.getTaskDefinitionKey()).changeState();
+            Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(processInstanceId).list().get(0);
+            taskService.setAssignee(task.getId(), historicTaskInstance.getAssignee());
+            //鑾峰彇骞惰缃戝叧鎵ц鍚庝繚鐣欑殑鎵ц瀹炰緥鏁版嵁
+            ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId());
+            List<ExecutionEntity> executionEntities = managementService.executeCommand(childByExecutionIdCmd);
+            //鍒犻櫎娴佺▼瀹炰緥鍨冨溇鏁版嵁
+            for (ExecutionEntity executionEntity : executionEntities) {
+                DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId());
+                managementService.executeCommand(deleteExecutionCmd);
+            }
+            runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.CANCEL.getStatus());
+            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
+            if (processHandler != null) {
+                processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.CANCEL.getStatus(), false);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException("鎾ら攢澶辫触:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ褰撳墠鐧诲綍浜哄崟鎹�
+     *
+     * @param processInstanceBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<ProcessInstanceVo> getCurrentSubmitByPage(ProcessInstanceBo processInstanceBo) {
+        List<ProcessInstanceVo> list = new ArrayList<>();
+        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
+        query.processInstanceTenantId(TenantHelper.getTenantId());
+        query.startedBy(processInstanceBo.getStartUserId());
+        if (StringUtils.isNotBlank(processInstanceBo.getName())) {
+            query.processInstanceNameLikeIgnoreCase("%" + processInstanceBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getKey())) {
+            query.processDefinitionKey(processInstanceBo.getKey());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getBusinessKey())) {
+            query.processInstanceBusinessKey(processInstanceBo.getBusinessKey());
+        }
+        if (StringUtils.isNotBlank(processInstanceBo.getCategoryCode())) {
+            query.processDefinitionCategory(processInstanceBo.getCategoryCode());
+        }
+        query.orderByProcessInstanceStartTime().desc();
+        List<HistoricProcessInstance> historicProcessInstanceList = query.listPage(processInstanceBo.getPageNum(), processInstanceBo.getPageSize());
+        List<TaskVo> taskVoList = new ArrayList<>();
+        if (CollUtil.isNotEmpty(historicProcessInstanceList)) {
+            List<String> processInstanceIds = StreamUtils.toList(historicProcessInstanceList, HistoricProcessInstance::getId);
+            List<Task> taskList = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).taskTenantId(TenantHelper.getTenantId()).list();
+            for (Task task : taskList) {
+                taskVoList.add(BeanUtil.toBean(task, TaskVo.class));
+            }
+        }
+        for (HistoricProcessInstance processInstance : historicProcessInstanceList) {
+            ProcessInstanceVo processInstanceVo = BeanUtil.toBean(processInstance, ProcessInstanceVo.class);
+            processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstance.getBusinessStatus()));
+            if (CollUtil.isNotEmpty(taskVoList)) {
+                List<TaskVo> collect = StreamUtils.filter(taskVoList, e -> e.getProcessInstanceId().equals(processInstance.getId()));
+                processInstanceVo.setTaskVoList(CollUtil.isNotEmpty(collect) ? collect : Collections.emptyList());
+            }
+            list.add(processInstanceVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 浠诲姟鍌姙(缁欏綋鍓嶄换鍔″姙鐞嗕汉鍙戦�佺珯鍐呬俊锛岄偖浠讹紝鐭俊绛�)
+     *
+     * @param taskUrgingBo 浠诲姟鍌姙
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean taskUrging(TaskUrgingBo taskUrgingBo) {
+        try {
+            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+                .processInstanceId(taskUrgingBo.getProcessInstanceId())
+                .processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+            if (processInstance == null) {
+                throw new ServiceException("浠诲姟宸茬粨鏉燂紒");
+            }
+            String message = taskUrgingBo.getMessage();
+            if (StringUtils.isBlank(message)) {
+                message = "鎮ㄧ殑銆�" + processInstance.getName() + "銆戝崟鎹繕鏈鎵癸紝璇锋偍鍙婃椂澶勭悊銆�";
+            }
+            List<Task> list = taskService.createTaskQuery().processInstanceId(taskUrgingBo.getProcessInstanceId()).list();
+            WorkflowUtils.sendMessage(list, processInstance.getName(), taskUrgingBo.getMessageType(), message);
+        } catch (ServiceException e) {
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
new file mode 100644
index 0000000..653b733
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
@@ -0,0 +1,708 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.dto.RoleDTO;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.BusinessStatusEnum;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.bo.*;
+import org.dromara.workflow.domain.vo.MultiInstanceVo;
+import org.dromara.workflow.domain.vo.TaskVo;
+import org.dromara.workflow.domain.vo.WfCopy;
+import org.dromara.workflow.flowable.strategy.FlowEventStrategy;
+import org.dromara.workflow.flowable.cmd.*;
+import org.dromara.workflow.flowable.strategy.FlowProcessEventHandler;
+import org.dromara.workflow.flowable.strategy.FlowTaskEventHandler;
+import org.dromara.workflow.mapper.ActTaskMapper;
+import org.dromara.workflow.service.IActTaskService;
+import org.dromara.workflow.utils.WorkflowUtils;
+import org.flowable.common.engine.api.FlowableObjectNotFoundException;
+import org.flowable.common.engine.impl.identity.Authentication;
+import org.flowable.engine.*;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.flowable.task.api.history.HistoricTaskInstanceQuery;
+import org.flowable.task.service.impl.persistence.entity.TaskEntity;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.dromara.workflow.common.constant.FlowConstant.FLOWABLE_SKIP_EXPRESSION_ENABLED;
+import static org.dromara.workflow.common.constant.FlowConstant.INITIATOR;
+
+/**
+ * 浠诲姟 鏈嶅姟灞傚疄鐜�
+ *
+ * @author may
+ */
+@RequiredArgsConstructor
+@Service
+public class ActTaskServiceImpl implements IActTaskService {
+
+    private final RuntimeService runtimeService;
+    private final TaskService taskService;
+    private final HistoryService historyService;
+    private final IdentityService identityService;
+    private final ManagementService managementService;
+    private final FlowEventStrategy flowEventStrategy;
+    private final ActTaskMapper actTaskMapper;
+
+    /**
+     * 鍚姩浠诲姟
+     *
+     * @param startProcessBo 鍚姩娴佺▼鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> startWorkFlow(StartProcessBo startProcessBo) {
+        Map<String, Object> map = new HashMap<>();
+        if (StringUtils.isBlank(startProcessBo.getBusinessKey())) {
+            throw new ServiceException("鍚姩宸ヤ綔娴佹椂蹇呴』鍖呭惈涓氬姟ID");
+        }
+        // 鍒ゆ柇褰撳墠涓氬姟鏄惁鍚姩杩囨祦绋�
+        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(startProcessBo.getBusinessKey()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+        if (ObjectUtil.isNotEmpty(historicProcessInstance)) {
+            BusinessStatusEnum.checkStartStatus(historicProcessInstance.getBusinessStatus());
+        }
+        TaskQuery taskQuery = taskService.createTaskQuery();
+        List<Task> taskResult = taskQuery.processInstanceBusinessKey(startProcessBo.getBusinessKey()).taskTenantId(TenantHelper.getTenantId()).list();
+        if (CollUtil.isNotEmpty(taskResult)) {
+            if (CollUtil.isNotEmpty(startProcessBo.getVariables())) {
+                taskService.setVariables(taskResult.get(0).getId(), startProcessBo.getVariables());
+            }
+            map.put("processInstanceId", taskResult.get(0).getProcessInstanceId());
+            map.put("taskId", taskResult.get(0).getId());
+            return map;
+        }
+        // 璁剧疆鍚姩浜�
+        identityService.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId()));
+        Authentication.setAuthenticatedUserId(String.valueOf(LoginHelper.getUserId()));
+        // 鍚姩娴佺▼瀹炰緥锛堟彁浜ょ敵璇凤級
+        Map<String, Object> variables = startProcessBo.getVariables();
+        // 鍚姩璺宠繃琛ㄨ揪寮�
+        variables.put(FLOWABLE_SKIP_EXPRESSION_ENABLED, true);
+        // 娴佺▼鍙戣捣浜�
+        variables.put(INITIATOR, (String.valueOf(LoginHelper.getUserId())));
+        ProcessInstance pi;
+        try {
+            pi = runtimeService.startProcessInstanceByKeyAndTenantId(startProcessBo.getProcessKey(), startProcessBo.getBusinessKey(), variables, TenantHelper.getTenantId());
+        } catch (FlowableObjectNotFoundException e) {
+            throw new ServiceException("鎵句笉鍒板綋鍓嶃��" + startProcessBo.getProcessKey() + "銆戞祦绋嬪畾涔夛紒");
+        }
+        // 灏嗘祦绋嬪畾涔夊悕绉� 浣滀负 娴佺▼瀹炰緥鍚嶇О
+        runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName());
+        // 鐢宠浜烘墽琛屾祦绋�
+        List<Task> taskList = taskService.createTaskQuery().processInstanceId(pi.getId()).taskTenantId(TenantHelper.getTenantId()).list();
+        if (taskList.size() > 1) {
+            throw new ServiceException("璇锋鏌ユ祦绋嬬涓�涓幆鑺傛槸鍚︿负鐢宠浜猴紒");
+        }
+
+        runtimeService.updateBusinessStatus(pi.getProcessInstanceId(), BusinessStatusEnum.DRAFT.getStatus());
+        taskService.setAssignee(taskList.get(0).getId(), LoginHelper.getUserId().toString());
+        taskService.setVariable(taskList.get(0).getId(), "processInstanceId", pi.getProcessInstanceId());
+        map.put("processInstanceId", pi.getProcessInstanceId());
+        map.put("taskId", taskList.get(0).getId());
+        return map;
+    }
+
+    /**
+     * 鍔炵悊浠诲姟
+     *
+     * @param completeTaskBo 鍔炵悊浠诲姟鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean completeTask(CompleteTaskBo completeTaskBo) {
+        try {
+            List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
+            String userId = String.valueOf(LoginHelper.getUserId());
+            TaskQuery taskQuery = taskService.createTaskQuery();
+            taskQuery.taskId(completeTaskBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskCandidateOrAssigned(userId);
+            if (CollUtil.isNotEmpty(roles)) {
+                List<String> groupIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId()));
+                taskQuery.taskCandidateGroupIn(groupIds);
+            }
+            Task task = taskQuery.singleResult();
+            if (task == null) {
+                throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+            }
+            if (task.isSuspended()) {
+                throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+            }
+            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
+            //鍔炵悊濮旀墭浠诲姟
+            if (ObjectUtil.isNotEmpty(task.getDelegationState()) && FlowConstant.PENDING.equals(task.getDelegationState().name())) {
+                taskService.resolveTask(completeTaskBo.getTaskId());
+                TaskEntity newTask = WorkflowUtils.createNewTask(task);
+                taskService.addComment(newTask.getId(), task.getProcessInstanceId(), completeTaskBo.getMessage());
+                taskService.complete(newTask.getId());
+                return true;
+            }
+            //闄勪欢涓婁紶
+            AttachmentCmd attachmentCmd = new AttachmentCmd(completeTaskBo.getFileId(), task.getId(), task.getProcessInstanceId());
+            managementService.executeCommand(attachmentCmd);
+            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
+            String businessStatus = WorkflowUtils.getBusinessStatus(task.getProcessInstanceId());
+            if (BusinessStatusEnum.DRAFT.getStatus().equals(businessStatus) || BusinessStatusEnum.BACK.getStatus().equals(businessStatus) || BusinessStatusEnum.CANCEL.getStatus().equals(businessStatus)) {
+                if (processHandler != null) {
+                    processHandler.handleProcess(processInstance.getBusinessKey(), businessStatus, true);
+                }
+            }
+            runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.WAITING.getStatus());
+            String key = processInstance.getProcessDefinitionKey() + "_" + task.getTaskDefinitionKey();
+            FlowTaskEventHandler taskHandler = flowEventStrategy.getTaskHandler(key);
+            if (taskHandler != null) {
+                taskHandler.handleTask(task, processInstance.getBusinessKey());
+            }
+            //鍔炵悊鎰忚
+            taskService.addComment(completeTaskBo.getTaskId(), task.getProcessInstanceId(), TaskStatusEnum.PASS.getStatus(), StringUtils.isBlank(completeTaskBo.getMessage()) ? "鍚屾剰" : completeTaskBo.getMessage());
+            //鍔炵悊浠诲姟
+            if (CollUtil.isNotEmpty(completeTaskBo.getVariables())) {
+                taskService.complete(completeTaskBo.getTaskId(), completeTaskBo.getVariables());
+            } else {
+                taskService.complete(completeTaskBo.getTaskId());
+            }
+            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId())
+                .processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+            if (pi == null) {
+                UpdateBusinessStatusCmd updateBusinessStatusCmd = new UpdateBusinessStatusCmd(task.getProcessInstanceId(), BusinessStatusEnum.FINISH.getStatus());
+                managementService.executeCommand(updateBusinessStatusCmd);
+                if (processHandler != null) {
+                    processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.FINISH.getStatus(), false);
+                }
+            } else {
+                List<Task> list = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list();
+                if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(completeTaskBo.getWfCopyList())) {
+                    TaskEntity newTask = WorkflowUtils.createNewTask(task);
+                    taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(),
+                        LoginHelper.getLoginUser().getNickname() + "銆愭妱閫併�戠粰" + String.join(",", StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserName)));
+                    taskService.complete(newTask.getId());
+                    List<Task> taskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
+                    WorkflowUtils.createCopyTask(taskList, StreamUtils.toList(completeTaskBo.getWfCopyList(), WfCopy::getUserId));
+                }
+                sendMessage(list, processInstance.getName(), completeTaskBo.getMessageType(), null);
+            }
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 鍙戦�佹秷鎭�
+     *
+     * @param list        浠诲姟
+     * @param name        娴佺▼鍚嶇О
+     * @param messageType 娑堟伅绫诲瀷
+     * @param message     娑堟伅鍐呭锛屼负绌哄垯鍙戦�侀粯璁ら厤缃殑娑堟伅鍐呭
+     */
+    @Async
+    public void sendMessage(List<Task> list, String name, List<String> messageType, String message) {
+        WorkflowUtils.sendMessage(list, name, messageType, message);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<TaskVo> getTaskWaitByPage(TaskBo taskBo) {
+        PageQuery pageQuery = new PageQuery();
+        pageQuery.setPageNum(taskBo.getPageNum());
+        pageQuery.setPageSize(taskBo.getPageSize());
+        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
+        List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
+        String userId = String.valueOf(LoginHelper.getUserId());
+        queryWrapper.eq("t.business_status_", BusinessStatusEnum.WAITING.getStatus());
+        queryWrapper.eq("t.tenant_id_", TenantHelper.getTenantId());
+        queryWrapper.and(w1 ->
+            w1.eq("t.assignee_", userId)
+                .or(w2 -> w2.isNull("t.assignee_")
+                    .and(w3 -> w3.eq("t.user_id_", userId).or().in("t.group_id_", StreamUtils.toList(roles, RoleDTO::getRoleId))))
+        );
+        if (StringUtils.isNotBlank(taskBo.getName())) {
+            queryWrapper.like("t.name_", taskBo.getName());
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
+            queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
+            queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
+        }
+        Page<TaskVo> page = actTaskMapper.getTaskWaitByPage(pageQuery.build(), queryWrapper);
+
+        List<TaskVo> taskList = page.getRecords();
+        for (TaskVo task : taskList) {
+            task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
+            task.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId()));
+            task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
+        }
+        return new TableDataInfo<>(taskList, page.getTotal());
+    }
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊緟鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<TaskVo> getAllTaskWaitByPage(TaskBo taskBo) {
+        TaskQuery query = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId());
+        if (StringUtils.isNotBlank(taskBo.getName())) {
+            query.taskNameLike("%" + taskBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
+            query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
+            query.processDefinitionKey(taskBo.getProcessDefinitionKey());
+        }
+        query.orderByTaskCreateTime().desc();
+        List<Task> taskList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize());
+        List<ProcessInstance> processInstanceList = null;
+        if (CollUtil.isNotEmpty(taskList)) {
+            Set<String> processInstanceIds = StreamUtils.toSet(taskList, Task::getProcessInstanceId);
+            processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIds).list();
+        }
+        List<TaskVo> list = new ArrayList<>();
+        for (Task task : taskList) {
+            TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class);
+            if (CollUtil.isNotEmpty(processInstanceList)) {
+                processInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> {
+                    taskVo.setBusinessStatus(e.getBusinessStatus());
+                    taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus()));
+                    taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey());
+                    taskVo.setProcessDefinitionName(e.getProcessDefinitionName());
+                });
+            }
+            taskVo.setAssignee(StringUtils.isNotBlank(task.getAssignee()) ? Long.valueOf(task.getAssignee()) : null);
+            taskVo.setParticipantVo(WorkflowUtils.getCurrentTaskParticipant(task.getId()));
+            taskVo.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
+            list.add(taskVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勫凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<TaskVo> getTaskFinishByPage(TaskBo taskBo) {
+        String userId = String.valueOf(LoginHelper.getUserId());
+        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).taskTenantId(TenantHelper.getTenantId()).finished().orderByHistoricTaskInstanceStartTime().desc();
+        if (StringUtils.isNotBlank(taskBo.getName())) {
+            query.taskNameLike("%" + taskBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
+            query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
+            query.processDefinitionKey(taskBo.getProcessDefinitionKey());
+        }
+        List<HistoricTaskInstance> taskInstanceList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize());
+        List<HistoricProcessInstance> historicProcessInstanceList = null;
+        if (CollUtil.isNotEmpty(taskInstanceList)) {
+            Set<String> processInstanceIds = StreamUtils.toSet(taskInstanceList, HistoricTaskInstance::getProcessInstanceId);
+            historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceIds(processInstanceIds).list();
+        }
+        List<TaskVo> list = new ArrayList<>();
+        for (HistoricTaskInstance task : taskInstanceList) {
+            TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class);
+            if (CollUtil.isNotEmpty(historicProcessInstanceList)) {
+                historicProcessInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> {
+                    taskVo.setBusinessStatus(e.getBusinessStatus());
+                    taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus()));
+                    taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey());
+                    taskVo.setProcessDefinitionName(e.getProcessDefinitionName());
+                });
+            }
+            taskVo.setAssignee(StringUtils.isNotBlank(task.getAssignee()) ? Long.valueOf(task.getAssignee()) : null);
+            list.add(taskVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鐨勬妱閫�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<TaskVo> getTaskCopyByPage(TaskBo taskBo) {
+        PageQuery pageQuery = new PageQuery();
+        pageQuery.setPageNum(taskBo.getPageNum());
+        pageQuery.setPageSize(taskBo.getPageSize());
+        QueryWrapper<TaskVo> queryWrapper = new QueryWrapper<>();
+        String userId = String.valueOf(LoginHelper.getUserId());
+        if (StringUtils.isNotBlank(taskBo.getName())) {
+            queryWrapper.like("t.name_", taskBo.getName());
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
+            queryWrapper.like("t.processDefinitionName", taskBo.getProcessDefinitionName());
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
+            queryWrapper.eq("t.processDefinitionKey", taskBo.getProcessDefinitionKey());
+        }
+        queryWrapper.eq("t.assignee_", userId);
+        Page<TaskVo> page = actTaskMapper.getTaskCopyByPage(pageQuery.build(), queryWrapper);
+
+        List<TaskVo> taskList = page.getRecords();
+        for (TaskVo task : taskList) {
+            task.setBusinessStatusName(BusinessStatusEnum.findByStatus(task.getBusinessStatus()));
+            task.setMultiInstance(WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey()) != null);
+        }
+        return new TableDataInfo<>(taskList, page.getTotal());
+    }
+
+    /**
+     * 鏌ヨ褰撳墠绉熸埛鎵�鏈夊凡鍔炰换鍔�
+     *
+     * @param taskBo 鍙傛暟
+     */
+    @Override
+    public TableDataInfo<TaskVo> getAllTaskFinishByPage(TaskBo taskBo) {
+        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().taskTenantId(TenantHelper.getTenantId()).finished().orderByHistoricTaskInstanceStartTime().desc();
+        if (StringUtils.isNotBlank(taskBo.getName())) {
+            query.taskNameLike("%" + taskBo.getName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionName())) {
+            query.processDefinitionNameLike("%" + taskBo.getProcessDefinitionName() + "%");
+        }
+        if (StringUtils.isNotBlank(taskBo.getProcessDefinitionKey())) {
+            query.processDefinitionKey(taskBo.getProcessDefinitionKey());
+        }
+        List<HistoricTaskInstance> taskInstanceList = query.listPage(taskBo.getPageNum(), taskBo.getPageSize());
+        List<HistoricProcessInstance> historicProcessInstanceList = null;
+        if (CollUtil.isNotEmpty(taskInstanceList)) {
+            Set<String> processInstanceIds = StreamUtils.toSet(taskInstanceList, HistoricTaskInstance::getProcessInstanceId);
+            historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceIds(processInstanceIds).list();
+        }
+        List<TaskVo> list = new ArrayList<>();
+        for (HistoricTaskInstance task : taskInstanceList) {
+            TaskVo taskVo = BeanUtil.toBean(task, TaskVo.class);
+            if (CollUtil.isNotEmpty(historicProcessInstanceList)) {
+                historicProcessInstanceList.stream().filter(e -> e.getId().equals(task.getProcessInstanceId())).findFirst().ifPresent(e -> {
+                    taskVo.setBusinessStatus(e.getBusinessStatus());
+                    taskVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(taskVo.getBusinessStatus()));
+                    taskVo.setProcessDefinitionKey(e.getProcessDefinitionKey());
+                    taskVo.setProcessDefinitionName(e.getProcessDefinitionName());
+                });
+            }
+            taskVo.setAssignee(Convert.toLong(task.getAssignee()));
+            list.add(taskVo);
+        }
+        long count = query.count();
+        return new TableDataInfo<>(list, count);
+    }
+
+    /**
+     * 濮旀淳浠诲姟
+     *
+     * @param delegateBo 鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean delegateTask(DelegateBo delegateBo) {
+        TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(delegateBo.getTaskId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        try {
+            TaskEntity newTask = WorkflowUtils.createNewTask(task);
+            taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.PENDING.getStatus(), "銆�" + LoginHelper.getLoginUser().getNickname() + "銆戝娲剧粰銆�" + delegateBo.getNickName() + "銆�");
+            //濮旀墭浠诲姟
+            taskService.delegateTask(delegateBo.getTaskId(), delegateBo.getUserId());
+            //鍔炵悊鐢熸垚鐨勪换鍔¤褰�
+            taskService.complete(newTask.getId());
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 缁堟浠诲姟
+     *
+     * @param terminationBo 鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean terminationTask(TerminationBo terminationBo) {
+        Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(terminationBo.getTaskId()).singleResult();
+
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(task.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+        if (ObjectUtil.isNotEmpty(historicProcessInstance) && BusinessStatusEnum.TERMINATION.getStatus().equals(historicProcessInstance.getBusinessStatus())) {
+            throw new ServiceException("璇ュ崟鎹凡缁堟锛�");
+        }
+        try {
+            if (StringUtils.isBlank(terminationBo.getComment())) {
+                terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "缁堟浜嗙敵璇�");
+            } else {
+                terminationBo.setComment(LoginHelper.getLoginUser().getNickname() + "缁堟浜嗙敵璇凤細" + terminationBo.getComment());
+            }
+            taskService.addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.TERMINATION.getStatus(), terminationBo.getComment());
+            List<Task> list = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list();
+            if (CollectionUtil.isNotEmpty(list)) {
+                List<Task> subTasks = StreamUtils.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
+                if (CollectionUtil.isNotEmpty(subTasks)) {
+                    subTasks.forEach(e -> taskService.deleteTask(e.getId()));
+                }
+                runtimeService.deleteProcessInstance(task.getProcessInstanceId(), StrUtil.EMPTY);
+            }
+            runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.TERMINATION.getStatus());
+            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(historicProcessInstance.getProcessDefinitionKey());
+            if (processHandler != null) {
+                processHandler.handleProcess(historicProcessInstance.getBusinessKey(), BusinessStatusEnum.TERMINATION.getStatus(), false);
+            }
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 杞姙浠诲姟
+     *
+     * @param transmitBo 鍙傛暟
+     */
+    @Override
+    public boolean transferTask(TransmitBo transmitBo) {
+        Task task = taskService.createTaskQuery().taskId(transmitBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId())).singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        try {
+            TaskEntity newTask = WorkflowUtils.createNewTask(task);
+            taskService.addComment(newTask.getId(), task.getProcessInstanceId(), TaskStatusEnum.TRANSFER.getStatus(), StringUtils.isNotBlank(transmitBo.getComment()) ? transmitBo.getComment() : LoginHelper.getUsername() + "杞姙浜嗕换鍔�");
+            taskService.complete(newTask.getId());
+            taskService.setAssignee(task.getId(), transmitBo.getUserId());
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 浼氱浠诲姟鍔犵
+     *
+     * @param addMultiBo 鍙傛暟
+     */
+    @Override
+    public boolean addMultiInstanceExecution(AddMultiBo addMultiBo) {
+        TaskQuery taskQuery = taskService.createTaskQuery();
+        taskQuery.taskId(addMultiBo.getTaskId());
+        taskQuery.taskTenantId(TenantHelper.getTenantId());
+        if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) {
+            taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));
+        }
+        Task task = taskQuery.singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        String taskDefinitionKey = task.getTaskDefinitionKey();
+        String processInstanceId = task.getProcessInstanceId();
+        String processDefinitionId = task.getProcessDefinitionId();
+
+        try {
+            MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey);
+            if (multiInstanceVo == null) {
+                throw new ServiceException("褰撳墠鐜妭涓嶆槸浼氱鑺傜偣");
+            }
+            if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) {
+                for (Long assignee : addMultiBo.getAssignees()) {
+                    runtimeService.addMultiInstanceExecution(taskDefinitionKey, processInstanceId, Collections.singletonMap(multiInstanceVo.getAssignee(), assignee));
+                }
+            } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) {
+                AddSequenceMultiInstanceCmd addSequenceMultiInstanceCmd = new AddSequenceMultiInstanceCmd(task.getExecutionId(), multiInstanceVo.getAssigneeList(), addMultiBo.getAssignees());
+                managementService.executeCommand(addSequenceMultiInstanceCmd);
+            }
+            List<String> assigneeNames = addMultiBo.getAssigneeNames();
+            String username = LoginHelper.getUsername();
+            TaskEntity newTask = WorkflowUtils.createNewTask(task);
+            taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN.getStatus(), username + "鍔犵銆�" + String.join(StringUtils.SEPARATOR, assigneeNames) + "銆�");
+            taskService.complete(newTask.getId());
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 浼氱浠诲姟鍑忕
+     *
+     * @param deleteMultiBo 鍙傛暟
+     */
+    @Override
+    public boolean deleteMultiInstanceExecution(DeleteMultiBo deleteMultiBo) {
+        TaskQuery taskQuery = taskService.createTaskQuery();
+        taskQuery.taskId(deleteMultiBo.getTaskId());
+        taskQuery.taskTenantId(TenantHelper.getTenantId());
+        if (!LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin()) {
+            taskQuery.taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));
+        }
+        Task task = taskQuery.singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        String taskDefinitionKey = task.getTaskDefinitionKey();
+        String processInstanceId = task.getProcessInstanceId();
+        String processDefinitionId = task.getProcessDefinitionId();
+        try {
+            MultiInstanceVo multiInstanceVo = WorkflowUtils.isMultiInstance(processDefinitionId, taskDefinitionKey);
+            if (multiInstanceVo == null) {
+                throw new ServiceException("褰撳墠鐜妭涓嶆槸浼氱鑺傜偣");
+            }
+            if (multiInstanceVo.getType() instanceof ParallelMultiInstanceBehavior) {
+                for (String executionId : deleteMultiBo.getExecutionIds()) {
+                    runtimeService.deleteMultiInstanceExecution(executionId, false);
+                }
+                for (String taskId : deleteMultiBo.getTaskIds()) {
+                    historyService.deleteHistoricTaskInstance(taskId);
+                }
+            } else if (multiInstanceVo.getType() instanceof SequentialMultiInstanceBehavior) {
+                DeleteSequenceMultiInstanceCmd deleteSequenceMultiInstanceCmd = new DeleteSequenceMultiInstanceCmd(task.getAssignee(), task.getExecutionId(), multiInstanceVo.getAssigneeList(), deleteMultiBo.getAssigneeIds());
+                managementService.executeCommand(deleteSequenceMultiInstanceCmd);
+            }
+            List<String> assigneeNames = deleteMultiBo.getAssigneeNames();
+            String username = LoginHelper.getUsername();
+            TaskEntity newTask = WorkflowUtils.createNewTask(task);
+            taskService.addComment(newTask.getId(), processInstanceId, TaskStatusEnum.SIGN_OFF.getStatus(), username + "鍑忕銆�" + String.join(StringUtils.SEPARATOR, assigneeNames) + "銆�");
+            taskService.complete(newTask.getId());
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    /**
+     * 椹冲洖瀹℃壒
+     *
+     * @param backProcessBo 鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String backProcess(BackProcessBo backProcessBo) {
+        Task task = taskService.createTaskQuery().taskId(backProcessBo.getTaskId()).taskTenantId(TenantHelper.getTenantId()).taskAssignee(String.valueOf(LoginHelper.getUserId())).singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException(FlowConstant.MESSAGE_CURRENT_TASK_IS_NULL);
+        }
+        if (task.isSuspended()) {
+            throw new ServiceException(FlowConstant.MESSAGE_SUSPENDED);
+        }
+        try {
+            String processInstanceId = task.getProcessInstanceId();
+            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
+            //鑾峰彇骞惰缃戝叧鎵ц鍚庝繚鐣欑殑鎵ц瀹炰緥鏁版嵁
+            ExecutionChildByExecutionIdCmd childByExecutionIdCmd = new ExecutionChildByExecutionIdCmd(task.getExecutionId());
+            List<ExecutionEntity> executionEntities = managementService.executeCommand(childByExecutionIdCmd);
+            //鏍¢獙鍗曟嵁
+            if (BusinessStatusEnum.BACK.getStatus().equals(processInstance.getBusinessStatus())) {
+                throw new ServiceException("璇ュ崟鎹凡閫�鍥烇紒");
+            }
+            //鍒ゆ柇鏄惁鏈夊涓换鍔�
+            List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).list();
+            //鐢宠浜鸿妭鐐�
+            HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).finished().orderByHistoricTaskInstanceEndTime().asc().list().get(0);
+            String backTaskDefinitionKey = historicTaskInstance.getTaskDefinitionKey();
+            taskService.addComment(task.getId(), processInstanceId, TaskStatusEnum.BACK.getStatus(), StringUtils.isNotBlank(backProcessBo.getMessage()) ? backProcessBo.getMessage() : "閫�鍥�");
+            if (taskList.size() > 1) {
+                //褰撳墠澶氫釜浠诲姟椹冲洖鍒板崟涓妭鐐�
+                runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdsToSingleActivityId(taskList.stream().map(Task::getTaskDefinitionKey).distinct().collect(Collectors.toList()), backTaskDefinitionKey).changeState();
+            } else {
+                //褰撳墠鍗曚釜鑺傜偣椹冲洖鍗曚釜鑺傜偣
+                runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId).moveActivityIdTo(task.getTaskDefinitionKey(), backTaskDefinitionKey).changeState();
+            }
+            List<Task> list = taskService.createTaskQuery().processInstanceId(processInstanceId).taskTenantId(TenantHelper.getTenantId()).list();
+            for (Task t : list) {
+                taskService.setAssignee(t.getId(), historicTaskInstance.getAssignee());
+            }
+            //鍙戦�佹秷鎭�
+            String message = "鎮ㄧ殑銆�" + processInstance.getName() + "銆戝崟鎹凡缁忚椹冲洖锛岃鎮ㄦ敞鎰忔煡鏀躲��";
+            sendMessage(list, processInstance.getName(), backProcessBo.getMessageType(), message);
+            //鍒犻櫎娴佺▼瀹炰緥鍨冨溇鏁版嵁
+            for (ExecutionEntity executionEntity : executionEntities) {
+                DeleteExecutionCmd deleteExecutionCmd = new DeleteExecutionCmd(executionEntity.getId());
+                managementService.executeCommand(deleteExecutionCmd);
+            }
+            runtimeService.updateBusinessStatus(processInstanceId, BusinessStatusEnum.BACK.getStatus());
+            FlowProcessEventHandler processHandler = flowEventStrategy.getProcessHandler(processInstance.getProcessDefinitionKey());
+            if (processHandler != null) {
+                processHandler.handleProcess(processInstance.getBusinessKey(), BusinessStatusEnum.BACK.getStatus(), false);
+            }
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+        return task.getProcessInstanceId();
+    }
+
+    /**
+     * 淇敼浠诲姟鍔炵悊浜�
+     *
+     * @param taskIds 浠诲姟id
+     * @param userId  鍔炵悊浜篿d
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateAssignee(String[] taskIds, String userId) {
+        try {
+            List<Task> list = taskService.createTaskQuery().taskIds(Arrays.asList(taskIds)).taskTenantId(TenantHelper.getTenantId()).list();
+            for (Task task : list) {
+                taskService.setAssignee(task.getId(), userId);
+            }
+        } catch (Exception e) {
+            throw new ServiceException("淇敼澶辫触锛�" + e.getMessage());
+        }
+        return true;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
new file mode 100644
index 0000000..7b55ee5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
@@ -0,0 +1,124 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.workflow.domain.TestLeave;
+import org.dromara.workflow.domain.bo.TestLeaveBo;
+import org.dromara.workflow.domain.vo.TestLeaveVo;
+import org.dromara.workflow.mapper.TestLeaveMapper;
+import org.dromara.workflow.service.IActProcessInstanceService;
+import org.dromara.workflow.service.ITestLeaveService;
+import org.dromara.workflow.utils.WorkflowUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璇峰亣Service涓氬姟灞傚鐞�
+ *
+ * @author may
+ * @date 2023-07-21
+ */
+@RequiredArgsConstructor
+@Service
+public class TestLeaveServiceImpl implements ITestLeaveService {
+
+    private final TestLeaveMapper baseMapper;
+    private final IActProcessInstanceService iActProcessInstanceService;
+
+    /**
+     * 鏌ヨ璇峰亣
+     */
+    @Override
+    public TestLeaveVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 鏌ヨ璇峰亣鍒楄〃
+     */
+    @Override
+    public TableDataInfo<TestLeaveVo> queryPageList(TestLeaveBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
+        Page<TestLeaveVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        TableDataInfo<TestLeaveVo> build = TableDataInfo.build(result);
+        List<TestLeaveVo> rows = build.getRows();
+        if (CollUtil.isNotEmpty(rows)) {
+            List<String> ids = StreamUtils.toList(rows, e -> String.valueOf(e.getId()));
+            WorkflowUtils.setProcessInstanceListVo(rows, ids, "id");
+        }
+        return build;
+    }
+
+    /**
+     * 鏌ヨ璇峰亣鍒楄〃
+     */
+    @Override
+    public List<TestLeaveVo> queryList(TestLeaveBo bo) {
+        LambdaQueryWrapper<TestLeave> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<TestLeave> buildQueryWrapper(TestLeaveBo bo) {
+        LambdaQueryWrapper<TestLeave> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getLeaveType()), TestLeave::getLeaveType, bo.getLeaveType());
+        lqw.ge(bo.getStartLeaveDays() != null,TestLeave::getLeaveDays, bo.getStartLeaveDays());
+        lqw.le(bo.getEndLeaveDays() != null,TestLeave::getLeaveDays, bo.getEndLeaveDays());
+        lqw.orderByDesc(BaseEntity::getCreateTime);
+        return lqw;
+    }
+
+    /**
+     * 鏂板璇峰亣
+     */
+    @Override
+    public TestLeave insertByBo(TestLeaveBo bo) {
+        TestLeave add = MapstructUtils.convert(bo, TestLeave.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return add;
+    }
+
+    /**
+     * 淇敼璇峰亣
+     */
+    @Override
+    public TestLeave updateByBo(TestLeaveBo bo) {
+        TestLeave update = MapstructUtils.convert(bo, TestLeave.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0 ? update : null;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(TestLeave entity) {
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎璇峰亣
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteWithValidByIds(Collection<Long> ids) {
+        List<String> idList = StreamUtils.toList(ids, String::valueOf);
+        iActProcessInstanceService.deleteRuntimeProcessAndHisInstByBusinessKeys(idList);
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
new file mode 100644
index 0000000..620403a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
@@ -0,0 +1,128 @@
+package org.dromara.workflow.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.workflow.domain.WfCategory;
+import org.dromara.workflow.domain.bo.WfCategoryBo;
+import org.dromara.workflow.domain.vo.WfCategoryVo;
+import org.dromara.workflow.mapper.WfCategoryMapper;
+import org.dromara.workflow.service.IWfCategoryService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被Service涓氬姟灞傚鐞�
+ *
+ * @author may
+ * @date 2023-06-28
+ */
+@RequiredArgsConstructor
+@Service
+public class WfCategoryServiceImpl implements IWfCategoryService {
+
+    private final WfCategoryMapper baseMapper;
+
+    private final RepositoryService repositoryService;
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被
+     */
+    @Override
+    public WfCategoryVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+     */
+    @Override
+    public List<WfCategoryVo> queryList(WfCategoryBo bo) {
+        LambdaQueryWrapper<WfCategory> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<WfCategory> buildQueryWrapper(WfCategoryBo bo) {
+        LambdaQueryWrapper<WfCategory> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getCategoryName()), WfCategory::getCategoryName, bo.getCategoryName());
+        lqw.eq(StringUtils.isNotBlank(bo.getCategoryCode()), WfCategory::getCategoryCode, bo.getCategoryCode());
+        return lqw;
+    }
+
+    /**
+     * 鏂板娴佺▼鍒嗙被
+     */
+    @Override
+    public Boolean insertByBo(WfCategoryBo bo) {
+        WfCategory add = MapstructUtils.convert(bo, WfCategory.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 淇敼娴佺▼鍒嗙被
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateByBo(WfCategoryBo bo) {
+        WfCategory update = MapstructUtils.convert(bo, WfCategory.class);
+        validEntityBeforeSave(update);
+        WfCategoryVo wfCategoryVo = baseMapper.selectVoById(bo.getId());
+        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery().processDefinitionCategory(wfCategoryVo.getCategoryCode()).list();
+        for (ProcessDefinition processDefinition : processDefinitionList) {
+            repositoryService.setProcessDefinitionCategory(processDefinition.getId(), bo.getCategoryCode());
+        }
+        List<Deployment> deploymentList = repositoryService.createDeploymentQuery().deploymentCategory(wfCategoryVo.getCategoryCode()).list();
+        for (Deployment deployment : deploymentList) {
+            repositoryService.setDeploymentCategory(deployment.getId(), bo.getCategoryCode());
+        }
+        List<Model> modelList = repositoryService.createModelQuery().modelCategory(wfCategoryVo.getCategoryCode()).list();
+        for (Model model : modelList) {
+            model.setCategory(bo.getCategoryCode());
+            repositoryService.saveModel(model);
+        }
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 淇濆瓨鍓嶇殑鏁版嵁鏍¢獙
+     */
+    private void validEntityBeforeSave(WfCategory entity) {
+        //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎娴佺▼鍒嗙被
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    /**
+     * 鎸夌収绫诲埆缂栫爜鏌ヨ
+     *
+     * @param categoryCode 鍒嗙被姣斿悧
+     */
+    @Override
+    public WfCategory queryByCategoryCode(String categoryCode) {
+        return baseMapper.selectOne(new LambdaQueryWrapper<WfCategory>().eq(WfCategory::getCategoryCode, categoryCode));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java
new file mode 100644
index 0000000..e30e553
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowUserServiceImpl.java
@@ -0,0 +1,216 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.system.mapper.SysUserRoleMapper;
+import org.dromara.workflow.domain.bo.SysUserMultiBo;
+import org.dromara.workflow.domain.vo.MultiInstanceVo;
+import org.dromara.workflow.domain.vo.TaskVo;
+import org.dromara.workflow.service.IWorkflowUserService;
+import org.dromara.workflow.utils.WorkflowUtils;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.flowable.task.api.Task;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 宸ヤ綔娴佺敤鎴烽�変汉绠$悊 涓氬姟澶勭悊灞�
+ *
+ * @author may
+ */
+@RequiredArgsConstructor
+@Service
+public class WorkflowUserServiceImpl implements IWorkflowUserService {
+
+    private final SysUserMapper sysUserMapper;
+    private final SysUserRoleMapper sysUserRoleMapper;
+    private final TaskService taskService;
+    private final RuntimeService runtimeService;
+
+    /**
+     * 鍒嗛〉鏌ヨ宸ヤ綔娴侀�夋嫨鍔犵浜哄憳
+     *
+     * @param sysUserMultiBo 鍙傛暟
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public TableDataInfo<SysUserVo> getWorkflowAddMultiInstanceByPage(SysUserMultiBo sysUserMultiBo) {
+        Task task = taskService.createTaskQuery().taskId(sysUserMultiBo.getTaskId()).singleResult();
+        if (task == null) {
+            throw new ServiceException("浠诲姟涓嶅瓨鍦�");
+        }
+        MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
+        if (multiInstance == null) {
+            return TableDataInfo.build();
+        }
+        LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
+        //妫�绱㈡潯浠�
+        queryWrapper.eq(StringUtils.isNotEmpty(sysUserMultiBo.getDeptId()), SysUser::getDeptId, sysUserMultiBo.getDeptId());
+        queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode());
+        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
+            List<Long> assigneeList = (List<Long>) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList());
+            queryWrapper.notIn(CollectionUtil.isNotEmpty(assigneeList), SysUser::getUserId, assigneeList);
+        } else {
+            List<Task> list = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
+            List<Long> userIds = StreamUtils.toList(list, e -> Long.valueOf(e.getAssignee()));
+            queryWrapper.notIn(CollectionUtil.isNotEmpty(userIds), SysUser::getUserId, userIds);
+        }
+        queryWrapper.like(StringUtils.isNotEmpty(sysUserMultiBo.getUserName()), SysUser::getUserName, sysUserMultiBo.getUserName());
+        queryWrapper.like(StringUtils.isNotEmpty(sysUserMultiBo.getNickName()), SysUser::getNickName, sysUserMultiBo.getNickName());
+        Page<SysUser> page = new Page<>(sysUserMultiBo.getPageNum(), sysUserMultiBo.getPageSize());
+        Page<SysUserVo> userPage = sysUserMapper.selectVoPage(page, queryWrapper);
+        return TableDataInfo.build(recordPage(userPage));
+    }
+
+    /**
+     * 鏌ヨ宸ヤ綔娴侀�夋嫨鍑忕浜哄憳
+     *
+     * @param taskId 浠诲姟id 浠诲姟id
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<TaskVo> getWorkflowDeleteMultiInstanceList(String taskId) {
+        Task task = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(taskId).singleResult();
+        List<Task> taskList = taskService.createTaskQuery().taskTenantId(TenantHelper.getTenantId()).processInstanceId(task.getProcessInstanceId()).list();
+        MultiInstanceVo multiInstance = WorkflowUtils.isMultiInstance(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
+        List<TaskVo> taskListVo = new ArrayList<>();
+        if (multiInstance == null) {
+            return Collections.emptyList();
+        }
+        List<Long> assigneeList = new ArrayList<>();
+        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
+            List<Object> variable = (List<Object>) runtimeService.getVariable(task.getExecutionId(), multiInstance.getAssigneeList());
+            for (Object o : variable) {
+                assigneeList.add(Long.valueOf(o.toString()));
+            }
+        }
+
+        if (multiInstance.getType() instanceof SequentialMultiInstanceBehavior) {
+            List<Long> userIds = StreamUtils.filter(assigneeList, e -> !String.valueOf(e).equals(task.getAssignee()));
+            List<SysUserVo> sysUsers = null;
+            if (CollectionUtil.isNotEmpty(userIds)) {
+                sysUsers = sysUserMapper.selectVoBatchIds(userIds);
+            }
+            for (Long userId : userIds) {
+                TaskVo taskVo = new TaskVo();
+                taskVo.setId("涓茶浼氱");
+                taskVo.setExecutionId("涓茶浼氱");
+                taskVo.setProcessInstanceId(task.getProcessInstanceId());
+                taskVo.setName(task.getName());
+                taskVo.setAssignee(userId);
+                if (CollectionUtil.isNotEmpty(sysUsers)) {
+                    sysUsers.stream().filter(u -> u.getUserId().toString().equals(userId.toString())).findFirst().ifPresent(u -> taskVo.setAssigneeName(u.getNickName()));
+                }
+                taskListVo.add(taskVo);
+            }
+            return taskListVo;
+        } else if (multiInstance.getType() instanceof ParallelMultiInstanceBehavior) {
+            List<Task> tasks = StreamUtils.filter(taskList, e -> StringUtils.isBlank(e.getParentTaskId()) && !e.getExecutionId().equals(task.getExecutionId()) && e.getTaskDefinitionKey().equals(task.getTaskDefinitionKey()));
+            if (CollectionUtil.isNotEmpty(tasks)) {
+                List<Long> userIds = StreamUtils.toList(tasks, e -> Long.valueOf(e.getAssignee()));
+                List<SysUserVo> sysUsers = null;
+                if (CollectionUtil.isNotEmpty(userIds)) {
+                    sysUsers = sysUserMapper.selectVoBatchIds(userIds);
+                }
+                for (Task t : tasks) {
+                    TaskVo taskVo = new TaskVo();
+                    taskVo.setId(t.getId());
+                    taskVo.setExecutionId(t.getExecutionId());
+                    taskVo.setProcessInstanceId(t.getProcessInstanceId());
+                    taskVo.setName(t.getName());
+                    taskVo.setAssignee(Long.valueOf(t.getAssignee()));
+                    if (CollectionUtil.isNotEmpty(sysUsers)) {
+                        sysUsers.stream().filter(u -> u.getUserId().toString().equals(t.getAssignee())).findFirst().ifPresent(e -> taskVo.setAssigneeName(e.getNickName()));
+                    }
+                    taskListVo.add(taskVo);
+                }
+                return taskListVo;
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * 缈昏瘧閮ㄩ棬
+     *
+     * @param page 鐢ㄦ埛鍒嗛〉鏁版嵁
+     */
+    private Page<SysUserVo> recordPage(Page<SysUserVo> page) {
+        List<SysUserVo> records = page.getRecords();
+        if (CollUtil.isEmpty(records)) {
+            return page;
+        }
+        List<Long> collectDeptId = StreamUtils.toList(records, SysUserVo::getDeptId);
+        if (CollUtil.isEmpty(collectDeptId)) {
+            return page;
+        }
+        page.setRecords(records);
+        return page;
+    }
+
+    /**
+     * 鎸夌収鐢ㄦ埛id鏌ヨ鐢ㄦ埛
+     *
+     * @param userIds 鐢ㄦ埛id
+     */
+    @Override
+    public List<SysUserVo> getUserListByIds(List<Long> userIds) {
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
+        // 妫�绱㈡潯浠�
+        queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode());
+        queryWrapper.in(SysUser::getUserId, userIds);
+        return sysUserMapper.selectVoList(queryWrapper);
+    }
+
+    /**
+     * 鎸夌収瑙掕壊id鏌ヨ鍏宠仈鐢ㄦ埛id
+     *
+     * @param roleIds 瑙掕壊id
+     */
+    @Override
+    public List<SysUserRole> getUserRoleListByRoleIds(List<Long> roleIds) {
+        return sysUserRoleMapper.selectList(new LambdaQueryWrapper<SysUserRole>().in(SysUserRole::getRoleId, roleIds));
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鐢ㄦ埛
+     *
+     * @param sysUserBo 鍙傛暟
+     * @param pageQuery 鍒嗛〉
+     */
+    @Override
+    public TableDataInfo<SysUserVo> getUserListByPage(SysUserBo sysUserBo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(sysUserBo.getDeptId() != null, SysUser::getDeptId, sysUserBo.getDeptId());
+        queryWrapper.eq(SysUser::getStatus, UserStatus.OK.getCode());
+        queryWrapper.like(StringUtils.isNotEmpty(sysUserBo.getUserName()), SysUser::getUserName, sysUserBo.getUserName());
+        queryWrapper.like(StringUtils.isNotEmpty(sysUserBo.getNickName()), SysUser::getNickName, sysUserBo.getNickName());
+        Page<SysUserVo> userPage = sysUserMapper.selectVoPage(pageQuery.build(), queryWrapper);
+        return TableDataInfo.build(recordPage(userPage));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java
new file mode 100644
index 0000000..18969b1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/ModelUtils.java
@@ -0,0 +1,220 @@
+package org.dromara.workflow.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.workflow.domain.vo.MultiInstanceVo;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.*;
+import org.flowable.bpmn.model.Process;
+import org.flowable.editor.language.json.converter.BpmnJsonConverter;
+import org.flowable.engine.impl.util.ProcessDefinitionUtil;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.ServerException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 妯″瀷宸ュ叿
+ *
+ * @author may
+ */
+public class ModelUtils {
+    public ModelUtils() {
+    }
+
+    public static BpmnModel xmlToBpmnModel(String xml) throws IOException {
+        if (xml == null) {
+            throw new ServerException("xml涓嶈兘涓虹┖");
+        }
+        try {
+            InputStream inputStream = new ByteArrayInputStream(StrUtil.utf8Bytes(xml));
+            XMLInputFactory factory = XMLInputFactory.newInstance();
+            XMLStreamReader reader = factory.createXMLStreamReader(inputStream);
+            return new BpmnXMLConverter().convertToBpmnModel(reader);
+        } catch (XMLStreamException e) {
+            throw new ServerException(e.getMessage());
+        }
+    }
+
+    /**
+     * bpmnModel杞负xml
+     *
+     * @param jsonBytes json
+     */
+    public static byte[] bpmnJsonToXmlBytes(byte[] jsonBytes) throws IOException {
+        if (jsonBytes == null) {
+            return new byte[0];
+        }
+        // 1. json瀛楄妭鐮佽浆鎴� BpmnModel 瀵硅薄
+        ObjectMapper objectMapper = JsonUtils.getObjectMapper();
+        JsonNode jsonNode = objectMapper.readTree(jsonBytes);
+        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
+
+        if (bpmnModel.getProcesses().isEmpty()) {
+            return new byte[0];
+        }
+        // 2.灏哹pmnModel杞负xml
+        return new BpmnXMLConverter().convertToXML(bpmnModel);
+    }
+
+    /**
+     * xml杞负bpmnModel
+     *
+     * @param xmlBytes xml
+     */
+    public static BpmnModel xmlToBpmnModel(byte[] xmlBytes) throws XMLStreamException {
+        ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(xmlBytes);
+        XMLInputFactory xif = XMLInputFactory.newInstance();
+        XMLStreamReader xtr = xif.createXMLStreamReader(byteArrayInputStream);
+        return new BpmnXMLConverter().convertToBpmnModel(xtr);
+    }
+
+    /**
+     * 鏍¢獙妯″瀷
+     *
+     * @param bpmnModel bpmn妯″瀷
+     */
+    public static void checkBpmnModel(BpmnModel bpmnModel) throws ServerException {
+        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
+
+        checkBpmnNode(flowElements, false);
+
+        List<SubProcess> subProcessList = flowElements.stream().filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList());
+        if (!CollUtil.isEmpty(subProcessList)) {
+            for (SubProcess subProcess : subProcessList) {
+                Collection<FlowElement> subProcessFlowElements = subProcess.getFlowElements();
+                checkBpmnNode(subProcessFlowElements, true);
+            }
+        }
+        List<MultiInstanceVo> multiInstanceVoList = new ArrayList<>();
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof UserTask && ObjectUtil.isNotEmpty(((UserTask) flowElement).getLoopCharacteristics()) && StringUtils.isNotBlank(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem())) {
+                MultiInstanceVo multiInstanceVo = new MultiInstanceVo();
+                multiInstanceVo.setAssigneeList(((UserTask) flowElement).getLoopCharacteristics().getInputDataItem());
+                multiInstanceVoList.add(multiInstanceVo);
+            }
+        }
+
+        if (CollectionUtil.isNotEmpty(multiInstanceVoList) && multiInstanceVoList.size() > 1) {
+            Map<String, List<MultiInstanceVo>> assigneeListGroup = StreamUtils.groupByKey(multiInstanceVoList, MultiInstanceVo::getAssigneeList);
+            for (Map.Entry<String, List<MultiInstanceVo>> entry : assigneeListGroup.entrySet()) {
+                List<MultiInstanceVo> value = entry.getValue();
+                if (CollectionUtil.isNotEmpty(value) && value.size() > 1) {
+                    String key = entry.getKey();
+                    throw new ServerException("浼氱浜哄憳闆嗗悎銆�" + key + "銆戦噸澶�,璇烽噸鏂拌缃泦鍚圞EY");
+                }
+            }
+        }
+    }
+
+    /**
+     * 鏍¢獙bpmn鑺傜偣鏄惁鍚堟硶
+     *
+     * @param flowElements 鑺傜偣闆嗗悎
+     * @param subtask      鏄惁瀛愭祦绋�
+     */
+    private static void checkBpmnNode(Collection<FlowElement> flowElements, boolean subtask) throws ServerException {
+
+        if (CollUtil.isEmpty(flowElements)) {
+            throw new ServerException(subtask ? "瀛愭祦绋嬪繀椤诲瓨鍦ㄨ妭鐐�" : "蹇呴』瀛樺湪鑺傜偣锛�");
+        }
+
+        List<StartEvent> startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList());
+        if (CollUtil.isEmpty(startEventList)) {
+            throw new ServerException(subtask ? "瀛愭祦绋嬪繀椤诲瓨鍦ㄥ紑濮嬭妭鐐�" : "蹇呴』瀛樺湪寮�濮嬭妭鐐癸紒");
+        }
+
+        if (startEventList.size() > 1) {
+            throw new ServerException(subtask ? "瀛愭祦绋嬪彧鑳藉瓨鍦ㄤ竴涓紑濮嬭妭鐐�" : "鍙兘瀛樺湪涓�涓紑濮嬭妭鐐癸紒");
+        }
+
+        StartEvent startEvent = startEventList.get(0);
+        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
+        if (CollUtil.isEmpty(outgoingFlows)) {
+            throw new ServerException(subtask ? "瀛愭祦绋嬫祦绋嬭妭鐐逛负绌猴紝璇疯嚦灏戣璁′竴鏉′富绾挎祦绋嬶紒" : "娴佺▼鑺傜偣涓虹┖锛岃鑷冲皯璁捐涓�鏉′富绾挎祦绋嬶紒");
+        }
+
+        FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
+        if (!(targetFlowElement instanceof UserTask) && !subtask) {
+            throw new ServerException("寮�濮嬭妭鐐瑰悗绗竴涓妭鐐瑰繀椤绘槸鐢ㄦ埛浠诲姟锛�");
+        }
+
+        List<EndEvent> endEventList = flowElements.stream().filter(EndEvent.class::isInstance).map(EndEvent.class::cast).collect(Collectors.toList());
+        if (CollUtil.isEmpty(endEventList)) {
+            throw new ServerException(subtask ? "瀛愭祦绋嬪繀椤诲瓨鍦ㄧ粨鏉熻妭鐐癸紒" : "蹇呴』瀛樺湪缁撴潫鑺傜偣锛�");
+        }
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍏ㄩ儴鑺傜偣
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    public static List<FlowElement> getFlowElements(String processDefinitionId) {
+        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId);
+        List<FlowElement> list = new ArrayList<>();
+        List<Process> processes = bpmnModel.getProcesses();
+        Collection<FlowElement> flowElements = processes.get(0).getFlowElements();
+        buildFlowElements(flowElements, list);
+        return list;
+    }
+
+    /**
+     * 閫掑綊鑾峰彇鎵�鏈夎妭鐐�
+     *
+     * @param flowElements 鑺傜偣淇℃伅
+     * @param list         闆嗗悎
+     */
+    private static void buildFlowElements(Collection<FlowElement> flowElements, List<FlowElement> list) {
+        for (FlowElement flowElement : flowElements) {
+            list.add(flowElement);
+            if (flowElement instanceof SubProcess) {
+                Collection<FlowElement> subFlowElements = ((SubProcess) flowElement).getFlowElements();
+                buildFlowElements(subFlowElements, list);
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇鍏ㄩ儴鎵╁睍淇℃伅
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     */
+    public Map<String, List<ExtensionElement>> getExtensionElements(String processDefinitionId) {
+        Map<String, List<ExtensionElement>> map = new HashMap<>();
+        List<FlowElement> flowElements = getFlowElements(processDefinitionId);
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof UserTask && CollUtil.isNotEmpty(flowElement.getExtensionElements())) {
+                map.putAll(flowElement.getExtensionElements());
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 鑾峰彇鏌愪釜鑺傜偣鐨勬墿灞曚俊鎭�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @param flowElementId       鑺傜偣id
+     */
+    public Map<String, List<ExtensionElement>> getExtensionElement(String processDefinitionId, String flowElementId) {
+        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId);
+        Process process = bpmnModel.getMainProcess();
+        FlowElement flowElement = process.getFlowElement(flowElementId);
+        return flowElement.getExtensionElements();
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java
new file mode 100644
index 0000000..711d793
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java
@@ -0,0 +1,341 @@
+package org.dromara.workflow.utils;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.mail.utils.MailUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import org.dromara.system.domain.SysUserRole;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.BusinessStatusEnum;
+import org.dromara.workflow.common.enums.MessageTypeEnum;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.ActHiProcinst;
+import org.dromara.workflow.domain.ActHiTaskinst;
+import org.dromara.workflow.domain.vo.MultiInstanceVo;
+import org.dromara.workflow.domain.vo.ParticipantVo;
+import org.dromara.workflow.domain.vo.ProcessInstanceVo;
+import org.dromara.workflow.flowable.cmd.UpdateHiTaskInstCmd;
+import org.dromara.workflow.mapper.ActHiTaskinstMapper;
+import org.dromara.workflow.service.IActHiProcinstService;
+import org.dromara.workflow.service.IWorkflowUserService;
+import org.dromara.workflow.service.impl.WorkflowUserServiceImpl;
+import org.flowable.bpmn.model.*;
+import org.flowable.common.engine.api.delegate.Expression;
+import org.flowable.engine.ProcessEngine;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.flowable.identitylink.api.history.HistoricIdentityLink;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.flowable.task.service.impl.persistence.entity.TaskEntity;
+
+import java.util.*;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 宸ヤ綔娴佸伐鍏�
+ *
+ * @author may
+ */
+public class WorkflowUtils {
+
+    public WorkflowUtils() {
+    }
+
+    private static final ProcessEngine PROCESS_ENGINE = SpringUtils.getBean(ProcessEngine.class);
+    private static final IWorkflowUserService I_WORKFLOW_USER_SERVICE = SpringUtils.getBean(WorkflowUserServiceImpl.class);
+    private static final IActHiProcinstService I_ACT_HI_PROCINST_SERVICE = SpringUtils.getBean(IActHiProcinstService.class);
+    private static final ActHiTaskinstMapper ACT_HI_TASKINST_MAPPER = SpringUtils.getBean(ActHiTaskinstMapper.class);
+
+    /**
+     * 鍒涘缓涓�涓柊浠诲姟
+     *
+     * @param currentTask 鍙傛暟
+     */
+    public static TaskEntity createNewTask(Task currentTask) {
+        TaskEntity task = null;
+        if (ObjectUtil.isNotEmpty(currentTask)) {
+            task = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
+            task.setCategory(currentTask.getCategory());
+            task.setDescription(currentTask.getDescription());
+            task.setTenantId(currentTask.getTenantId());
+            task.setAssignee(currentTask.getAssignee());
+            task.setName(currentTask.getName());
+            task.setProcessDefinitionId(currentTask.getProcessDefinitionId());
+            task.setProcessInstanceId(currentTask.getProcessInstanceId());
+            task.setTaskDefinitionKey(currentTask.getTaskDefinitionKey());
+            task.setPriority(currentTask.getPriority());
+            task.setCreateTime(new Date());
+            task.setTenantId(TenantHelper.getTenantId());
+            PROCESS_ENGINE.getTaskService().saveTask(task);
+        }
+        if (ObjectUtil.isNotNull(task)) {
+            UpdateHiTaskInstCmd updateHiTaskInstCmd = new UpdateHiTaskInstCmd(Collections.singletonList(task.getId()), task.getProcessDefinitionId(), task.getProcessInstanceId());
+            PROCESS_ENGINE.getManagementService().executeCommand(updateHiTaskInstCmd);
+        }
+        return task;
+    }
+
+    /**
+     * 鎶勯�佷换鍔�
+     *
+     * @param parentTaskList 鐖剁骇浠诲姟
+     * @param userIds        浜哄憳id
+     */
+    public static void createCopyTask(List<Task> parentTaskList, List<Long> userIds) {
+        List<Task> list = new ArrayList<>();
+        for (Task parentTask : parentTaskList) {
+            for (Long userId : userIds) {
+                TaskEntity newTask = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
+                newTask.setParentTaskId(parentTask.getId());
+                newTask.setAssignee(userId.toString());
+                newTask.setName("銆愭妱閫併��-" + parentTask.getName());
+                newTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
+                newTask.setProcessInstanceId(parentTask.getProcessInstanceId());
+                newTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
+                newTask.setTenantId(TenantHelper.getTenantId());
+                list.add(newTask);
+            }
+        }
+        PROCESS_ENGINE.getTaskService().bulkSaveTasks(list);
+        if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(parentTaskList)) {
+            String processInstanceId = parentTaskList.get(0).getProcessInstanceId();
+            String processDefinitionId = parentTaskList.get(0).getProcessDefinitionId();
+            List<String> taskIds = StreamUtils.toList(list, Task::getId);
+            ActHiTaskinst actHiTaskinst = new ActHiTaskinst();
+            actHiTaskinst.setProcDefId(processDefinitionId);
+            actHiTaskinst.setProcInstId(processInstanceId);
+            actHiTaskinst.setScopeType(TaskStatusEnum.COPY.getStatus());
+            actHiTaskinst.setTenantId(TenantHelper.getTenantId());
+            LambdaUpdateWrapper<ActHiTaskinst> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.in(ActHiTaskinst::getId, taskIds);
+            ACT_HI_TASKINST_MAPPER.update(actHiTaskinst, updateWrapper);
+            for (Task task : list) {
+                PROCESS_ENGINE.getTaskService().addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), StrUtil.EMPTY);
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇褰撳墠浠诲姟鍙備笌鑰�
+     *
+     * @param taskId 浠诲姟id
+     */
+    public static ParticipantVo getCurrentTaskParticipant(String taskId) {
+        ParticipantVo participantVo = new ParticipantVo();
+        List<HistoricIdentityLink> linksForTask = PROCESS_ENGINE.getHistoryService().getHistoricIdentityLinksForTask(taskId);
+        Task task = PROCESS_ENGINE.getTaskService().createTaskQuery().taskTenantId(TenantHelper.getTenantId()).taskId(taskId).singleResult();
+        if (task != null && CollUtil.isNotEmpty(linksForTask)) {
+            List<HistoricIdentityLink> groupList = StreamUtils.filter(linksForTask, e -> StringUtils.isNotBlank(e.getGroupId()));
+            if (CollUtil.isNotEmpty(groupList)) {
+                List<Long> groupIds = StreamUtils.toList(groupList, e -> Long.valueOf(e.getGroupId()));
+                List<SysUserRole> sysUserRoles = I_WORKFLOW_USER_SERVICE.getUserRoleListByRoleIds(groupIds);
+                if (CollUtil.isNotEmpty(sysUserRoles)) {
+                    participantVo.setGroupIds(groupIds);
+                    List<Long> userIdList = StreamUtils.toList(sysUserRoles, SysUserRole::getUserId);
+                    List<SysUserVo> sysUsers = I_WORKFLOW_USER_SERVICE.getUserListByIds(userIdList);
+                    if (CollUtil.isNotEmpty(sysUsers)) {
+                        List<Long> userIds = StreamUtils.toList(sysUsers, SysUserVo::getUserId);
+                        List<String> nickNames = StreamUtils.toList(sysUsers, SysUserVo::getNickName);
+                        participantVo.setCandidate(userIds);
+                        participantVo.setCandidateName(nickNames);
+                        participantVo.setClaim(!StringUtils.isBlank(task.getAssignee()));
+                    }
+                }
+            } else {
+                List<HistoricIdentityLink> candidateList = StreamUtils.filter(linksForTask, e -> FlowConstant.CANDIDATE.equals(e.getType()));
+                List<Long> userIdList = new ArrayList<>();
+                for (HistoricIdentityLink historicIdentityLink : linksForTask) {
+                    try {
+                        userIdList.add(Long.valueOf(historicIdentityLink.getUserId()));
+                    } catch (NumberFormatException ignored) {
+
+                    }
+                }
+                List<SysUserVo> sysUsers = I_WORKFLOW_USER_SERVICE.getUserListByIds(userIdList);
+                if (CollUtil.isNotEmpty(sysUsers)) {
+                    List<Long> userIds = StreamUtils.toList(sysUsers, SysUserVo::getUserId);
+                    List<String> nickNames = StreamUtils.toList(sysUsers, SysUserVo::getNickName);
+                    participantVo.setCandidate(userIds);
+                    participantVo.setCandidateName(nickNames);
+                    if (StringUtils.isBlank(task.getAssignee()) && CollUtil.isNotEmpty(candidateList)) {
+                        participantVo.setClaim(false);
+                    }
+                    if (!StringUtils.isBlank(task.getAssignee()) && CollUtil.isNotEmpty(candidateList)) {
+                        participantVo.setClaim(true);
+                    }
+                }
+            }
+        }
+        return participantVo;
+    }
+
+    /**
+     * 鍒ゆ柇褰撳墠鑺傜偣鏄惁涓轰細绛捐妭鐐�
+     *
+     * @param processDefinitionId 娴佺▼瀹氫箟id
+     * @param taskDefinitionKey   娴佺▼瀹氫箟id
+     */
+    public static MultiInstanceVo isMultiInstance(String processDefinitionId, String taskDefinitionKey) {
+        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
+        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
+        MultiInstanceVo multiInstanceVo = new MultiInstanceVo();
+        //鍒ゆ柇鏄惁涓哄苟琛屼細绛捐妭鐐�
+        if (flowNode.getBehavior() instanceof ParallelMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
+            Expression collectionExpression = behavior.getCollectionExpression();
+            String assigneeList = collectionExpression.getExpressionText();
+            String assignee = behavior.getCollectionElementVariable();
+            multiInstanceVo.setType(behavior);
+            multiInstanceVo.setAssignee(assignee);
+            multiInstanceVo.setAssigneeList(assigneeList);
+            return multiInstanceVo;
+            //鍒ゆ柇鏄惁涓轰覆琛屼細绛捐妭鐐�
+        } else if (flowNode.getBehavior() instanceof SequentialMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
+            Expression collectionExpression = behavior.getCollectionExpression();
+            String assigneeList = collectionExpression.getExpressionText();
+            String assignee = behavior.getCollectionElementVariable();
+            multiInstanceVo.setType(behavior);
+            multiInstanceVo.setAssignee(assignee);
+            multiInstanceVo.setAssigneeList(assigneeList);
+            return multiInstanceVo;
+        }
+        return null;
+    }
+
+    /**
+     * 鑾峰彇褰撳墠娴佺▼鐘舵��
+     *
+     * @param taskId 浠诲姟id
+     */
+    public static String getBusinessStatusByTaskId(String taskId) {
+        HistoricTaskInstance historicTaskInstance = PROCESS_ENGINE.getHistoryService().createHistoricTaskInstanceQuery().taskId(taskId).taskTenantId(TenantHelper.getTenantId()).singleResult();
+        HistoricProcessInstance historicProcessInstance = PROCESS_ENGINE.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(historicTaskInstance.getProcessInstanceId()).processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+        return historicProcessInstance.getBusinessStatus();
+    }
+
+    /**
+     * 鑾峰彇褰撳墠娴佺▼鐘舵��
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥id
+     */
+    public static String getBusinessStatus(String processInstanceId) {
+        HistoricProcessInstance historicProcessInstance = PROCESS_ENGINE.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).processInstanceTenantId(TenantHelper.getTenantId()).singleResult();
+        return historicProcessInstance.getBusinessStatus();
+    }
+
+    /**
+     * 璁剧疆娴佺▼瀹炰緥瀵硅薄
+     *
+     * @param obj         涓氬姟瀵硅薄
+     * @param businessKey 涓氬姟id
+     */
+    public static void setProcessInstanceVo(Object obj, String businessKey) {
+        if (StringUtils.isBlank(businessKey)) {
+            return;
+        }
+        ActHiProcinst actHiProcinst = I_ACT_HI_PROCINST_SERVICE.selectByBusinessKey(businessKey);
+        if (actHiProcinst == null) {
+            ProcessInstanceVo processInstanceVo = new ProcessInstanceVo();
+            processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus());
+            ReflectUtils.invokeSetter(obj, PROCESS_INSTANCE_VO, processInstanceVo);
+            return;
+        }
+        ProcessInstanceVo processInstanceVo = BeanUtil.toBean(actHiProcinst, ProcessInstanceVo.class);
+        processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus()));
+        ReflectUtils.invokeSetter(obj, PROCESS_INSTANCE_VO, processInstanceVo);
+    }
+
+    /**
+     * 璁剧疆娴佺▼瀹炰緥瀵硅薄
+     *
+     * @param obj       涓氬姟瀵硅薄
+     * @param idList    涓氬姟id
+     * @param fieldName 涓婚敭灞炴�у悕绉�
+     */
+    public static void setProcessInstanceListVo(Object obj, List<String> idList, String fieldName) {
+        if (CollUtil.isEmpty(idList)) {
+            return;
+        }
+        List<ActHiProcinst> actHiProcinstList = I_ACT_HI_PROCINST_SERVICE.selectByBusinessKeyIn(idList);
+        if (obj instanceof Collection<?> collection) {
+            for (Object o : collection) {
+                String fieldValue = ReflectUtils.invokeGetter(o, fieldName).toString();
+                if (CollUtil.isEmpty(actHiProcinstList)) {
+                    ProcessInstanceVo processInstanceVo = new ProcessInstanceVo();
+                    processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus());
+                    processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus()));
+                    ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo);
+                } else {
+                    ActHiProcinst actHiProcinst = actHiProcinstList.stream().filter(e -> e.getBusinessKey().equals(fieldValue)).findFirst().orElse(null);
+                    if (ObjectUtil.isNotEmpty(actHiProcinst)) {
+                        ProcessInstanceVo processInstanceVo = BeanUtil.toBean(actHiProcinst, ProcessInstanceVo.class);
+                        processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus()));
+                        ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo);
+                    } else {
+                        ProcessInstanceVo processInstanceVo = new ProcessInstanceVo();
+                        processInstanceVo.setBusinessStatus(BusinessStatusEnum.DRAFT.getStatus());
+                        processInstanceVo.setBusinessStatusName(BusinessStatusEnum.findByStatus(processInstanceVo.getBusinessStatus()));
+                        ReflectUtils.invokeSetter(o, PROCESS_INSTANCE_VO, processInstanceVo);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 鍙戦�佹秷鎭�
+     *
+     * @param list        浠诲姟
+     * @param name        娴佺▼鍚嶇О
+     * @param messageType 娑堟伅绫诲瀷
+     * @param message     娑堟伅鍐呭锛屼负绌哄垯鍙戦�侀粯璁ら厤缃殑娑堟伅鍐呭
+     */
+    public static void sendMessage(List<Task> list, String name, List<String> messageType, String message) {
+        Set<Long> userIds = new HashSet<>();
+        if (StringUtils.isBlank(message)) {
+            message = "鏈夋柊鐨勩��" + name + "銆戝崟鎹凡缁忔彁浜よ嚦鎮ㄧ殑寰呭姙锛岃鎮ㄥ強鏃跺鐞嗐��";
+        }
+        for (Task t : list) {
+            ParticipantVo taskParticipant = WorkflowUtils.getCurrentTaskParticipant(t.getId());
+            if (CollUtil.isNotEmpty(taskParticipant.getGroupIds())) {
+                List<SysUserRole> sysUserRoles = I_WORKFLOW_USER_SERVICE.getUserRoleListByRoleIds(taskParticipant.getGroupIds());
+                if (CollUtil.isNotEmpty(sysUserRoles)) {
+                    userIds.addAll(StreamUtils.toList(sysUserRoles, SysUserRole::getUserId));
+                }
+            }
+            List<Long> candidate = taskParticipant.getCandidate();
+            if (CollUtil.isNotEmpty(candidate)) {
+                userIds.addAll(candidate);
+            }
+        }
+        if (CollUtil.isNotEmpty(userIds)) {
+            List<SysUserVo> sysUserVoList = I_WORKFLOW_USER_SERVICE.getUserListByIds(new ArrayList<>(userIds));
+            for (String code : messageType) {
+                if (code.equals(MessageTypeEnum.SYSTEM_MESSAGE.getCode())) {
+                    WebSocketMessageDto dto = new WebSocketMessageDto();
+                    dto.setSessionKeys(new ArrayList<>(userIds));
+                    dto.setMessage(message);
+                    WebSocketUtils.publishMessage(dto);
+                }
+                if (code.equals(MessageTypeEnum.EMAIL_MESSAGE.getCode())) {
+                    MailUtils.sendText(StreamUtils.join(sysUserVoList, SysUserVo::getEmail), "鍗曟嵁瀹℃壒鎻愰啋", message);
+                }
+                if (code.equals(MessageTypeEnum.SMS_MESSAGE.getCode())) {
+                    //todo 鐭俊鍙戦��
+                }
+            }
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md
new file mode 100644
index 0000000..c938b1e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/package-info.md
@@ -0,0 +1,3 @@
+java鍖呬娇鐢� `.` 鍒嗗壊 resource 鐩綍浣跨敤 `/` 鍒嗗壊
+<br>
+姝ゆ枃浠剁洰鐨� 闃叉鏂囦欢澶圭矘杩炴壘涓嶅埌 `xml` 鏂囦欢
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml
new file mode 100644
index 0000000..44814ec
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiProcinstMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.ActHiProcinstMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml
new file mode 100644
index 0000000..51a095e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActHiTaskinstMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.ActHiTaskinstMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml
new file mode 100644
index 0000000..64d90e6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/ActTaskMapper.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.ActTaskMapper">
+    <resultMap type="org.dromara.workflow.domain.vo.TaskVo" id="TaskWaitingVoResult">
+        <result property="id" column="ID_"/>
+        <result property="name" column="NAME_"/>
+        <result property="description" column="DESCRIPTION_"/>
+        <result property="priority" column="PRIORITY_"/>
+        <result property="owner" column="OWNER_"/>
+        <result property="assignee" column="ASSIGNEE_"/>
+        <result property="processInstanceId" column="PROC_INST_ID_"/>
+        <result property="executionId" column="EXECUTION_ID_"/>
+        <result property="taskDefinitionId" column="TASK_DEF_ID_"/>
+        <result property="processDefinitionId" column="PROC_DEF_ID_"/>
+        <result property="createTime" column="CREATE_TIME_"/>
+        <result property="endTime" column="END_TIME_"/>
+        <result property="taskDefinitionKey" column="TASK_DEF_KEY_"/>
+        <result property="dueDate" column="DUE_DATE_"/>
+        <result property="processDefinitionKey" column="key_"/>
+        <result property="category" column="CATEGORY_"/>
+        <result property="parentTaskId" column="PARENT_TASK_ID_"/>
+        <result property="tenantId" column="TENANT_ID_"/>
+        <result property="claimTime" column="CLAIM_TIME"/>
+        <result property="businessStatus" column="BUSINESS_STATUS_"/>
+        <result property="processDefinitionName" column="processDefinitionName"/>
+        <result property="processDefinitionKey" column="processDefinitionName"/>
+
+    </resultMap>
+    <select id="getTaskWaitByPage" resultMap="TaskWaitingVoResult">
+        select *
+        from (SELECT RES.*,
+                     AHP.BUSINESS_STATUS_,
+                     ARP.NAME_ as processDefinitionName,
+                     ARP.KEY_  as processDefinitionKey,
+                     LINK.USER_ID_,
+                     LINK.GROUP_ID_
+              FROM ACT_RU_TASK RES
+                       INNER JOIN ACT_HI_PROCINST AHP ON RES.PROC_INST_ID_ = AHP.PROC_INST_ID_
+                       INNER JOIN ACT_RE_PROCDEF ARP ON ARP.ID_ = RES.PROC_DEF_ID_
+                       LEFT JOIN ACT_RU_IDENTITYLINK LINK ON LINK.TASK_ID_ = RES.ID_ AND LINK.TYPE_ = 'candidate'
+              WHERE RES.PARENT_TASK_ID_ is null
+              ORDER BY RES.CREATE_TIME_ DESC) t ${ew.getCustomSqlSegment}
+    </select>
+
+    <select id="getTaskCopyByPage" resultMap="TaskWaitingVoResult">
+        select *
+        from (SELECT AHT.*,
+                     AHP.BUSINESS_STATUS_,
+                     ARP.NAME_ as processDefinitionName,
+                     ARP.KEY_  as processDefinitionKey
+              FROM ACT_HI_TASKINST AHT
+                       INNER JOIN ACT_HI_PROCINST AHP ON AHT.PROC_INST_ID_ = AHP.PROC_INST_ID_
+                       INNER JOIN ACT_RE_PROCDEF ARP ON ARP.ID_ = AHT.PROC_DEF_ID_
+              WHERE AHT.PARENT_TASK_ID_ is not null
+                and AHT.scope_type_ = 'copy'
+              ORDER BY AHT.START_TIME_ DESC) t ${ew.getCustomSqlSegment}
+    </select>
+</mapper>
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml
new file mode 100644
index 0000000..e824e3f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/TestLeaveMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.TestLeaveMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml
new file mode 100644
index 0000000..fb05c24
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/WfCategoryMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.workflow.mapper.WfCategoryMapper">
+
+</mapper>
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\344\274\232\347\255\276\357\274\211-leave5.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\344\274\232\347\255\276\357\274\211-leave5.zip"
new file mode 100644
index 0000000..afe031a
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\344\274\232\347\255\276\357\274\211-leave5.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\214\205\345\256\271\347\275\221\345\205\263\357\274\211-leave4.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\214\205\345\256\271\347\275\221\345\205\263\357\274\211-leave4.zip"
new file mode 100644
index 0000000..9df55d9
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\214\205\345\256\271\347\275\221\345\205\263\357\274\211-leave4.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\255\220\346\265\201\347\250\213\357\274\211-leave6.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\255\220\346\265\201\347\250\213\357\274\211-leave6.zip"
new file mode 100644
index 0000000..ca53c3b
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\255\220\346\265\201\347\250\213\357\274\211-leave6.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\271\266\350\241\214\347\275\221\345\205\263\357\274\211-leave3.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\271\266\350\241\214\347\275\221\345\205\263\357\274\211-leave3.zip"
new file mode 100644
index 0000000..ab10060
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\345\271\266\350\241\214\347\275\221\345\205\263\357\274\211-leave3.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\216\222\344\273\226\347\275\221\345\205\263\357\274\211-leave2.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\216\222\344\273\226\347\275\221\345\205\263\357\274\211-leave2.zip"
new file mode 100644
index 0000000..9e151e2
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\216\222\344\273\226\347\275\221\345\205\263\357\274\211-leave2.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\231\256\351\200\232\346\265\201\347\250\213\357\274\211-leave1.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\231\256\351\200\232\346\265\201\347\250\213\357\274\211-leave1.zip"
new file mode 100644
index 0000000..2edee6a
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\346\231\256\351\200\232\346\265\201\347\250\213\357\274\211-leave1.zip"
Binary files differ
diff --git "a/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\347\233\221\345\220\254\357\274\211-leave7.zip" "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\347\233\221\345\220\254\357\274\211-leave7.zip"
new file mode 100644
index 0000000..0a71e29
--- /dev/null
+++ "b/script/bpmn/\350\257\267\345\201\207\346\265\201\347\250\213\357\274\210\347\233\221\345\220\254\357\274\211-leave7.zip"
Binary files differ
diff --git a/script/sql/flowable.sql b/script/sql/flowable.sql
new file mode 100644
index 0000000..b064647
--- /dev/null
+++ b/script/sql/flowable.sql
@@ -0,0 +1,65 @@
+insert into sys_menu values('11616', '宸ヤ綔娴�'  , '0',    '6', 'workflow',          '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11617', '妯″瀷绠$悊', '11616', '2', 'model',             'workflow/model/index',             '', '1', '1', 'C', '0', '0', 'workflow:model:list',    'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11618', '鎴戠殑浠诲姟', '0', '7', 'task',              '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting',       'workflow/task/taskWaiting',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish',       'workflow/task/taskFinish',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList',       'workflow/task/taskCopyList',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance',   'workflow/processInstance/index',   '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11622', '娴佺▼鍒嗙被', '11616', '1', 'category',          'workflow/category/index',          '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument',        'workflow/task/myDocument',         '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor',           '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+insert into sys_menu values('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting',    'workflow/task/allTaskWaiting',     '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate(), NULL, NULL, '');
+
+
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add',   '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit',  '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, sysdate(), null, null, '');
+-- 璇峰亣鍗曚俊鎭�
+DROP TABLE if EXISTS test_leave;
+create table test_leave
+(
+    id          bigint                       not null comment '涓婚敭',
+    leave_type  varchar(255)                 not null comment '璇峰亣绫诲瀷',
+    start_date   datetime                     not null comment '寮�濮嬫椂闂�',
+    end_date     datetime                     not null comment '缁撴潫鏃堕棿',
+    leave_days  int(10)                      not null comment '璇峰亣澶╂暟',
+    remark      varchar(255)                 null comment '璇峰亣鍘熷洜',
+    create_dept bigint                       null comment '鍒涘缓閮ㄩ棬',
+    create_by   bigint                       null comment '鍒涘缓鑰�',
+    create_time datetime                     null comment '鍒涘缓鏃堕棿',
+    update_by   bigint                       null comment '鏇存柊鑰�',
+    update_time datetime                     null comment '鏇存柊鏃堕棿',
+    tenant_id   varchar(20) default '000000' null comment '绉熸埛缂栧彿',
+    PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB COMMENT = '璇峰亣鐢宠琛�';
+
+-- 娴佺▼鍒嗙被淇℃伅琛�
+DROP TABLE if EXISTS wf_category;
+create table wf_category
+(
+    id            bigint                       not null comment '涓婚敭'
+        primary key,
+    category_name varchar(255)                 null comment '鍒嗙被鍚嶇О',
+    category_code varchar(255)                 null comment '鍒嗙被缂栫爜',
+    parent_id     bigint                       null comment '鐖剁骇id',
+    sort_num      int(19)                      null comment '鎺掑簭',
+    tenant_id     varchar(20) default '000000' null comment '绉熸埛缂栧彿',
+    create_dept   bigint                       null comment '鍒涘缓閮ㄩ棬',
+    create_by     bigint                       null comment '鍒涘缓鑰�',
+    create_time   datetime                     null comment '鍒涘缓鏃堕棿',
+    update_by     bigint                       null comment '鏇存柊鑰�',
+    update_time   datetime                     null comment '鏇存柊鏃堕棿',
+    constraint uni_category_code
+        unique (category_code)
+) engine=innodb comment= '娴佺▼鍒嗙被';
+
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '璇峰亣鐢宠', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, sysdate(), NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '璇峰亣鐢宠鏌ヨ', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '璇峰亣鐢宠鏂板', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '璇峰亣鐢宠淇敼', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '璇峰亣鐢宠鍒犻櫎', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '璇峰亣鐢宠瀵煎嚭', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, sysdate(), NULL, NULL, '');
diff --git a/script/sql/oracle/flowable.sql b/script/sql/oracle/flowable.sql
new file mode 100644
index 0000000..308c7c4
--- /dev/null
+++ b/script/sql/oracle/flowable.sql
@@ -0,0 +1,93 @@
+insert into sys_menu values('11616', '宸ヤ綔娴�'  , '0',    '6', 'workflow',          '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11617', '妯″瀷绠$悊', '11616', '2', 'model',             'workflow/model/index',             '', '1', '1', 'C', '0', '0', 'workflow:model:list',    'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11618', '鎴戠殑浠诲姟', '0', '7', 'task',              '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting',       'workflow/task/taskWaiting',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish',       'workflow/task/taskFinish',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList',       'workflow/task/taskCopyList',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance',   'workflow/processInstance/index',   '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11622', '娴佺▼鍒嗙被', '11616', '1', 'category',          'workflow/category/index',          '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument',        'workflow/task/myDocument',         '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor',           '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+insert into sys_menu values('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting',    'workflow/task/allTaskWaiting',     '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, sysdate, NULL, NULL, '');
+
+
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add',   '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit',  '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, sysdate, null, null, '');
+
+-- 璇峰亣鍗曚俊鎭�
+create table TEST_LEAVE
+(
+    ID          NUMBER(20) not null
+        constraint PK_TEST_LEAVE
+        primary key,
+    LEAVE_TYPE  VARCHAR2(255),
+    START_DATE  DATE,
+    END_DATE    DATE,
+    LEAVE_DAYS  NUMBER(10),
+    REMARK      VARCHAR2(255),
+    CREATE_DEPT NUMBER(20),
+    CREATE_BY   NUMBER(20),
+    CREATE_TIME DATE,
+    UPDATE_BY   NUMBER(20),
+    UPDATE_TIME DATE,
+    TENANT_ID   VARCHAR2(255) default '000000'
+);
+
+comment on table TEST_LEAVE is '璇峰亣鐢宠琛�'
+comment on column TEST_LEAVE.ID is '涓婚敭'
+comment on column TEST_LEAVE.LEAVE_TYPE is '璇峰亣绫诲瀷'
+comment on column TEST_LEAVE.START_DATE is '寮�濮嬫椂闂�'
+comment on column TEST_LEAVE.END_DATE is '缁撴潫鏃堕棿'
+comment on column TEST_LEAVE.LEAVE_DAYS is '璇峰亣澶╂暟'
+comment on column TEST_LEAVE.REMARK is '璇峰亣鍘熷洜'
+comment on column TEST_LEAVE.CREATE_DEPT is '鍒涘缓閮ㄩ棬'
+comment on column TEST_LEAVE.CREATE_BY is '鍒涘缓鑰�'
+comment on column TEST_LEAVE.CREATE_TIME is '鍒涘缓鏃堕棿'
+comment on column TEST_LEAVE.UPDATE_BY is '鏇存柊鑰�'
+comment on column TEST_LEAVE.UPDATE_TIME is '鏇存柊鏃堕棿'
+comment on column TEST_LEAVE.TENANT_ID is '绉熸埛缂栧彿'
+
+-- 娴佺▼鍒嗙被淇℃伅琛�
+create table WF_CATEGORY
+(
+    ID            NUMBER(20) not null
+        constraint PK_WF_CATEGORY
+        primary key,
+    CATEGORY_NAME VARCHAR2(255),
+    CATEGORY_CODE VARCHAR2(255)
+        constraint UNI_CATEGORY_CODE
+        unique,
+    PARENT_ID     NUMBER(20),
+    SORT_NUM      NUMBER(10),
+    TENANT_ID     NUMBER(20),
+    CREATE_DEPT   NUMBER(20),
+    CREATE_BY     NUMBER(20),
+    CREATE_TIME   DATE,
+    UPDATE_BY     NUMBER(20),
+    UPDATE_TIME   DATE
+);
+
+comment on table WF_CATEGORY is '娴佺▼鍒嗙被'
+comment on column WF_CATEGORY.ID is '涓婚敭'
+comment on column WF_CATEGORY.CATEGORY_NAME is '鍒嗙被鍚嶇О'
+comment on column WF_CATEGORY.CATEGORY_CODE is '鍒嗙被缂栫爜'
+comment on column WF_CATEGORY.PARENT_ID is '鐖剁骇id'
+comment on column WF_CATEGORY.SORT_NUM is '鎺掑簭'
+comment on column WF_CATEGORY.TENANT_ID is '绉熸埛缂栧彿'
+comment on column WF_CATEGORY.CREATE_DEPT is '鍒涘缓閮ㄩ棬'
+comment on column WF_CATEGORY.CREATE_BY is '鍒涘缓鑰�'
+comment on column WF_CATEGORY.CREATE_TIME is '鍒涘缓鏃堕棿'
+comment on column WF_CATEGORY.UPDATE_BY is '鏇存柊鑰�'
+comment on column WF_CATEGORY.UPDATE_TIME is '鏇存柊鏃堕棿'
+
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '璇峰亣鐢宠', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, sysdate, NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '璇峰亣鐢宠鏌ヨ', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, sysdate, NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '璇峰亣鐢宠鏂板', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, sysdate, NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '璇峰亣鐢宠淇敼', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, sysdate, NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '璇峰亣鐢宠鍒犻櫎', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, sysdate, NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '璇峰亣鐢宠瀵煎嚭', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, sysdate, NULL, NULL, '');
diff --git a/script/sql/postgres/flowable.sql b/script/sql/postgres/flowable.sql
new file mode 100644
index 0000000..aa68592
--- /dev/null
+++ b/script/sql/postgres/flowable.sql
@@ -0,0 +1,122 @@
+insert into sys_menu values('11616', '宸ヤ綔娴�'  , '0',    '6', 'workflow',          '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11617', '妯″瀷绠$悊', '11616', '2', 'model',             'workflow/model/index',             '', '1', '1', 'C', '0', '0', 'workflow:model:list',    'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11618', '鎴戠殑浠诲姟', '0', '7', 'task',              '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting',       'workflow/task/taskWaiting',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish',       'workflow/task/taskFinish',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList',       'workflow/task/taskCopyList',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance',   'workflow/processInstance/index',   '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11622', '娴佺▼鍒嗙被', '11616', '1', 'category',          'workflow/category/index',          '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument',        'workflow/task/myDocument',         '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor',           '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+insert into sys_menu values('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting',    'workflow/task/allTaskWaiting',     '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, now(), NULL, NULL, '');
+
+
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, now(), null, null, '');
+insert into sys_menu values ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add',   '#', 103, 1, now(), null, null, '');
+insert into sys_menu values ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit',  '#', 103, 1, now(), null, null, '');
+insert into sys_menu values ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, now(), null, null, '');
+insert into sys_menu values ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, now(), null, null, '');
+-- 璇峰亣鍗曚俊鎭�
+DROP TABLE if EXISTS test_leave;
+create table test_leave
+(
+    id          bigint not null
+        constraint test_leave_pk
+            primary key,
+    leave_type  varchar(255),
+    start_date  timestamp,
+    end_date    timestamp,
+    leave_days  bigint,
+    remark      varchar(255),
+    create_dept bigint,
+    create_by   bigint,
+    create_time timestamp,
+    update_by   bigint,
+    update_time timestamp,
+    tenant_id   varchar(20)
+);
+
+comment on table test_leave is '璇峰亣鐢宠琛�';
+
+comment on column test_leave.id is '涓婚敭';
+
+comment on column test_leave.leave_type is '璇峰亣绫诲瀷';
+
+comment on column test_leave.start_date is '寮�濮嬫椂闂�';
+
+comment on column test_leave.end_date is '缁撴潫鏃堕棿';
+
+comment on column test_leave.remark is '璇峰亣鍘熷洜';
+
+comment on column test_leave.create_dept is '鍒涘缓閮ㄩ棬';
+
+comment on column test_leave.create_by is '鍒涘缓鑰�';
+
+comment on column test_leave.create_time is '鍒涘缓鏃堕棿';
+
+comment on column test_leave.update_by is '鏇存柊鑰�';
+
+comment on column test_leave.update_time is '鏇存柊鏃堕棿';
+
+comment on column test_leave.tenant_id is '绉熸埛缂栫爜';
+
+alter table test_leave
+    owner to postgres;
+
+-- 娴佺▼鍒嗙被淇℃伅琛�
+DROP TABLE if EXISTS wf_category;
+create table wf_category
+(
+    id            bigint not null
+        constraint wf_category_pk
+            primary key,
+    category_name varchar(255),
+    category_code varchar(255),
+    parent_id     bigint,
+    sort_num      bigint,
+    tenant_id     bigint,
+    create_dept   bigint,
+    create_by     bigint,
+    create_time   timestamp,
+    update_by     bigint,
+    update_time   timestamp
+);
+
+comment on table wf_category is '娴佺▼鍒嗙被';
+
+comment on column wf_category.id is '涓婚敭';
+
+comment on column wf_category.category_name is '鍒嗙被鍚嶇О';
+
+comment on column wf_category.category_code is '鍒嗙被缂栫爜';
+
+comment on column wf_category.parent_id is '鐖剁骇id';
+
+comment on column wf_category.sort_num is '鎺掑簭';
+
+comment on column wf_category.tenant_id is '绉熸埛id';
+
+comment on column wf_category.create_dept is '鍒涘缓閮ㄩ棬';
+
+comment on column wf_category.create_by is '鍒涘缓鑰�';
+
+comment on column wf_category.create_time is '鍒涘缓鏃堕棿';
+
+comment on column wf_category.update_by is '淇敼鑰�';
+
+comment on column wf_category.update_time is '淇敼鏃堕棿';
+
+alter table wf_category
+    owner to postgres;
+
+create unique index uni_category_code
+    on wf_category (category_code);
+
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '璇峰亣鐢宠', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, now(), NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '璇峰亣鐢宠鏌ヨ', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '璇峰亣鐢宠鏂板', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '璇峰亣鐢宠淇敼', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '璇峰亣鐢宠鍒犻櫎', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '璇峰亣鐢宠瀵煎嚭', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, now(), NULL, NULL, '');
diff --git a/script/sql/sqlserver/flowable.sql b/script/sql/sqlserver/flowable.sql
new file mode 100644
index 0000000..2c3f706
--- /dev/null
+++ b/script/sql/sqlserver/flowable.sql
@@ -0,0 +1,155 @@
+insert into sys_menu values('11616', '宸ヤ綔娴�'  , '0',    '6', 'workflow',          '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11617', '妯″瀷绠$悊', '11616', '2', 'model',             'workflow/model/index',             '', '1', '1', 'C', '0', '0', 'workflow:model:list',    'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11618', '鎴戠殑浠诲姟', '0', '7', 'task',              '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11619', '鎴戠殑寰呭姙', '11618', '2', 'taskWaiting',       'workflow/task/taskWaiting',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11632', '鎴戠殑宸插姙', '11618', '3', 'taskFinish',       'workflow/task/taskFinish',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11633', '鎴戠殑鎶勯��', '11618', '4', 'taskCopyList',       'workflow/task/taskCopyList',              '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11620', '娴佺▼瀹氫箟', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11621', '娴佺▼瀹炰緥', '11630', '1', 'processInstance',   'workflow/processInstance/index',   '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11622', '娴佺▼鍒嗙被', '11616', '1', 'category',          'workflow/category/index',          '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11629', '鎴戝彂璧风殑', '11618', '1', 'myDocument',        'workflow/task/myDocument',         '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11630', '娴佺▼鐩戞帶', '11616', '4', 'monitor',           '',                                 '', '1', '0', 'M', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+insert into sys_menu values('11631', '寰呭姙浠诲姟', '11630', '2', 'allTaskWaiting',    'workflow/task/allTaskWaiting',     '', '1', '1', 'C', '0', '0', '',                       'tree-table', 103, 1, getdate(), NULL, NULL, '');
+
+
+-- 娴佺▼鍒嗙被绠$悊鐩稿叧鎸夐挳
+insert into sys_menu values ('11623', '娴佺▼鍒嗙被鏌ヨ', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1, getdate(), null, null, '');
+insert into sys_menu values ('11624', '娴佺▼鍒嗙被鏂板', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add',   '#', 103, 1, getdate(), null, null, '');
+insert into sys_menu values ('11625', '娴佺▼鍒嗙被淇敼', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit',  '#', 103, 1, getdate(), null, null, '');
+insert into sys_menu values ('11626', '娴佺▼鍒嗙被鍒犻櫎', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove','#', 103, 1, getdate(), null, null, '');
+insert into sys_menu values ('11627', '娴佺▼鍒嗙被瀵煎嚭', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export','#', 103, 1, getdate(), null, null, '');
+-- 璇峰亣鍗曚俊鎭�
+DROP TABLE if EXISTS test_leave;
+create table test_leave
+(
+    id          bigint        not null
+        primary key,
+    leave_type  nvarchar(255) not null,
+    start_date  datetime2     not null,
+    end_date    datetime2     not null,
+    leave_days  int           not null,
+    remark      nvarchar(255),
+    create_dept bigint,
+    create_by   bigint,
+    create_time datetime2,
+    update_by   bigint,
+    update_time datetime2,
+    tenant_id   nvarchar(20) default '000000'
+)
+go
+
+exec sp_addextendedproperty 'MS_Description', N'璇峰亣鐢宠琛�', 'SCHEMA', 'dbo', 'TABLE', 'test_leave'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'涓婚敭', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'id'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'璇峰亣绫诲瀷', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'leave_type'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'寮�濮嬫椂闂�', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'start_date'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'缁撴潫鏃堕棿', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'end_date'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'璇峰亣澶╂暟', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'leave_days'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'璇峰亣鍘熷洜', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'remark'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓閮ㄩ棬', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'create_dept'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓鑰�', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'create_by'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓鏃堕棿', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'create_time'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鏇存柊鑰�', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'update_by'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鏇存柊鏃堕棿', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN',
+     'update_time'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'绉熸埛缂栧彿', 'SCHEMA', 'dbo', 'TABLE', 'test_leave', 'COLUMN', 'tenant_id'
+go
+
+-- 娴佺▼鍒嗙被淇℃伅琛�
+DROP TABLE if EXISTS wf_category;
+create table wf_category
+(
+    id            bigint not null
+        primary key,
+    category_name nvarchar(255),
+    category_code nvarchar(255)
+        constraint uni_category_code
+        unique,
+    parent_id     bigint,
+    sort_num      int,
+    tenant_id     nvarchar(20) default '000000',
+    create_dept   bigint,
+    create_by     bigint,
+    create_time   datetime2,
+    update_by     bigint,
+    update_time   datetime2
+)
+go
+
+exec sp_addextendedproperty 'MS_Description', N'娴佺▼鍒嗙被', 'SCHEMA', 'dbo', 'TABLE', 'wf_category'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'涓婚敭', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'id'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒嗙被鍚嶇О', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'category_name'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒嗙被缂栫爜', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'category_code'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鐖剁骇id', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'parent_id'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鎺掑簭', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'sort_num'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'绉熸埛缂栧彿', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'tenant_id'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓閮ㄩ棬', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'create_dept'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓鑰�', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'create_by'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鍒涘缓鏃堕棿', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'create_time'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鏇存柊鑰�', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN', 'update_by'
+go
+
+exec sp_addextendedproperty 'MS_Description', N'鏇存柊鏃堕棿', 'SCHEMA', 'dbo', 'TABLE', 'wf_category', 'COLUMN',
+     'update_time'
+go
+
+
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '璇峰亣鐢宠', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'demo:leave:list', '#', 103, 1, getdate(), NULL, NULL, '璇峰亣鐢宠鑿滃崟');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11639, '璇峰亣鐢宠鏌ヨ', 11638, 1, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:query', '#', 103, 1, getdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11640, '璇峰亣鐢宠鏂板', 11638, 2, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:add', '#', 103, 1, getdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11641, '璇峰亣鐢宠淇敼', 11638, 3, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:edit', '#', 103, 1, getdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11642, '璇峰亣鐢宠鍒犻櫎', 11638, 4, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:remove', '#', 103, 1, getdate(), NULL, NULL, '');
+INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11643, '璇峰亣鐢宠瀵煎嚭', 11638, 5, '#', '', 1, 0, 'F', '0', '0', 'demo:leave:export', '#', 103, 1, getdate(), NULL, NULL, '');

--
Gitblit v1.9.3