From 50da976ac81fbf9f3fcd6aeeeb48ec55d7e692e9 Mon Sep 17 00:00:00 2001
From: shenrongliang <1328040932@qq.com>
Date: 星期四, 26 六月 2025 10:11:11 +0800
Subject: [PATCH] 集成工作流

---
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java            |  118 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java               |   67 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java   |  232 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java          |   77 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java        |   59 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java         |   58 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java     |  146 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java                             |  253 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java          |  101 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java                  |   48 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java         |  165 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java                    |  192 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java                 |   81 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java                     |   28 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java    | 1037 +++++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java           |   59 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java   |   84 
 ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java                      |    4 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java                 |   42 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java               |   48 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java               |   69 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java                    |   52 
 ruoyi-admin/src/main/resources/application.yml                                                                   |    3 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java            |   61 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java           |   53 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java               |  370 ++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java               |   38 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java              |   82 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java                              |  374 ++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java              |   27 
 ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml                                                    |   27 
 ruoyi-admin/src/main/resources/application-dev.yml                                                               |    9 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java         |   39 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java                    |   44 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java            |   18 
 ruoyi-modules/pom.xml                                                                                            |    1 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java                         |   33 
 ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml                                                      |   84 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java        |   99 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java                   |  112 
 ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt                                                       |   12 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java                 |   23 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java                           |  206 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java      |   71 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java       |  118 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java                  |   18 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java         |   75 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java             |   27 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java                  |   59 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java                 |   49 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java            |  405 ++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java               |  118 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java             |   47 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java                   |   65 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java     |   71 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java               |  123 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java                   |   43 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java               |   50 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java                                 |   69 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java                      |   44 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java                               |   50 
 ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml                                   |   12 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java                               |  115 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java                   |   46 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java |  134 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java                   |  134 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java                |   33 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java       |  131 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java                      |   41 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java              |   19 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java          |   94 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java                   |   94 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java                        |   51 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java                        |   44 
 ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java           |   16 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java                      |   82 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java      |  102 
 ruoyi-modules/ruoyi-flowable/pom.xml                                                                             |   58 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java                            |  706 +++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java      |  364 ++
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java                           |   24 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java                        |   78 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java                   |   76 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java          |  178 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java                            |  125 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java                 |   26 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java                  |   61 
 ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java                   |    4 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java              |   28 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java                       |   78 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java                    |   43 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java       |  664 +++
 ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml                             |   19 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java                  |   24 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java              |   72 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java       |  239 +
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java             |   32 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java                  |   40 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java             |   83 
 ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java                         |  222 +
 100 files changed, 10,626 insertions(+), 3 deletions(-)

diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 2492b43..1d1594c 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -89,9 +89,16 @@
   # 鑵捐浜� sms.tencentcloudapi.com
   endpoint: "dysmsapi.aliyuncs.com"
   accessKeyId: xxxxxxx
-  accessKeySecret: xxxxxx
+  accessKeySecret: xxxxxxq
   signName: 娴嬭瘯
   # 鑵捐涓撶敤
   sdkAppId:
+# ===============================
+# Flowable 閰嶇疆锛堥泦鎴� Flowable 鏃跺惎鐢級
+# ===============================
+flowable:
+  database-schema-update: true   # 棣栨杩愯寤鸿涓� true锛屽悗鏈熻涓� false
+  async-executor-activate: false
+  id-generator: simple
 
 
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index b507d85..a887655 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -314,5 +314,4 @@
               url: http://127.0.0.1:8081
         stdio:
           servers-configuration: classpath:mcp-server.json
-        request-timeout: 300s
-
+        request-timeout: 300s
\ No newline at end of file
diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java
index 4de52e3..5071dee 100644
--- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java
+++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java
@@ -162,4 +162,8 @@
      *
      */
     void updateXcxUser(SysUserBo user);
+
+    List<SysUser> selectUserByRoleIds(List<Long> groups);
+
+    List<SysUser> selectUserByDeptIds(List<Long> groups);
 }
diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java
index 63bec16..42ef9cf 100644
--- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java
+++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysUserService.java
@@ -1,5 +1,6 @@
 package org.ruoyi.system.service;
 
+import org.ruoyi.common.core.domain.R;
 import org.ruoyi.core.page.PageQuery;
 import org.ruoyi.core.page.TableDataInfo;
 import org.ruoyi.system.domain.SysUser;
@@ -227,4 +228,7 @@
      */
     int deleteUserByIds(Long[] userIds);
 
+    R<List<SysUser>> selectUserByRoleIds(List<Long> groups);
+
+    R<List<SysUser>> selectUserByDeptIds(List<Long> groups);
 }
diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
index b5165cd..c94c577 100644
--- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -13,6 +13,7 @@
 import lombok.extern.slf4j.Slf4j;
 import org.ruoyi.common.core.constant.CacheNames;
 import org.ruoyi.common.core.constant.UserConstants;
+import org.ruoyi.common.core.domain.R;
 import org.ruoyi.common.core.exception.ServiceException;
 import org.ruoyi.common.core.service.UserService;
 import org.ruoyi.common.core.utils.MapstructUtils;
@@ -564,4 +565,19 @@
                 .eq(SysUser::getUserName, userName));
         return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserBalance().toString();
     }
+
+    /**
+     * 鏍规嵁瑙掕壊缁勬煡璇㈢敤鎴�
+     */
+    @Override
+    public R<List<SysUser>> selectUserByRoleIds(List<Long> groups) {
+        List<SysUser> list = baseMapper.selectUserByRoleIds(groups);
+        return R.ok(list);
+    }
+
+    @Override
+    public R<List<SysUser>> selectUserByDeptIds(List<Long> groups) {
+        List<SysUser> list = baseMapper.selectUserByDeptIds(groups);
+        return R.ok(list);
+    }
 }
diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml b/ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml
index 8d39f48..67d648e 100644
--- a/ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml
+++ b/ruoyi-modules-api/ruoyi-system-api/src/main/resources/mapper/SysUserMapper.xml
@@ -143,6 +143,18 @@
         <include refid="selectUserVo"/>
         where u.del_flag = '0' and u.user_id = #{userId}
     </select>
+    <select id="selectUserByRoleIds" resultMap="SysUserResult">
+        <include refid="selectUserVo"/>
+        where u.del_flag = '0' and r.role_id in <foreach item="item" collection="list" separator="," open="(" close=")" index="">
+            #{item}
+        </foreach>
+    </select>
+    <select id="selectUserByDeptIds" resultMap="SysUserResult">
+        <include refid="selectUserVo"/>
+        where u.del_flag = '0' and u.dept_id in <foreach item="item" collection="list" separator="," open="(" close=")" index="">
+            #{item}
+        </foreach>
+    </select>
 
 
 </mapper>
diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml
index 1b3ff0c..95230d7 100644
--- a/ruoyi-modules/pom.xml
+++ b/ruoyi-modules/pom.xml
@@ -21,6 +21,7 @@
         <module>ruoyi-chat</module>
         <module>ruoyi-system</module>
         <module>ruoyi-generator</module>
+        <module>ruoyi-flowable</module>
     </modules>
 
     <properties>
diff --git a/ruoyi-modules/ruoyi-flowable/pom.xml b/ruoyi-modules/ruoyi-flowable/pom.xml
new file mode 100644
index 0000000..78a807e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/pom.xml
@@ -0,0 +1,58 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>ruoyi-modules</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>ruoyi-flowable</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <flowable.version>6.8.0</flowable.version>
+    </properties>
+
+    <dependencies>
+        <!-- Flowable 宸ヤ綔娴� -->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-starter-process</artifactId>
+            <version>${flowable.version}</version>
+        </dependency>
+
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+            <version>5.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-system-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>oauth2-oidc-sdk</artifactId>
+            <version>11.10.1</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java
new file mode 100644
index 0000000..36f58c5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/ProcessConstants.java
@@ -0,0 +1,82 @@
+package org.ruoyi.flowable.common.constant;
+
+/**
+ * 娴佺▼甯搁噺淇℃伅
+ *
+ * @author Xuan xuan
+ * @date 2021/4/17 22:46
+ */
+public class ProcessConstants {
+
+    public static final String SUFFIX = ".bpmn";
+
+    /**
+     * 鍔ㄦ�佹暟鎹�
+     */
+    public static final String DATA_TYPE = "dynamic";
+
+    /**
+     * 鍗曚釜瀹℃壒浜�
+     */
+    public static final String USER_TYPE_ASSIGNEE = "assignee";
+
+
+    /**
+     * 鍊欓�変汉
+     */
+    public static final String USER_TYPE_USERS = "candidateUsers";
+
+
+    /**
+     * 瀹℃壒缁�
+     */
+    public static final String USER_TYPE_ROUPS = "candidateGroups";
+
+    /**
+     * 鍗曚釜瀹℃壒浜�
+     */
+    public static final String PROCESS_APPROVAL = "approval";
+
+    /**
+     * 浼氱浜哄憳
+     */
+    public static final String PROCESS_MULTI_INSTANCE_USER = "userList";
+
+    /**
+     * nameapace
+     */
+    public static final String NAMASPASE = "http://flowable.org/bpmn";
+
+    /**
+     * 浼氱鑺傜偣
+     */
+    public static final String PROCESS_MULTI_INSTANCE = "multiInstance";
+
+    /**
+     * 鑷畾涔夊睘鎬� dataType
+     */
+    public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType";
+
+    /**
+     * 鑷畾涔夊睘鎬� userType
+     */
+    public static final String PROCESS_CUSTOM_USER_TYPE = "userType";
+
+    /**
+     * 鑷畾涔夊睘鎬� localScope
+     */
+    public static final String PROCESS_FORM_LOCAL_SCOPE = "localScope";
+
+    /**
+     * 鑷畾涔夊睘鎬� 娴佺▼鐘舵��
+     */
+    public static final String PROCESS_STATUS_KEY = "processStatus";
+
+
+    /**
+     * 娴佺▼璺宠繃
+     */
+    public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
+
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java
new file mode 100644
index 0000000..8cfb176
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/constant/TaskConstants.java
@@ -0,0 +1,23 @@
+package org.ruoyi.flowable.common.constant;
+
+/**
+ * @author konbai
+ * @createTime 2022/4/24 13:24
+ */
+public class TaskConstants {
+
+    /**
+     * 娴佺▼鍙戣捣浜�
+     */
+    public static final String PROCESS_INITIATOR = "initiator";
+
+    /**
+     * 瑙掕壊鍊欓�夌粍鍓嶇紑
+     */
+    public static final String ROLE_GROUP_PREFIX = "ROLE";
+
+    /**
+     * 閮ㄩ棬鍊欓�夌粍鍓嶇紑
+     */
+    public static final String DEPT_GROUP_PREFIX = "DEPT";
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java
new file mode 100644
index 0000000..5097d79
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FlowComment.java
@@ -0,0 +1,44 @@
+package org.ruoyi.flowable.common.enums;
+
+/**
+ * 娴佺▼鎰忚绫诲瀷
+ *
+ * @author Xuan xuan
+ * @date 2021/4/19
+ */
+public enum FlowComment {
+
+    /**
+     * 璇存槑
+     */
+    NORMAL("1", "姝e父"),
+    REBACK("2", "閫�鍥�"),
+    REJECT("3", "椹冲洖"),
+    DELEGATE("4", "濮旀淳"),
+    TRANSFER("5", "杞姙"),
+    STOP("6", "缁堟"),
+    REVOKE("7", "鎾ゅ洖");
+
+    /**
+     * 绫诲瀷
+     */
+    private final String type;
+
+    /**
+     * 璇存槑
+     */
+    private final String remark;
+
+    FlowComment(String type, String remark) {
+        this.type = type;
+        this.remark = remark;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java
new file mode 100644
index 0000000..56edc78
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/FormType.java
@@ -0,0 +1,33 @@
+package org.ruoyi.flowable.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/28 9:51
+ */
+@Getter
+@AllArgsConstructor
+public enum FormType {
+
+    /**
+     * 娴佺▼琛ㄥ崟
+     */
+    PROCESS(0),
+
+    /**
+     * 澶栫疆琛ㄥ崟
+     */
+    EXTERNAL(1),
+
+    /**
+     * 鑺傜偣鐙珛琛ㄥ崟
+     */
+    INDEPENDENT(2);
+
+    /**
+     * 琛ㄥ崟绫诲瀷
+     */
+    private final Integer type;
+}
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java
new file mode 100644
index 0000000..407d99b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/common/enums/ProcessStatus.java
@@ -0,0 +1,44 @@
+package org.ruoyi.flowable.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.ruoyi.common.core.utils.StringUtils;
+
+/**
+ * @author konbai
+ * @since 2023/3/9 00:45
+ */
+@Getter
+@AllArgsConstructor
+public enum ProcessStatus {
+
+    /**
+     * 杩涜涓紙瀹℃壒涓級
+     */
+    RUNNING("running"),
+    /**
+     * 宸茬粓姝�
+     */
+    TERMINATED("terminated"),
+    /**
+     * 宸插畬鎴�
+     */
+    COMPLETED("completed"),
+    /**
+     * 宸插彇娑�
+     */
+    CANCELED("canceled");
+
+    private final String status;
+
+    public static ProcessStatus getProcessStatus(String str) {
+        if (StringUtils.isNotBlank(str)) {
+            for (ProcessStatus value : values()) {
+                if (StringUtils.equalsIgnoreCase(str, value.getStatus())) {
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java
new file mode 100644
index 0000000..7f885ae
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/GlobalEventListenerConfig.java
@@ -0,0 +1,28 @@
+package org.ruoyi.flowable.config;
+
+import lombok.AllArgsConstructor;
+import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
+import org.flowable.engine.RuntimeService;
+import org.ruoyi.flowable.listener.GlobalEventListener;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * flowable鍏ㄥ眬鐩戝惉閰嶇疆
+ *
+ * @author ssc
+ */
+@Configuration
+@AllArgsConstructor
+public class GlobalEventListenerConfig implements ApplicationListener<ContextRefreshedEvent> {
+
+    private final GlobalEventListener globalEventListener;
+    private final RuntimeService runtimeService;
+
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        // 娴佺▼姝e父缁撴潫
+        runtimeService.addEventListener(globalEventListener, FlowableEngineEventType.PROCESS_COMPLETED);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java
new file mode 100644
index 0000000..ee9741c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MyDefaultProcessDiagramCanvas.java
@@ -0,0 +1,94 @@
+package org.ruoyi.flowable.config;
+
+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;
+
+/**
+ * @author XuanXuan
+ * @date 2021-04-03
+ */
+public class MyDefaultProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
+    //璁剧疆楂樹寒绾跨殑棰滆壊  杩欓噷鎴戣缃垚缁胯壊
+    protected static Color HIGHLIGHT_SEQUENCEFLOW_COLOR = Color.GREEN;
+
+    public MyDefaultProcessDiagramCanvas(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 MyDefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
+        super(width, height, minX, minY, imageType);
+    }
+
+
+    /**
+     * 鐢荤嚎棰滆壊璁剧疆
+     */
+    @Override
+    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(originalPaint);
+            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);
+    }
+
+    /**
+     * 楂樹寒鑺傜偣璁剧疆
+     */
+    @Override
+    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);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..1ab8d26
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/config/MybatisPlusConfig.java
@@ -0,0 +1,82 @@
+package org.ruoyi.flowable.config;
+
+import cn.hutool.core.net.NetUtil;
+import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * mybatis-plus閰嶇疆绫�(涓嬫柟娉ㄩ噴鏈夋彃浠朵粙缁�)
+ *
+ * @author Lion Li
+ */
+@EnableTransactionManagement(proxyTargetClass = true)
+@Configuration
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 鍒嗛〉鎻掍欢
+        interceptor.addInnerInterceptor(paginationInnerInterceptor());
+        // 涔愯閿佹彃浠�
+        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+
+
+    /**
+     * 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+     */
+    public PaginationInnerInterceptor paginationInnerInterceptor() {
+        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+        // 璁剧疆鏈�澶у崟椤甸檺鍒舵暟閲忥紝榛樿 500 鏉★紝-1 涓嶅彈闄愬埗
+        paginationInnerInterceptor.setMaxLimit(-1L);
+        // 鍒嗛〉鍚堢悊鍖�
+        paginationInnerInterceptor.setOverflow(true);
+        return paginationInnerInterceptor;
+    }
+
+    /**
+     * 涔愯閿佹彃浠�
+     */
+    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+        return new OptimisticLockerInnerInterceptor();
+    }
+
+
+    /**
+     * 浣跨敤缃戝崱淇℃伅缁戝畾闆姳鐢熸垚鍣�
+     * 闃叉闆嗙兢闆姳ID閲嶅
+     */
+    @Bean
+    public IdentifierGenerator idGenerator() {
+        return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
+    }
+
+    /**
+     * PaginationInnerInterceptor 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+     * https://baomidou.com/pages/97710a/
+     * OptimisticLockerInnerInterceptor 涔愯閿佹彃浠�
+     * https://baomidou.com/pages/0d93c0/
+     * MetaObjectHandler 鍏冨璞″瓧娈靛~鍏呮帶鍒跺櫒
+     * https://baomidou.com/pages/4c6bcf/
+     * ISqlInjector sql娉ㄥ叆鍣�
+     * https://baomidou.com/pages/42ea4a/
+     * BlockAttackInnerInterceptor 濡傛灉鏄鍏ㄨ〃鐨勫垹闄ゆ垨鏇存柊鎿嶄綔锛屽氨浼氱粓姝㈣鎿嶄綔
+     * https://baomidou.com/pages/f9a237/
+     * IllegalSQLInnerInterceptor sql鎬ц兘瑙勮寖鎻掍欢(鍨冨溇SQL鎷︽埅)
+     * IdentifierGenerator 鑷畾涔変富閿瓥鐣�
+     * https://baomidou.com/pages/568eb2/
+     * TenantLineInnerInterceptor 澶氱鎴锋彃浠�
+     * https://baomidou.com/pages/aef2f2/
+     * DynamicTableNameInnerInterceptor 鍔ㄦ�佽〃鍚嶆彃浠�
+     * https://baomidou.com/pages/2a45ff/
+     */
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java
new file mode 100644
index 0000000..ca2dbc6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/FormConf.java
@@ -0,0 +1,69 @@
+package org.ruoyi.flowable.core;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 琛ㄥ崟灞炴�х被
+ *
+ * @author KonBAI
+ * @createTime 2022/8/6 18:54
+ */
+@Data
+public class FormConf {
+
+    /**
+     * 鏍囬
+     */
+    private String title;
+    /**
+     * 琛ㄥ崟鍚�
+     */
+    private String formRef;
+    /**
+     * 琛ㄥ崟妯″瀷
+     */
+    private Map<String, Object>  formModel;
+    /**
+     * 琛ㄥ崟灏哄
+     */
+    private String size;
+    /**
+     * 鏍囩瀵归綈
+     */
+    private String labelPosition;
+    /**
+     * 鏍囩瀹藉害
+     */
+    private Integer labelWidth;
+    /**
+     * 鏍¢獙妯″瀷
+     */
+    private String formRules;
+    /**
+     * 鏍呮牸闂撮殧
+     */
+    private Integer gutter;
+    /**
+     * 绂佺敤琛ㄥ崟
+     */
+    private Boolean disabled = false;
+    /**
+     * 鏍呮牸鍗犳嵁鐨勫垪鏁�
+     */
+    private Integer span;
+    /**
+     * 琛ㄥ崟鎸夐挳
+     */
+    private Boolean formBtns = true;
+    /**
+     * 琛ㄥ崟椤�
+     */
+    private List<Map<String, Object>> fields;
+    /**
+     * 琛ㄥ崟鏁版嵁
+     */
+    private Map<String, Object> formData;
+}
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java
new file mode 100644
index 0000000..6d6077c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/ProcessQuery.java
@@ -0,0 +1,41 @@
+package org.ruoyi.flowable.core.domain;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 娴佺▼鏌ヨ瀹炰綋瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/6/11 01:15
+ */
+@Data
+public class ProcessQuery {
+
+    /**
+     * 娴佺▼鏍囪瘑
+     */
+    private String processKey;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    private String processName;
+
+    /**
+     * 娴佺▼鍒嗙被
+     */
+    private String category;
+
+    /**
+     * 鐘舵��
+     */
+    private String state;
+
+    /**
+     * 璇锋眰鍙傛暟
+     */
+    private Map<String, Object> params = new HashMap<>();
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java
new file mode 100644
index 0000000..a2eaa8a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/domain/model/PageQuery.java
@@ -0,0 +1,112 @@
+package org.ruoyi.flowable.core.domain.model;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.Data;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.core.utils.sql.SqlUtil;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍒嗛〉鏌ヨ瀹炰綋绫�
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class PageQuery implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鍒嗛〉澶у皬
+     */
+    private Integer pageSize;
+
+    /**
+     * 褰撳墠椤垫暟
+     */
+    private Integer pageNum;
+
+    /**
+     * 鎺掑簭鍒�
+     */
+    private String orderByColumn;
+
+    /**
+     * 鎺掑簭鐨勬柟鍚慸esc鎴栬�卆sc
+     */
+    private String isAsc;
+
+    /**
+     * 褰撳墠璁板綍璧峰绱㈠紩 榛樿鍊�
+     */
+    public static final int DEFAULT_PAGE_NUM = 1;
+
+    /**
+     * 姣忛〉鏄剧ず璁板綍鏁� 榛樿鍊� 榛樿鏌ュ叏閮�
+     */
+    public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
+
+    public <T> Page<T> build() {
+        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
+        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
+        if (pageNum <= 0) {
+            pageNum = DEFAULT_PAGE_NUM;
+        }
+        Page<T> page = new Page<>(pageNum, pageSize);
+        List<OrderItem> orderItems = buildOrderItem();
+        if (CollUtil.isNotEmpty(orderItems)) {
+            page.addOrder(orderItems);
+        }
+        return page;
+    }
+
+    /**
+     * 鏋勫缓鎺掑簭
+     *
+     * 鏀寔鐨勭敤娉曞涓�:
+     * {isAsc:"asc",orderByColumn:"id"} order by id asc
+     * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
+     * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
+     * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
+     */
+    private List<OrderItem> buildOrderItem() {
+        if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
+            return null;
+        }
+        String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
+        orderBy = StringUtils.toUnderScoreCase(orderBy);
+
+        // 鍏煎鍓嶇鎺掑簭绫诲瀷
+        isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
+
+        String[] orderByArr = orderBy.split(",");
+        String[] isAscArr = isAsc.split(",");
+        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
+            throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+        }
+
+        List<OrderItem> list = new ArrayList<>();
+        // 姣忎釜瀛楁鍚勮嚜鎺掑簭
+        for (int i = 0; i < orderByArr.length; i++) {
+            String orderByStr = orderByArr[i];
+            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
+            if ("asc".equals(isAscStr)) {
+                list.add(OrderItem.asc(orderByStr));
+            } else if ("desc".equals(isAscStr)) {
+                list.add(OrderItem.desc(orderByStr));
+            } else {
+                throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+            }
+        }
+        return list;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java
new file mode 100644
index 0000000..4c49694
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/mapper/BaseMapperPlus.java
@@ -0,0 +1,192 @@
+package org.ruoyi.flowable.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.ruoyi.flowable.utils.BeanCopyUtils;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鑷畾涔� Mapper 鎺ュ彛, 瀹炵幇 鑷畾涔夋墿灞�
+ *
+ * @param <M> mapper 娉涘瀷
+ * @param <T> table 娉涘瀷
+ * @param <V> vo 娉涘瀷
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
+
+    Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+    default Class<V> currentVoClass() {
+        return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
+    }
+
+    default Class<T> currentModelClass() {
+        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
+    }
+
+    default Class<M> currentMapperClass() {
+        return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
+    }
+
+    default List<T> selectList() {
+        return this.selectList(new QueryWrapper<>());
+    }
+
+    /**
+     * 鎵归噺鎻掑叆
+     */
+    default boolean insertBatch(Collection<T> entityList) {
+        return Db.saveBatch(entityList);
+    }
+
+    /**
+     * 鎵归噺鏇存柊
+     */
+    default boolean updateBatchById(Collection<T> entityList) {
+        return Db.updateBatchById(entityList);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆鎴栨洿鏂�
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList) {
+        return Db.saveOrUpdateBatch(entityList);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveBatch(entityList, batchSize);
+    }
+
+    /**
+     * 鎵归噺鏇存柊(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
+        return Db.updateBatchById(entityList, batchSize);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆鎴栨洿鏂�(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveOrUpdateBatch(entityList, batchSize);
+    }
+
+    /**
+     * 鎻掑叆鎴栨洿鏂�(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertOrUpdate(T entity) {
+        return Db.saveOrUpdate(entity);
+    }
+
+    default V selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 ID 鏌ヨ
+     */
+    default <C> C selectVoById(Serializable id, Class<C> voClass) {
+        T obj = this.selectById(id);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return BeanCopyUtils.copy(obj, voClass);
+    }
+
+    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
+        return selectVoBatchIds(idList, this.currentVoClass());
+    }
+
+    /**
+     * 鏌ヨ锛堟牴鎹甀D 鎵归噺鏌ヨ锛�
+     */
+    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
+        List<T> list = this.selectBatchIds(idList);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default List<V> selectVoByMap(Map<String, Object> map) {
+        return selectVoByMap(map, this.currentVoClass());
+    }
+
+    /**
+     * 鏌ヨ锛堟牴鎹� columnMap 鏉′欢锛�
+     */
+    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
+        List<T> list = this.selectByMap(map);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default V selectVoOne(Wrapper<T> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 entity 鏉′欢锛屾煡璇竴鏉¤褰�
+     */
+    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
+        T obj = this.selectOne(wrapper);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return BeanCopyUtils.copy(obj, voClass);
+    }
+
+    default List<V> selectVoList(Wrapper<T> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 entity 鏉′欢锛屾煡璇㈠叏閮ㄨ褰�
+     */
+    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return BeanCopyUtils.copyList(list, voClass);
+    }
+
+    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨVO
+     */
+    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
+        IPage<T> pageData = this.selectPage(page, wrapper);
+        IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
+        if (CollUtil.isEmpty(pageData.getRecords())) {
+            return (P) voPage;
+        }
+        voPage.setRecords(BeanCopyUtils.copyList(pageData.getRecords(), voClass));
+        return (P) voPage;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java
new file mode 100644
index 0000000..b555389
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/core/page/TableDataInfo.java
@@ -0,0 +1,78 @@
+package org.ruoyi.flowable.core.page;
+
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class TableDataInfo<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎬昏褰曟暟
+     */
+    private long total;
+
+    /**
+     * 鍒楄〃鏁版嵁
+     */
+    private List<T> rows;
+
+    /**
+     * 娑堟伅鐘舵�佺爜
+     */
+    private int code;
+
+    /**
+     * 娑堟伅鍐呭
+     */
+    private String msg;
+
+    /**
+     * 鍒嗛〉
+     *
+     * @param list  鍒楄〃鏁版嵁
+     * @param total 鎬昏褰曟暟
+     */
+    public TableDataInfo(List<T> list, long total) {
+        this.rows = list;
+        this.total = total;
+    }
+
+    public static <T> TableDataInfo<T> build(IPage<T> page) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        rspData.setRows(page.getRecords());
+        rspData.setTotal(page.getTotal());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build(List<T> list) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        rspData.setRows(list);
+        rspData.setTotal(list.size());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build() {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        return rspData;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java
new file mode 100644
index 0000000..706aaa5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/factory/FlowServiceFactory.java
@@ -0,0 +1,43 @@
+package org.ruoyi.flowable.factory;
+
+import jakarta.annotation.Resource;
+import lombok.Getter;
+import org.flowable.engine.*;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+/**
+ * flowable 寮曟搸娉ㄥ叆灏佽
+ * @author XuanXuan
+ * @date 2021-04-03
+ */
+@Component
+@Getter
+public class FlowServiceFactory {
+
+    @Resource
+    protected RepositoryService repositoryService;
+
+    @Resource
+    protected RuntimeService runtimeService;
+
+    @Resource
+    protected IdentityService identityService;
+
+    @Resource
+    protected TaskService taskService;
+
+    @Resource
+    protected FormService formService;
+
+    @Resource
+    protected HistoryService historyService;
+
+    @Resource
+    protected ManagementService managementService;
+
+    @Qualifier("processEngine")
+    @Resource
+    protected ProcessEngine processEngine;
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java
new file mode 100644
index 0000000..84417b9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramCanvas.java
@@ -0,0 +1,370 @@
+package org.ruoyi.flowable.flow;
+
+import org.flowable.bpmn.model.AssociationDirection;
+import org.flowable.bpmn.model.GraphicInfo;
+import org.flowable.image.impl.DefaultProcessDiagramCanvas;
+import org.flowable.image.util.ReflectUtil;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+
+/**
+ * @author XuanXuan
+ * @date 2021/4/4 23:58
+ */
+public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
+    //瀹氫箟璧拌繃娴佺▼杩炵嚎棰滆壊涓虹豢鑹�
+    protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN;
+    //璁剧疆鏈蛋杩囨祦绋嬬殑杩炴帴绾块鑹�
+    protected static Color CONNECTION_COLOR = Color.BLACK;
+    //璁剧疆flows杩炴帴绾垮瓧浣撻鑹瞨ed
+    protected static Color LABEL_COLOR = new Color(0, 0, 0);
+    //楂樹寒鏄剧ずtask妗嗛鑹�
+    protected static Color HIGHLIGHT_COLOR = Color.GREEN;
+    protected static Color HIGHLIGHT_COLOR1 = Color.RED;
+
+    public CustomProcessDiagramCanvas(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);
+        this.initialize(imageType);
+    }
+
+    /**
+     * 閲嶅啓缁樺埗杩炵嚎鐨勬柟寮�,璁剧疆缁樺埗棰滆壊
+     * @param xPoints
+     * @param yPoints
+     * @param conditional
+     * @param isDefault
+     * @param connectionType
+     * @param associationDirection
+     * @param highLighted
+     * @param scaleFactor
+     */
+    @Override
+    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
+        Paint originalPaint = this.g.getPaint();
+        Stroke originalStroke = this.g.getStroke();
+        this.g.setPaint(CONNECTION_COLOR);
+        if (connectionType.equals("association")) {
+            this.g.setStroke(ASSOCIATION_STROKE);
+        } else if (highLighted) {
+            this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR);
+            this.g.setStroke(HIGHLIGHT_FLOW_STROKE);
+        }
+
+        for (int i = 1; i < xPoints.length; ++i) {
+            int sourceX = xPoints[i - 1];
+            int sourceY = yPoints[i - 1];
+            int targetX = xPoints[i];
+            int targetY = yPoints[i];
+            java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, (double) sourceY, (double) targetX, (double) targetY);
+            this.g.draw(line);
+        }
+
+        java.awt.geom.Line2D.Double line;
+        if (isDefault) {
+            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
+            this.drawDefaultSequenceFlowIndicator(line, scaleFactor);
+        }
+
+        if (conditional) {
+            line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
+            this.drawConditionalSequenceFlowIndicator(line, scaleFactor);
+        }
+
+        if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
+            line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], (double) yPoints[xPoints.length - 1]);
+            this.drawArrowHead(line, scaleFactor);
+        }
+
+        if (associationDirection.equals(AssociationDirection.BOTH)) {
+            line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], (double) xPoints[0], (double) yPoints[0]);
+            this.drawArrowHead(line, scaleFactor);
+        }
+
+        this.g.setPaint(originalPaint);
+        this.g.setStroke(originalStroke);
+    }
+
+    /**
+     * 璁剧疆瀛椾綋澶у皬鍥炬爣棰滆壊
+     * @param imageType
+     */
+    @Override
+    public void initialize(String imageType) {
+        if ("png".equalsIgnoreCase(imageType)) {
+            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2);
+        } else {
+            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1);
+        }
+
+        this.g = this.processDiagram.createGraphics();
+        if (!"png".equalsIgnoreCase(imageType)) {
+            this.g.setBackground(new Color(255, 255, 255, 0));
+            this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+        }
+
+        this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        //淇敼鍥炬爣棰滆壊锛屼慨鏀瑰浘鏍囧瓧浣撳ぇ灏�
+        this.g.setPaint(Color.black);
+        Font font = new Font(this.activityFontName, 10, 14);
+        this.g.setFont(font);
+        this.fontMetrics = this.g.getFontMetrics();
+        //淇敼杩炴帴绾垮瓧浣撳ぇ灏�
+        LABEL_FONT = new Font(this.labelFontName, 10, 15);
+        ANNOTATION_FONT = new Font(this.annotationFontName, 0, 11);
+
+        try {
+            USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png", this.customClassLoader));
+            SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", this.customClassLoader));
+            SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", this.customClassLoader));
+            RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", this.customClassLoader));
+            SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", this.customClassLoader));
+            MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", this.customClassLoader));
+            BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", this.customClassLoader));
+            SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", this.customClassLoader));
+            DMN_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png", this.customClassLoader));
+            CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", this.customClassLoader));
+            MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", this.customClassLoader));
+            HTTP_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/httpTask.png", this.customClassLoader));
+            TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", this.customClassLoader));
+            COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", this.customClassLoader));
+            COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", this.customClassLoader));
+            ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", this.customClassLoader));
+            ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", this.customClassLoader));
+            MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", this.customClassLoader));
+            MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", this.customClassLoader));
+            SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", this.customClassLoader));
+            SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", this.customClassLoader));
+        } catch (IOException var4) {
+            LOGGER.warn("Could not load image for process diagram creation: {}", var4.getMessage());
+        }
+
+    }
+
+    /**
+     * 璁剧疆杩炴帴绾垮瓧浣�
+     * @param text
+     * @param graphicInfo
+     * @param centered
+     */
+    @Override
+    public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) {
+        float interline = 1.0f;
+
+        // text
+        if (text != null && text.length() > 0) {
+            Paint originalPaint = g.getPaint();
+            Font originalFont = g.getFont();
+
+            g.setPaint(LABEL_COLOR);
+            g.setFont(LABEL_FONT);
+
+            int wrapWidth = 100;
+            int textY = (int) graphicInfo.getY();
+
+            // TODO: use drawMultilineText()
+            AttributedString as = new AttributedString(text);
+            as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
+            as.addAttribute(TextAttribute.FONT, g.getFont());
+            AttributedCharacterIterator aci = as.getIterator();
+            FontRenderContext frc = new FontRenderContext(null, true, false);
+            LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
+
+            while (lbm.getPosition() < text.length()) {
+                TextLayout tl = lbm.nextLayout(wrapWidth);
+                textY += tl.getAscent();
+
+                Rectangle2D bb = tl.getBounds();
+                double tX = graphicInfo.getX();
+
+                if (centered) {
+                    tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
+                }
+                tl.draw(g, (float) tX, textY);
+                textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
+            }
+
+            // restore originals
+            g.setFont(originalFont);
+            g.setPaint(originalPaint);
+        }
+    }
+
+    /**
+     * 楂樹寒鏄剧ずtask妗嗗畬鎴愮殑
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     */
+    @Override
+    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);
+    }
+
+    /**
+     * 鑷畾涔塼ask妗嗗綋鍓嶇殑浣嶇疆
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     */
+    public void drawHighLightNow(int x, int y, int width, int height) {
+        Paint originalPaint = g.getPaint();
+        Stroke originalStroke = g.getStroke();
+
+        g.setPaint(HIGHLIGHT_COLOR1);
+        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);
+    }
+
+    /**
+     * 鑷畾涔夌粨鏉熻妭鐐�
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     */
+    public void drawHighLightEnd(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);
+    }
+
+    /**
+     * task妗嗚嚜瀹氫箟鏂囧瓧
+     * @param name
+     * @param graphicInfo
+     * @param thickBorder
+     * @param scaleFactor
+     */
+    @Override
+    protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) {
+
+        Paint originalPaint = g.getPaint();
+        int x = (int) graphicInfo.getX();
+        int y = (int) graphicInfo.getY();
+        int width = (int) graphicInfo.getWidth();
+        int height = (int) graphicInfo.getHeight();
+
+        // Create a new gradient paint for every task box, gradient depends on x and y and is not relative
+        g.setPaint(TASK_BOX_COLOR);
+
+        int arcR = 6;
+        if (thickBorder) {
+            arcR = 3;
+        }
+
+        // shape
+        RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR);
+        g.fill(rect);
+        g.setPaint(TASK_BORDER_COLOR);
+
+        if (thickBorder) {
+            Stroke originalStroke = g.getStroke();
+            g.setStroke(THICK_TASK_BORDER_STROKE);
+            g.draw(rect);
+            g.setStroke(originalStroke);
+        } else {
+            g.draw(rect);
+        }
+
+        g.setPaint(originalPaint);
+        // text
+        if (scaleFactor == 1.0 && name != null && name.length() > 0) {
+            int boxWidth = width - (2 * TEXT_PADDING);
+            int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
+            int boxX = x + width / 2 - boxWidth / 2;
+            int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;
+
+            drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight);
+        }
+    }
+
+    protected static Color EVENT_COLOR = new Color(255, 255, 255);
+
+    /**
+     * 閲嶅啓寮�濮嬩簨浠�
+     * @param graphicInfo
+     * @param image
+     * @param scaleFactor
+     */
+    @Override
+    public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) {
+        Paint originalPaint = g.getPaint();
+        g.setPaint(EVENT_COLOR);
+        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
+                graphicInfo.getWidth(), graphicInfo.getHeight());
+        g.fill(circle);
+        g.setPaint(EVENT_BORDER_COLOR);
+        g.draw(circle);
+        g.setPaint(originalPaint);
+        if (image != null) {
+            // calculate coordinates to center image
+            int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / (2 * scaleFactor)));
+            int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / (2 * scaleFactor)));
+            g.drawImage(image, imageX, imageY,
+                    (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
+        }
+
+    }
+
+    /**
+     * 閲嶅啓缁撴潫浜嬩欢
+     * @param graphicInfo
+     * @param scaleFactor
+     */
+    @Override
+    public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
+        Paint originalPaint = g.getPaint();
+        Stroke originalStroke = g.getStroke();
+        g.setPaint(EVENT_COLOR);
+        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
+                graphicInfo.getWidth(), graphicInfo.getHeight());
+        g.fill(circle);
+        g.setPaint(EVENT_BORDER_COLOR);
+//        g.setPaint(HIGHLIGHT_COLOR);
+        if (scaleFactor == 1.0) {
+            g.setStroke(END_EVENT_STROKE);
+        } else {
+            g.setStroke(new BasicStroke(2.0f));
+        }
+        g.draw(circle);
+        g.setStroke(originalStroke);
+        g.setPaint(originalPaint);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java
new file mode 100644
index 0000000..6ce490c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java
@@ -0,0 +1,405 @@
+package org.ruoyi.flowable.flow;
+
+
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.image.impl.DefaultProcessDiagramCanvas;
+import org.flowable.image.impl.DefaultProcessDiagramGenerator;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author XuanXuan
+ * @date 2021/4/5 0:31
+ */
+public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator {
+    @Override
+    protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, boolean drawSequenceFlowNameWithNoLabelDI) {
+        this.prepareBpmnModel(bpmnModel);
+        DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
+        Iterator var13 = bpmnModel.getPools().iterator();
+
+        while (var13.hasNext()) {
+            Pool process = (Pool) var13.next();
+            GraphicInfo subProcesses = bpmnModel.getGraphicInfo(process.getId());
+            processDiagramCanvas.drawPoolOrLane(process.getName(), subProcesses, scaleFactor);
+        }
+
+        var13 = bpmnModel.getProcesses().iterator();
+
+        Process process1;
+        Iterator subProcesses1;
+        while (var13.hasNext()) {
+            process1 = (Process) var13.next();
+            subProcesses1 = process1.getLanes().iterator();
+
+            while (subProcesses1.hasNext()) {
+                Lane artifact = (Lane) subProcesses1.next();
+                GraphicInfo subProcess = bpmnModel.getGraphicInfo(artifact.getId());
+                processDiagramCanvas.drawPoolOrLane(artifact.getName(), subProcess, scaleFactor);
+            }
+        }
+
+        var13 = bpmnModel.getProcesses().iterator();
+
+        while (var13.hasNext()) {
+            process1 = (Process) var13.next();
+            subProcesses1 = process1.findFlowElementsOfType(FlowNode.class).iterator();
+
+            while (subProcesses1.hasNext()) {
+                FlowNode artifact1 = (FlowNode) subProcesses1.next();
+                if (!this.isPartOfCollapsedSubProcess(artifact1, bpmnModel)) {
+                    this.drawActivity(processDiagramCanvas, bpmnModel, artifact1, highLightedActivities, highLightedFlows, scaleFactor, Boolean.valueOf(drawSequenceFlowNameWithNoLabelDI));
+                }
+            }
+        }
+
+        var13 = bpmnModel.getProcesses().iterator();
+
+        label75:
+        while (true) {
+            List subProcesses2;
+            do {
+                if (!var13.hasNext()) {
+                    return processDiagramCanvas;
+                }
+
+                process1 = (Process) var13.next();
+                subProcesses1 = process1.getArtifacts().iterator();
+
+                while (subProcesses1.hasNext()) {
+                    Artifact artifact2 = (Artifact) subProcesses1.next();
+                    this.drawArtifact(processDiagramCanvas, bpmnModel, artifact2);
+                }
+
+                subProcesses2 = process1.findFlowElementsOfType(SubProcess.class, true);
+            } while (subProcesses2 == null);
+
+            Iterator artifact3 = subProcesses2.iterator();
+
+            while (true) {
+                GraphicInfo graphicInfo;
+                SubProcess subProcess1;
+                do {
+                    do {
+                        if (!artifact3.hasNext()) {
+                            continue label75;
+                        }
+
+                        subProcess1 = (SubProcess) artifact3.next();
+                        graphicInfo = bpmnModel.getGraphicInfo(subProcess1.getId());
+                    } while (graphicInfo != null && graphicInfo.getExpanded() != null && !graphicInfo.getExpanded().booleanValue());
+                } while (this.isPartOfCollapsedSubProcess(subProcess1, bpmnModel));
+
+                Iterator var19 = subProcess1.getArtifacts().iterator();
+
+                while (var19.hasNext()) {
+                    Artifact subProcessArtifact = (Artifact) var19.next();
+                    this.drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
+                }
+            }
+        }
+    }
+
+    protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
+        double minX = 1.7976931348623157E308D;
+        double maxX = 0.0D;
+        double minY = 1.7976931348623157E308D;
+        double maxY = 0.0D;
+
+        GraphicInfo nrOfLanes;
+        for (Iterator flowNodes = bpmnModel.getPools().iterator(); flowNodes.hasNext(); maxY = nrOfLanes.getY() + nrOfLanes.getHeight()) {
+            Pool artifacts = (Pool) flowNodes.next();
+            nrOfLanes = bpmnModel.getGraphicInfo(artifacts.getId());
+            minX = nrOfLanes.getX();
+            maxX = nrOfLanes.getX() + nrOfLanes.getWidth();
+            minY = nrOfLanes.getY();
+        }
+
+        List var23 = gatherAllFlowNodes(bpmnModel);
+        Iterator var24 = var23.iterator();
+
+        label155:
+        while (var24.hasNext()) {
+            FlowNode var26 = (FlowNode) var24.next();
+            GraphicInfo artifact = bpmnModel.getGraphicInfo(var26.getId());
+            if (artifact.getX() + artifact.getWidth() > maxX) {
+                maxX = artifact.getX() + artifact.getWidth();
+            }
+
+            if (artifact.getX() < minX) {
+                minX = artifact.getX();
+            }
+
+            if (artifact.getY() + artifact.getHeight() > maxY) {
+                maxY = artifact.getY() + artifact.getHeight();
+            }
+
+            if (artifact.getY() < minY) {
+                minY = artifact.getY();
+            }
+
+            Iterator process = var26.getOutgoingFlows().iterator();
+
+            while (true) {
+                List l;
+                do {
+                    if (!process.hasNext()) {
+                        continue label155;
+                    }
+
+                    SequenceFlow graphicInfoList = (SequenceFlow) process.next();
+                    l = bpmnModel.getFlowLocationGraphicInfo(graphicInfoList.getId());
+                } while (l == null);
+
+                Iterator graphicInfo = l.iterator();
+
+                while (graphicInfo.hasNext()) {
+                    GraphicInfo graphicInfo1 = (GraphicInfo) graphicInfo.next();
+                    if (graphicInfo1.getX() > maxX) {
+                        maxX = graphicInfo1.getX();
+                    }
+
+                    if (graphicInfo1.getX() < minX) {
+                        minX = graphicInfo1.getX();
+                    }
+
+                    if (graphicInfo1.getY() > maxY) {
+                        maxY = graphicInfo1.getY();
+                    }
+
+                    if (graphicInfo1.getY() < minY) {
+                        minY = graphicInfo1.getY();
+                    }
+                }
+            }
+        }
+
+        List var25 = gatherAllArtifacts(bpmnModel);
+        Iterator var27 = var25.iterator();
+
+        GraphicInfo var37;
+        while (var27.hasNext()) {
+            Artifact var29 = (Artifact) var27.next();
+            GraphicInfo var31 = bpmnModel.getGraphicInfo(var29.getId());
+            if (var31 != null) {
+                if (var31.getX() + var31.getWidth() > maxX) {
+                    maxX = var31.getX() + var31.getWidth();
+                }
+
+                if (var31.getX() < minX) {
+                    minX = var31.getX();
+                }
+
+                if (var31.getY() + var31.getHeight() > maxY) {
+                    maxY = var31.getY() + var31.getHeight();
+                }
+
+                if (var31.getY() < minY) {
+                    minY = var31.getY();
+                }
+            }
+
+            List var33 = bpmnModel.getFlowLocationGraphicInfo(var29.getId());
+            if (var33 != null) {
+                Iterator var35 = var33.iterator();
+
+                while (var35.hasNext()) {
+                    var37 = (GraphicInfo) var35.next();
+                    if (var37.getX() > maxX) {
+                        maxX = var37.getX();
+                    }
+
+                    if (var37.getX() < minX) {
+                        minX = var37.getX();
+                    }
+
+                    if (var37.getY() > maxY) {
+                        maxY = var37.getY();
+                    }
+
+                    if (var37.getY() < minY) {
+                        minY = var37.getY();
+                    }
+                }
+            }
+        }
+
+        int var28 = 0;
+        Iterator var30 = bpmnModel.getProcesses().iterator();
+
+        while (var30.hasNext()) {
+            Process var32 = (Process) var30.next();
+            Iterator var34 = var32.getLanes().iterator();
+
+            while (var34.hasNext()) {
+                Lane var36 = (Lane) var34.next();
+                ++var28;
+                var37 = bpmnModel.getGraphicInfo(var36.getId());
+                if (var37.getX() + var37.getWidth() > maxX) {
+                    maxX = var37.getX() + var37.getWidth();
+                }
+
+                if (var37.getX() < minX) {
+                    minX = var37.getX();
+                }
+
+                if (var37.getY() + var37.getHeight() > maxY) {
+                    maxY = var37.getY() + var37.getHeight();
+                }
+
+                if (var37.getY() < minY) {
+                    minY = var37.getY();
+                }
+            }
+        }
+
+        if (var23.isEmpty() && bpmnModel.getPools().isEmpty() && var28 == 0) {
+            minX = 0.0D;
+            minY = 0.0D;
+        }
+
+        return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
+    }
+
+
+    private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
+        processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
+
+    }
+
+    private static void drawHighLightNow(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
+        processDiagramCanvas.drawHighLightNow((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
+
+    }
+
+    private static void drawHighLightEnd(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) {
+        processDiagramCanvas.drawHighLightEnd((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight());
+
+    }
+
+    @Override
+    protected void drawActivity(DefaultProcessDiagramCanvas 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())) {
+
+                if (highLightedActivities.get(highLightedActivities.size() - 1).equals(flowNode.getId())
+                        && !"endenv".equals(flowNode.getId())) {
+                    if ((flowNode.getId().contains("Event_"))) {
+                        drawHighLightEnd((CustomProcessDiagramCanvas) processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+                    } else {
+                        drawHighLightNow((CustomProcessDiagramCanvas) processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()));
+                    }
+                } else {
+                    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 && !(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);
+                }
+            }
+        }
+    }
+}
+
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java
new file mode 100644
index 0000000..dd651fe
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FindNextNodeUtil.java
@@ -0,0 +1,222 @@
+package org.ruoyi.flowable.flow;
+
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.ProcessDefinition;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Xuan xuan
+ * @date 2021/4/19 20:51
+ */
+public class FindNextNodeUtil {
+
+    /**
+     * 鑾峰彇涓嬩竴姝ラ鐨勭敤鎴蜂换鍔�
+     *
+     * @param repositoryService
+     * @param map
+     * @return
+     */
+    public static List<UserTask> getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, Map<String, Object> map) {
+        List<UserTask> data = new ArrayList<>();
+        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
+        Process mainProcess = bpmnModel.getMainProcess();
+        Collection<FlowElement> flowElements = mainProcess.getFlowElements();
+        String key = task.getTaskDefinitionKey();
+        FlowElement flowElement = bpmnModel.getFlowElement(key);
+        next(flowElements, flowElement, map, data);
+        return data;
+    }
+
+    public static void next(Collection<FlowElement> flowElements, FlowElement flowElement, Map<String, Object> map, List<UserTask> nextUser) {
+        //濡傛灉鏄粨鏉熻妭鐐�
+        if (flowElement instanceof EndEvent) {
+            //濡傛灉鏄瓙浠诲姟鐨勭粨鏉熻妭鐐�
+            if (getSubProcess(flowElements, flowElement) != null) {
+                flowElement = getSubProcess(flowElements, flowElement);
+            }
+        }
+        //鑾峰彇Task鐨勫嚭绾夸俊鎭�--鍙互鎷ユ湁澶氫釜
+        List<SequenceFlow> outGoingFlows = null;
+        if (flowElement instanceof Task) {
+            outGoingFlows = ((Task) flowElement).getOutgoingFlows();
+        } else if (flowElement instanceof Gateway) {
+            outGoingFlows = ((Gateway) flowElement).getOutgoingFlows();
+        } else if (flowElement instanceof StartEvent) {
+            outGoingFlows = ((StartEvent) flowElement).getOutgoingFlows();
+        } else if (flowElement instanceof SubProcess) {
+            outGoingFlows = ((SubProcess) flowElement).getOutgoingFlows();
+        } else if (flowElement instanceof CallActivity) {
+            outGoingFlows = ((CallActivity) flowElement).getOutgoingFlows();
+        }
+        if (outGoingFlows != null && outGoingFlows.size() > 0) {
+            //閬嶅巻鎵�鏈夌殑鍑虹嚎--鎵惧埌鍙互姝g‘鎵ц鐨勯偅涓�鏉�
+            for (SequenceFlow sequenceFlow : outGoingFlows) {
+                //1.鏈夎〃杈惧紡锛屼笖涓簍rue
+                //2.鏃犺〃杈惧紡
+                String expression = sequenceFlow.getConditionExpression();
+                if (expression == null || expressionResult(map, expression.substring(expression.lastIndexOf("{") + 1, expression.lastIndexOf("}")))) {
+                    //鍑虹嚎鐨勪笅涓�鑺傜偣
+                    String nextFlowElementID = sequenceFlow.getTargetRef();
+                    if (checkSubProcess(nextFlowElementID, flowElements, nextUser)) {
+                        continue;
+                    }
+
+                    //鏌ヨ涓嬩竴鑺傜偣鐨勪俊鎭�
+                    FlowElement nextFlowElement = getFlowElementById(nextFlowElementID, flowElements);
+                    //璋冪敤娴佺▼
+                    if (nextFlowElement instanceof CallActivity) {
+                        CallActivity ca = (CallActivity) nextFlowElement;
+                        if (ca.getLoopCharacteristics() != null) {
+                            UserTask userTask = new UserTask();
+                            userTask.setId(ca.getId());
+
+                            userTask.setId(ca.getId());
+                            userTask.setLoopCharacteristics(ca.getLoopCharacteristics());
+                            userTask.setName(ca.getName());
+                            nextUser.add(userTask);
+                        }
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //鐢ㄦ埛浠诲姟
+                    if (nextFlowElement instanceof UserTask) {
+                        nextUser.add((UserTask) nextFlowElement);
+                    }
+                    //鎺掍粬缃戝叧
+                    else if (nextFlowElement instanceof ExclusiveGateway) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //骞惰缃戝叧
+                    else if (nextFlowElement instanceof ParallelGateway) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //鎺ユ敹浠诲姟
+                    else if (nextFlowElement instanceof ReceiveTask) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //鏈嶅姟浠诲姟
+                    else if (nextFlowElement instanceof ServiceTask) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //瀛愪换鍔$殑璧风偣
+                    else if (nextFlowElement instanceof StartEvent) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                    //缁撴潫鑺傜偣
+                    else if (nextFlowElement instanceof EndEvent) {
+                        next(flowElements, nextFlowElement, map, nextUser);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁鏄瀹炰緥瀛愭祦绋嬪苟涓旈渶瑕佽缃泦鍚堢被鍨嬪彉閲�
+     */
+    public static boolean checkSubProcess(String Id, Collection<FlowElement> flowElements, List<UserTask> nextUser) {
+        for (FlowElement flowElement1 : flowElements) {
+            if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(Id)) {
+
+                SubProcess sp = (SubProcess) flowElement1;
+                if (sp.getLoopCharacteristics() != null) {
+                    String inputDataItem = sp.getLoopCharacteristics().getInputDataItem();
+                    UserTask userTask = new UserTask();
+                    userTask.setId(sp.getId());
+                    userTask.setLoopCharacteristics(sp.getLoopCharacteristics());
+                    userTask.setName(sp.getName());
+                    nextUser.add(userTask);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * 鏌ヨ涓�涓妭鐐圭殑鏄惁瀛愪换鍔′腑鐨勮妭鐐癸紝濡傛灉鏄紝杩斿洖瀛愪换鍔�
+     *
+     * @param flowElements 鍏ㄦ祦绋嬬殑鑺傜偣闆嗗悎
+     * @param flowElement  褰撳墠鑺傜偣
+     * @return
+     */
+    public static FlowElement getSubProcess(Collection<FlowElement> flowElements, FlowElement flowElement) {
+        for (FlowElement flowElement1 : flowElements) {
+            if (flowElement1 instanceof SubProcess) {
+                for (FlowElement flowElement2 : ((SubProcess) flowElement1).getFlowElements()) {
+                    if (flowElement.equals(flowElement2)) {
+                        return flowElement1;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 鏍规嵁ID鏌ヨ娴佺▼鑺傜偣瀵硅薄, 濡傛灉鏄瓙浠诲姟锛屽垯杩斿洖瀛愪换鍔$殑寮�濮嬭妭鐐�
+     *
+     * @param Id           鑺傜偣ID
+     * @param flowElements 娴佺▼鑺傜偣闆嗗悎
+     * @return
+     */
+    public static FlowElement getFlowElementById(String Id, Collection<FlowElement> flowElements) {
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement.getId().equals(Id)) {
+                //濡傛灉鏄瓙浠诲姟锛屽垯鏌ヨ鍑哄瓙浠诲姟鐨勫紑濮嬭妭鐐�
+                if (flowElement instanceof SubProcess) {
+                    return getStartFlowElement(((SubProcess) flowElement).getFlowElements());
+                }
+                return flowElement;
+            }
+            if (flowElement instanceof SubProcess) {
+                FlowElement flowElement1 = getFlowElementById(Id, ((SubProcess) flowElement).getFlowElements());
+                if (flowElement1 != null) {
+                    return flowElement1;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 杩斿洖娴佺▼鐨勫紑濮嬭妭鐐�
+     *
+     * @param flowElements 鑺傜偣闆嗗悎
+     * @description:
+     */
+    public static FlowElement getStartFlowElement(Collection<FlowElement> flowElements) {
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof StartEvent) {
+                return flowElement;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鏍¢獙el琛ㄨ揪寮�
+     *
+     * @param map
+     * @param expression
+     * @return
+     */
+    public static boolean expressionResult(Map<String, Object> map, String expression) {
+        Expression exp = AviatorEvaluator.compile(expression);
+        final Object execute = exp.execute(map);
+        return Boolean.parseBoolean(String.valueOf(execute));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java
new file mode 100644
index 0000000..62eaa14
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableConfig.java
@@ -0,0 +1,24 @@
+package org.ruoyi.flowable.flow;
+
+import org.flowable.spring.SpringProcessEngineConfiguration;
+import org.flowable.spring.boot.EngineConfigurationConfigurer;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * @author XuanXuan
+ * @date 2021/4/5 01:32
+ */
+@Configuration
+public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
+
+    @Override
+    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
+        engineConfiguration.setActivityFontName("瀹嬩綋");
+        engineConfiguration.setLabelFontName("瀹嬩綋");
+        engineConfiguration.setAnnotationFontName("瀹嬩綋");
+
+    }
+}
+
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java
new file mode 100644
index 0000000..16892d5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/flow/FlowableUtils.java
@@ -0,0 +1,706 @@
+package org.ruoyi.flowable.flow;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import org.ruoyi.common.core.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.*;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.flowable.task.api.history.HistoricTaskInstance;
+
+import java.util.*;
+
+/**
+ * @author XuanXuan
+ * @date 2021-04-03 23:57
+ */
+@Slf4j
+public class FlowableUtils {
+
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧叆鍙h繛绾�
+     * @param source
+     * @return
+     */
+    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
+        List<SequenceFlow> sequenceFlows = null;
+        if (source instanceof FlowNode) {
+            sequenceFlows = ((FlowNode) source).getIncomingFlows();
+        } else if (source instanceof Gateway) {
+            sequenceFlows = ((Gateway) source).getIncomingFlows();
+        } else if (source instanceof SubProcess) {
+            sequenceFlows = ((SubProcess) source).getIncomingFlows();
+        } else if (source instanceof StartEvent) {
+            sequenceFlows = ((StartEvent) source).getIncomingFlows();
+        } else if (source instanceof EndEvent) {
+            sequenceFlows = ((EndEvent) source).getIncomingFlows();
+        }
+        return sequenceFlows;
+    }
+
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧嚭鍙h繛绾�
+     * @param source
+     * @return
+     */
+    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
+        List<SequenceFlow> sequenceFlows = null;
+        if (source instanceof FlowNode) {
+            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
+        } else if (source instanceof Gateway) {
+            sequenceFlows = ((Gateway) source).getOutgoingFlows();
+        } else if (source instanceof SubProcess) {
+            sequenceFlows = ((SubProcess) source).getOutgoingFlows();
+        } else if (source instanceof StartEvent) {
+            sequenceFlows = ((StartEvent) source).getOutgoingFlows();
+        } else if (source instanceof EndEvent) {
+            sequenceFlows = ((EndEvent) source).getOutgoingFlows();
+        }
+        return sequenceFlows;
+    }
+
+    /**
+     * 鑾峰彇鍏ㄩ儴鑺傜偣鍒楄〃锛屽寘鍚瓙娴佺▼鑺傜偣
+     * @param flowElements
+     * @param allElements
+     * @return
+     */
+    public static Collection<FlowElement> getAllElements(Collection<FlowElement> flowElements, Collection<FlowElement> allElements) {
+        allElements = allElements == null ? new ArrayList<>() : allElements;
+
+        for (FlowElement flowElement : flowElements) {
+            allElements.add(flowElement);
+            if (flowElement instanceof SubProcess) {
+                // 缁х画娣卞叆瀛愭祦绋嬶紝杩涗竴姝ヨ幏鍙栧瓙娴佺▼
+                allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements);
+            }
+        }
+        return allElements;
+    }
+
+    /**
+     * 杩唬鑾峰彇鐖剁骇浠诲姟鑺傜偣鍒楄〃锛屽悜鍓嶆壘
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param userTaskList 宸叉壘鍒扮殑鐢ㄦ埛浠诲姟鑺傜偣
+     * @return
+     */
+    public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+        // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 绫诲瀷涓虹敤鎴疯妭鐐癸紝鍒欐柊澧炵埗绾ц妭鐐�
+                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
+                    userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
+                    continue;
+                }
+                // 绫诲瀷涓哄瓙娴佺▼锛屽垯娣诲姞瀛愭祦绋嬪紑濮嬭妭鐐瑰嚭鍙e鐩歌繛鐨勮妭鐐�
+                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
+                    // 鑾峰彇瀛愭祦绋嬬敤鎴蜂换鍔¤妭鐐�
+                    List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
+                    // 濡傛灉鎵惧埌鑺傜偣锛屽垯璇存槑璇ョ嚎璺壘鍒拌妭鐐癸紝涓嶇户缁悜涓嬫壘锛屽弽涔嬬户缁�
+                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
+                        userTaskList.addAll(childUserTaskList);
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
+            }
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 鏍规嵁姝e湪杩愯鐨勪换鍔¤妭鐐癸紝杩唬鑾峰彇瀛愮骇浠诲姟鑺傜偣鍒楄〃锛屽悜鍚庢壘
+     * @param source 璧峰鑺傜偣
+     * @param runTaskKeyList 姝e湪杩愯鐨勪换鍔� Key锛岀敤浜庢牎楠屼换鍔¤妭鐐规槸鍚︽槸姝e湪杩愯鐨勮妭鐐�
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param userTaskList 闇�瑕佹挙鍥炵殑鐢ㄦ埛浠诲姟鍒楄〃
+     * @return
+     */
+    public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+
+        // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 濡傛灉涓虹敤鎴蜂换鍔$被鍨嬶紝涓斾换鍔¤妭鐐圭殑 Key 姝e湪杩愯鐨勪换鍔′腑瀛樺湪锛屾坊鍔�
+                if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
+                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
+                    continue;
+                }
+                // 濡傛灉鑺傜偣涓哄瓙娴佺▼鑺傜偣鎯呭喌锛屽垯浠庤妭鐐逛腑鐨勭涓�涓妭鐐瑰紑濮嬭幏鍙�
+                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                    List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
+                    // 濡傛灉鎵惧埌鑺傜偣锛屽垯璇存槑璇ョ嚎璺壘鍒拌妭鐐癸紝涓嶇户缁悜涓嬫壘锛屽弽涔嬬户缁�
+                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
+                        userTaskList.addAll(childUserTaskList);
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
+            }
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 杩唬鑾峰彇瀛愭祦绋嬬敤鎴蜂换鍔¤妭鐐�
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param userTaskList 闇�瑕佹挙鍥炵殑鐢ㄦ埛浠诲姟鍒楄〃
+     * @return
+     */
+    public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 濡傛灉涓虹敤鎴蜂换鍔$被鍨嬶紝涓斾换鍔¤妭鐐圭殑 Key 姝e湪杩愯鐨勪换鍔′腑瀛樺湪锛屾坊鍔�
+                if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
+                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
+                    continue;
+                }
+                // 濡傛灉鑺傜偣涓哄瓙娴佺▼鑺傜偣鎯呭喌锛屽垯浠庤妭鐐逛腑鐨勭涓�涓妭鐐瑰紑濮嬭幏鍙�
+                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                    List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
+                    // 濡傛灉鎵惧埌鑺傜偣锛屽垯璇存槑璇ョ嚎璺壘鍒拌妭鐐癸紝涓嶇户缁悜涓嬫壘锛屽弽涔嬬户缁�
+                    if (childUserTaskList != null && childUserTaskList.size() > 0) {
+                        userTaskList.addAll(childUserTaskList);
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
+            }
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 浠庡悗鍚戝墠瀵昏矾锛岃幏鍙栨墍鏈夎剰绾胯矾涓婄殑鐐�
+     * @param source 璧峰鑺傜偣
+     * @param passRoads 宸茬粡缁忚繃鐨勭偣闆嗗悎
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param targets 鐩爣鑴忕嚎璺粓鐐�
+     * @param dirtyRoads 纭畾涓鸿剰鏁版嵁鐨勭偣锛屽洜涓轰笉闇�瑕侀噸澶嶏紝鍥犳浣跨敤 set 瀛樺偍
+     * @return
+     */
+    public static Set<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads, Set<String> hasSequenceFlow, List<String> targets, Set<String> dirtyRoads) {
+        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
+        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+        // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 鏂板缁忚繃鐨勮矾绾�
+                passRoads.add(sequenceFlow.getSourceFlowElement().getId());
+                // 濡傛灉姝ょ偣涓虹洰鏍囩偣锛岀‘瀹氱粡杩囩殑璺嚎涓鸿剰绾胯矾锛屾坊鍔犵偣鍒拌剰绾胯矾涓紝鐒跺悗鎵句笅涓繛绾�
+                if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) {
+                    dirtyRoads.addAll(passRoads);
+                    continue;
+                }
+                // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+                if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
+                    dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads);
+                    // 鏄惁瀛樺湪瀛愭祦绋嬩笂锛宼rue 鏄紝false 鍚�
+                    Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null);
+                    if (isInChildProcess) {
+                        // 宸插湪瀛愭祦绋嬩笂鎵惧埌锛岃璺嚎缁撴潫
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, targets, dirtyRoads);
+            }
+        }
+        return dirtyRoads;
+    }
+
+    /**
+     * 杩唬鑾峰彇瀛愭祦绋嬭剰璺嚎
+     * 璇存槑锛屽亣濡傚洖閫�鐨勭偣灏辨槸瀛愭祦绋嬶紝閭d箞涔熻偗瀹氫細鍥為��鍒板瓙娴佺▼鏈�鍒濈殑鐢ㄦ埛浠诲姟鑺傜偣锛屽洜姝ゅ瓙娴佺▼涓殑鑺傜偣鍏ㄦ槸鑴忚矾绾�
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param dirtyRoads 纭畾涓鸿剰鏁版嵁鐨勭偣锛屽洜涓轰笉闇�瑕侀噸澶嶏紝鍥犳浣跨敤 set 瀛樺偍
+     * @return
+     */
+    public static Set<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow, Set<String> dirtyRoads) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 娣诲姞鑴忚矾绾�
+                dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId());
+                // 濡傛灉鑺傜偣涓哄瓙娴佺▼鑺傜偣鎯呭喌锛屽垯浠庤妭鐐逛腑鐨勭涓�涓妭鐐瑰紑濮嬭幏鍙�
+                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                    dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads);
+                }
+                // 缁х画杩唬
+                dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, dirtyRoads);
+            }
+        }
+        return dirtyRoads;
+    }
+
+    /**
+     * 鍒ゆ柇鑴忚矾绾跨粨鏉熻妭鐐规槸鍚﹀湪瀛愭祦绋嬩笂
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param targets 鍒ゆ柇鑴忚矾绾胯妭鐐规槸鍚﹀瓨鍦ㄥ瓙娴佺▼涓婏紝鍙瀛樺湪涓�涓紝璇存槑鑴忚矾绾垮彧鍒板瓙娴佺▼涓烘
+     * @param inChildProcess 鏄惁瀛樺湪瀛愭祦绋嬩笂锛宼rue 鏄紝false 鍚�
+     * @return
+     */
+    public static Boolean dirtyTargetInChildProcess(FlowElement source, Set<String> hasSequenceFlow, List<String> targets, Boolean inChildProcess) {
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+        inChildProcess = inChildProcess == null ? false : inChildProcess;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null && !inChildProcess) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 濡傛灉鍙戠幇鐩爣鐐瑰湪瀛愭祦绋嬩笂瀛樺湪锛岃鏄庡彧鍒板瓙娴佺▼涓烘
+                if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) {
+                    inChildProcess = true;
+                    break;
+                }
+                // 濡傛灉鑺傜偣涓哄瓙娴佺▼鑺傜偣鎯呭喌锛屽垯浠庤妭鐐逛腑鐨勭涓�涓妭鐐瑰紑濮嬭幏鍙�
+                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+                    inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess);
+                }
+                // 缁х画杩唬
+                inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, targets, inChildProcess);
+            }
+        }
+        return inChildProcess;
+    }
+
+    /**
+     * 杩唬浠庡悗鍚戝墠鎵弿锛屽垽鏂洰鏍囪妭鐐圭浉瀵逛簬褰撳墠鑺傜偣鏄惁鏄覆琛�
+     * 涓嶅瓨鍦ㄧ洿鎺ュ洖閫�鍒板瓙娴佺▼涓殑鎯呭喌锛屼絾瀛樺湪浠庡瓙娴佺▼鍑哄幓鍒扮埗娴佺▼鎯呭喌
+     * @param source 璧峰鑺傜偣
+     * @param isSequential 鏄惁涓茶
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param targetKsy 鐩爣鑺傜偣
+     * @return
+     */
+    public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set<String> hasSequenceFlow, Boolean isSequential) {
+        isSequential = isSequential == null ? true : isSequential;
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+        // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, isSequential);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 濡傛灉鐩爣鑺傜偣宸茶鍒ゆ柇涓哄苟琛岋紝鍚庨潰閮戒笉闇�瑕佹墽琛岋紝鐩存帴杩斿洖
+                if (isSequential == false) {
+                    break;
+                }
+                // 杩欐潯绾胯矾瀛樺湪鐩爣鑺傜偣锛岃繖鏉$嚎璺畬鎴愶紝杩涘叆涓嬩釜绾胯矾
+                if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
+                    continue;
+                }
+                if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
+                    isSequential = false;
+                    break;
+                }
+                // 鍚﹀垯灏辩户缁凯浠�
+                isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, hasSequenceFlow, isSequential);
+            }
+        }
+        return isSequential;
+    }
+
+    /**
+     * 浠庡悗鍚戝墠瀵昏矾锛岃幏鍙栧埌杈捐妭鐐圭殑鎵�鏈夎矾绾�
+     * 涓嶅瓨鍦ㄧ洿鎺ュ洖閫�鍒板瓙娴佺▼锛屼絾鏄瓨鍦ㄥ洖閫�鍒扮埗绾ф祦绋嬬殑鎯呭喌
+     * @param source 璧峰鑺傜偣
+     * @param passRoads 宸茬粡缁忚繃鐨勭偣闆嗗悎
+     * @param roads 璺嚎
+     * @return
+     */
+    public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
+        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
+        roads = roads == null ? new ArrayList<>() : roads;
+        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+        // 濡傛灉璇ヨ妭鐐逛负寮�濮嬭妭鐐癸紝涓斿瓨鍦ㄤ笂绾у瓙鑺傜偣锛屽垯椤虹潃涓婄骇瀛愯妭鐐圭户缁凯浠�
+        if (source instanceof StartEvent && source.getSubProcess() != null) {
+            roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+
+        if (sequenceFlows != null && sequenceFlows.size() != 0) {
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                // 娣诲姞缁忚繃璺嚎
+                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
+                    passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
+                }
+                // 缁х画杩唬
+                roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
+            }
+        } else {
+            // 娣诲姞璺嚎
+            roads.add(passRoads);
+        }
+        return roads;
+    }
+
+    /**
+     * 鍘嗗彶鑺傜偣鏁版嵁娓呮礂锛屾竻娲楁帀鍙堝洖婊氬鑷寸殑鑴忔暟鎹�
+     * @param allElements 鍏ㄩ儴鑺傜偣淇℃伅
+     * @param historicTaskInstanceList 鍘嗗彶浠诲姟瀹炰緥淇℃伅锛屾暟鎹噰鐢ㄥ紑濮嬫椂闂村崌搴�
+     * @return
+     */
+    public static List<String> historicTaskInstanceClean(Collection<FlowElement> allElements, List<HistoricTaskInstance> historicTaskInstanceList) {
+        // 浼氱鑺傜偣鏀堕泦
+        List<String> multiTask = new ArrayList<>();
+        allElements.forEach(flowElement -> {
+            if (flowElement instanceof UserTask) {
+                // 濡傛灉璇ヨ妭鐐圭殑琛屼负涓轰細绛捐涓猴紝璇存槑璇ヨ妭鐐逛负浼氱鑺傜偣
+                if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) {
+                    multiTask.add(flowElement.getId());
+                }
+            }
+        });
+        // 寰幆鏀惧叆鏍堬紝鏍� LIFO锛氬悗杩涘厛鍑�
+        Stack<HistoricTaskInstance> stack = new Stack<>();
+        historicTaskInstanceList.forEach(item -> stack.push(item));
+        // 娓呮礂鍚庣殑鍘嗗彶浠诲姟瀹炰緥
+        List<String> lastHistoricTaskInstanceList = new ArrayList<>();
+        // 缃戝叧瀛樺湪鍙兘鍙蛋浜嗛儴鍒嗗垎鏀儏鍐碉紝涓旇繕瀛樺湪璺宠浆搴熷純鏁版嵁浠ュ強鍏朵粬鍒嗘敮鏁版嵁鐨勫共鎵帮紝鍥犳闇�瑕佸鍘嗗彶鑺傜偣鏁版嵁杩涜娓呮礂
+        // 涓存椂鐢ㄦ埛浠诲姟 key
+        StringBuilder userTaskKey = null;
+        // 涓存椂琚垹鎺夌殑浠诲姟 key锛屽瓨鍦ㄥ苟琛屾儏鍐�
+        List<String> deleteKeyList = new ArrayList<>();
+        // 涓存椂鑴忔暟鎹嚎璺�
+        List<Set<String>> dirtyDataLineList = new ArrayList<>();
+        // 鐢辨煇涓偣璺冲埌浼氱鐐�,姝ゆ椂鍑虹幇澶氫釜浼氱瀹炰緥瀵瑰簲 1 涓烦杞儏鍐碉紝闇�瑕佹妸杩欎簺杩炵画鑴忔暟鎹兘鎵惧埌
+        // 浼氱鐗规畩澶勭悊涓嬫爣
+        int multiIndex = -1;
+        // 浼氱鐗规畩澶勭悊 key
+        StringBuilder multiKey = null;
+        // 浼氱鐗规畩澶勭悊鎿嶄綔鏍囪瘑
+        boolean multiOpera = false;
+        while (!stack.empty()) {
+            // 浠庤繖閲屽紑濮� userTaskKey 閮借繕鏄笂涓爤鐨� key
+            // 鏄惁鏄剰鏁版嵁绾胯矾涓婄殑鐐�
+            final boolean[] isDirtyData = {false};
+            for (Set<String> oldDirtyDataLine : dirtyDataLineList) {
+                if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) {
+                    isDirtyData[0] = true;
+                }
+            }
+            // 鍒犻櫎鍘熷洜涓嶄负绌猴紝璇存槑浠庤繖鏉℃暟鎹紑濮嬪洖璺虫垨鑰呭洖閫�鐨�
+            // MI_END锛氫細绛惧畬鎴愬悗锛屽叾浠栨湭绛惧埌鑺傜偣鐨勫垹闄ゅ師鍥狅紝涓嶅湪澶勭悊鑼冨洿鍐�
+            if (stack.peek().getDeleteReason() != null && !stack.peek().getDeleteReason().equals("MI_END")) {
+                // 鍙互鐞嗚В涓鸿剰绾胯矾璧风偣
+                String dirtyPoint = "";
+                if (stack.peek().getDeleteReason().indexOf("Change activity to ") >= 0) {
+                    dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
+                }
+                // 浼氱鍥為��鍒犻櫎鍘熷洜鏈夌偣涓嶅悓
+                if (stack.peek().getDeleteReason().indexOf("Change parent activity to ") >= 0) {
+                    dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
+                }
+                FlowElement dirtyTask = null;
+                // 鑾峰彇鍙樻洿鑺傜偣鐨勫搴旂殑鍏ュ彛澶勮繛绾�
+                // 濡傛灉鏄綉鍏冲苟琛屽洖閫�鎯呭喌锛屼細鍙樻垚涓ゆ潯鑴忔暟鎹矾绾匡紝鏁堟灉涓�鏍�
+                for (FlowElement flowElement : allElements) {
+                    if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) {
+                        dirtyTask = flowElement;
+                    }
+                }
+                // 鑾峰彇鑴忔暟鎹嚎璺�
+                Set<String> dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null);
+                // 鑷繁鏈韩涔熸槸鑴忕嚎璺笂鐨勭偣锛屽姞杩涘幓
+                dirtyDataLine.add(stack.peek().getTaskDefinitionKey());
+                log.info(stack.peek().getTaskDefinitionKey() + "鐐硅剰璺嚎闆嗗悎锛�" + dirtyDataLine);
+                // 鏄叏鏂扮殑闇�瑕佹坊鍔犵殑鑴忕嚎璺�
+                boolean isNewDirtyData = true;
+                for (int i = 0; i < dirtyDataLineList.size(); i++) {
+                    // 濡傛灉鍙戠幇浠栫殑涓婁釜鑺傜偣鍦ㄨ剰绾胯矾鍐咃紝璇存槑杩欎釜鐐瑰彲鑳芥槸骞惰鐨勮妭鐐癸紝鎴栬�呰繛缁┏鍥�
+                    // 杩欐椂锛岄兘浠ヤ箣鍓嶇殑鑴忕嚎璺妭鐐逛负鏍囧噯锛屽彧闇�鍚堝苟鑴忕嚎璺嵆鍙紝涔熷氨鏄矾绾胯ˉ鍏�
+                    if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) {
+                        isNewDirtyData = false;
+                        dirtyDataLineList.get(i).addAll(dirtyDataLine);
+                    }
+                }
+                // 宸茬‘瀹氭椂鍏ㄦ柊鐨勮剰绾胯矾
+                if (isNewDirtyData) {
+                    // deleteKey 鍗曚竴璺嚎椹冲洖鍒板苟琛岋紝杩欑鍚屾椂鐢熸垚澶氫釜鏂板疄渚嬭褰曟儏鍐碉紝杩欐椂 deleteKey 鍏跺疄鏄敱澶氫釜鍊肩粍鎴�
+                    // 鎸夌収閫昏緫锛屽洖閫�鍚庣珛鍒荤敓鎴愮殑瀹炰緥璁板綍灏辨槸鍥為��鐨勮褰�
+                    // 鑷充簬椹冲洖鎵�鐢熸垚鐨� Key锛岀洿鎺ヤ粠鍒犻櫎鍘熷洜涓幏鍙栵紝鍥犱负瀛樺湪椹冲洖鍒板苟琛岀殑鎯呭喌
+                    deleteKeyList.add(dirtyPoint + ",");
+                    dirtyDataLineList.add(dirtyDataLine);
+                }
+                // 娣诲姞鍚庯紝鐜板湪杩欎釜鐐瑰彉鎴愯剰绾胯矾涓婄殑鐐逛簡
+                isDirtyData[0] = true;
+            }
+            // 濡傛灉涓嶆槸鑴忕嚎璺笂鐨勭偣锛岃鏄庢槸鏈夋晥鏁版嵁锛屾坊鍔犲巻鍙插疄渚� Key
+            if (!isDirtyData[0]) {
+                lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey());
+            }
+            // 鏍¢獙鑴忕嚎璺槸鍚︾粨鏉�
+            for (int i = 0; i < deleteKeyList.size(); i ++) {
+                // 濡傛灉鍙戠幇鑴忔暟鎹睘浜庝細绛撅紝璁板綍涓嬩笅鏍囦笌瀵瑰簲 Key锛屼互澶囧悗缁瘮瀵癸紝浼氱鑴忔暟鎹寖鐣村紑濮�
+                if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey())
+                        && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
+                    multiIndex = i;
+                    multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey());
+                }
+                // 浼氱鑴忔暟鎹鐞嗭紝鑺傜偣閫�鍥炰細绛炬竻绌�
+                // 濡傛灉鍦ㄤ細绛捐剰鏁版嵁鑼冪暣涓彂鐜� Key鏀瑰彉锛岃鏄庝細绛捐剰鏁版嵁鍦ㄤ笂涓妭鐐瑰氨缁撴潫浜嗭紝鍙互鎶婁細绛捐剰鏁版嵁鍒犳帀
+                if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) {
+                    deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
+                    multiKey = null;
+                    // 缁撴潫杩涜涓嬫牎楠屽垹闄�
+                    multiOpera = true;
+                }
+                // 鍏朵粬鑴忔暟鎹鐞�
+                // 鍙戠幇璇ヨ矾绾挎渶鍚庝竴鏉¤剰鏁版嵁锛岃鏄庤繖鏉¤剰鏁版嵁绾胯矾澶勭悊瀹屼簡锛屽垹闄よ剰鏁版嵁淇℃伅
+                // 鑴忔暟鎹骇鐢熺殑鏂板疄渚嬩腑鏄惁鍖呭惈杩欐潯鏁版嵁
+                if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
+                    // 鍒犻櫎鍖归厤鍒扮殑閮ㄥ垎
+                    deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
+                }
+                // 濡傛灉姣忕粍涓殑鍏冪礌閮戒互鍖归厤杩囷紝璇存槑鑴忔暟鎹粨鏉�
+                if ("".equals(deleteKeyList.get(i))) {
+                    // 鍚屾椂鍒犻櫎鑴忔暟鎹�
+                    deleteKeyList.remove(i);
+                    dirtyDataLineList.remove(i);
+                    break;
+                }
+            }
+            // 浼氱鏁版嵁澶勭悊闇�瑕佸湪寰幆澶栧鐞嗭紝鍚﹀垯鍙兘瀵艰嚧婧㈠嚭
+            // 浼氱鐨勬暟鎹偗瀹氭槸涔嬪墠鏀捐繘鍘荤殑鎵�浠ョ悊璁轰笂涓嶄細婧㈠嚭锛屼絾杩樻槸鏍¢獙涓�
+            if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) {
+                // 鍚屾椂鍒犻櫎鑴忔暟鎹�
+                deleteKeyList.remove(multiIndex);
+                dirtyDataLineList.remove(multiIndex);
+                multiIndex = -1;
+                multiOpera = false;
+            }
+            // pop() 鏂规硶涓� peek() 鏂规硶涓嶅悓锛屽湪杩斿洖鍊肩殑鍚屾椂锛屼細鎶婂�间粠鏍堜腑绉婚櫎
+            // 淇濆瓨鏂扮殑 userTaskKey 鍦ㄤ笅涓惊鐜腑浣跨敤
+            userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey());
+        }
+        log.info("娓呮礂鍚庣殑鍘嗗彶鑺傜偣鏁版嵁锛�" + lastHistoricTaskInstanceList);
+        return lastHistoricTaskInstanceList;
+    }
+
+    /**
+     * 娣辨悳閫掑綊鑾峰彇娴佺▼鏈�氳繃鐨勮妭鐐�
+     * @param bpmnModel 娴佺▼妯″瀷
+     * @param unfinishedTaskSet 鏈粨鏉熺殑浠诲姟鑺傜偣
+     * @param finishedSequenceFlowSet 宸茬粡瀹屾垚鐨勮繛绾�
+     * @param finishedTaskSet 宸插畬鎴愮殑浠诲姟鑺傜偣
+     * @return
+     */
+    public static Set<String> dfsFindRejects(BpmnModel bpmnModel, Set<String> unfinishedTaskSet, Set<String> finishedSequenceFlowSet, Set<String> finishedTaskSet) {
+        if (ObjectUtil.isNull(bpmnModel)) {
+            throw new ServiceException("娴佺▼妯″瀷涓嶅瓨鍦�");
+        }
+        Collection<FlowElement> allElements = getAllElements(bpmnModel.getMainProcess().getFlowElements(), null);
+        Set<String> rejectedSet = new HashSet<>();
+        for (FlowElement flowElement : allElements) {
+            // 鐢ㄦ埛鑺傜偣涓旀湭缁撴潫鍏冪礌
+            if (flowElement instanceof UserTask && unfinishedTaskSet.contains(flowElement.getId())) {
+                List<String> hasSequenceFlow = iteratorFindFinishes(flowElement, null);
+                List<String> rejects = iteratorFindRejects(flowElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, null);
+                rejectedSet.addAll(rejects);
+            }
+        }
+        return rejectedSet;
+    }
+
+    /**
+     * 杩唬鑾峰彇鐖剁骇鑺傜偣鍒楄〃锛屽悜鍓嶆壘
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @return
+     */
+    public static List<String> iteratorFindFinishes(FlowElement source, List<String> hasSequenceFlow) {
+        hasSequenceFlow = hasSequenceFlow == null ? new ArrayList<>() : hasSequenceFlow;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                FlowElement finishedElement = sequenceFlow.getSourceFlowElement();
+                // 绫诲瀷涓哄瓙娴佺▼锛屽垯娣诲姞瀛愭祦绋嬪紑濮嬭妭鐐瑰嚭鍙e鐩歌繛鐨勮妭鐐�
+                if (finishedElement instanceof SubProcess) {
+                    FlowElement firstElement = (StartEvent) ((SubProcess) finishedElement).getFlowElements().toArray()[0];
+                    // 鑾峰彇瀛愭祦绋嬬殑杩炵嚎
+                    hasSequenceFlow.addAll(iteratorFindFinishes(firstElement, null));
+                }
+                // 缁х画杩唬
+                hasSequenceFlow = iteratorFindFinishes(finishedElement, hasSequenceFlow);
+            }
+        }
+        return hasSequenceFlow;
+    }
+
+    /**
+     * 鏍规嵁姝e湪杩愯鐨勪换鍔¤妭鐐癸紝杩唬鑾峰彇瀛愮骇浠诲姟鑺傜偣鍒楄〃锛屽悜鍚庢壘
+     * @param source 璧峰鑺傜偣
+     * @param finishedSequenceFlowSet 宸茬粡瀹屾垚鐨勮繛绾�
+     * @param finishedTaskSet 宸茬粡瀹屾垚鐨勪换鍔¤妭鐐�
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param rejectedList 鏈�氳繃鐨勫厓绱�
+     * @return
+     */
+    public static List<String> iteratorFindRejects(FlowElement source, Set<String> finishedSequenceFlowSet, Set<String> finishedTaskSet
+            , List<String> hasSequenceFlow, List<String> rejectedList) {
+        hasSequenceFlow = hasSequenceFlow == null ? new ArrayList<>() : hasSequenceFlow;
+        rejectedList = rejectedList == null ? new ArrayList<>() : rejectedList;
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧嚭鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+
+        if (sequenceFlows != null) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                FlowElement targetElement = sequenceFlow.getTargetFlowElement();
+                // 娣诲姞鏈畬鎴愮殑鑺傜偣
+                if (finishedTaskSet.contains(targetElement.getId())) {
+                    rejectedList.add(targetElement.getId());
+                }
+                // 娣诲姞鏈畬鎴愮殑杩炵嚎
+                if (finishedSequenceFlowSet.contains(sequenceFlow.getId())) {
+                    rejectedList.add(sequenceFlow.getId());
+                }
+                // 濡傛灉鑺傜偣涓哄瓙娴佺▼鑺傜偣鎯呭喌锛屽垯浠庤妭鐐逛腑鐨勭涓�涓妭鐐瑰紑濮嬭幏鍙�
+                if (targetElement instanceof SubProcess) {
+                    FlowElement firstElement = (FlowElement) (((SubProcess) targetElement).getFlowElements().toArray()[0]);
+                    List<String> childList = iteratorFindRejects(firstElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, null);
+                    // 濡傛灉鎵惧埌鑺傜偣锛屽垯璇存槑璇ョ嚎璺壘鍒拌妭鐐癸紝涓嶇户缁悜涓嬫壘锛屽弽涔嬬户缁�
+                    if (childList != null && childList.size() > 0) {
+                        rejectedList.addAll(childList);
+                        continue;
+                    }
+                }
+                // 缁х画杩唬
+                rejectedList = iteratorFindRejects(targetElement, finishedSequenceFlowSet, finishedTaskSet, hasSequenceFlow, rejectedList);
+            }
+        }
+        return rejectedList;
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java
new file mode 100644
index 0000000..853a39a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/GlobalEventListener.java
@@ -0,0 +1,40 @@
+package org.ruoyi.flowable.listener;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import org.ruoyi.flowable.common.constant.ProcessConstants;
+
+import org.ruoyi.flowable.common.enums.ProcessStatus;
+import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Flowable 鍏ㄥ眬鐩戝惉鍣�
+ *
+ * @author konbai
+ * @since 2023/3/8 22:45
+ */
+@Component
+public class GlobalEventListener extends AbstractFlowableEngineEventListener {
+
+    @Autowired
+    private RuntimeService runtimeService;
+
+    /**
+     * 娴佺▼缁撴潫鐩戝惉鍣�
+     */
+    @Override
+    protected void processCompleted(FlowableEngineEntityEvent event) {
+        String processInstanceId = event.getProcessInstanceId();
+        Object variable = runtimeService.getVariable(processInstanceId, ProcessConstants.PROCESS_STATUS_KEY);
+        ProcessStatus status = ProcessStatus.getProcessStatus(Convert.toStr(variable));
+        if (ObjectUtil.isNotNull(status) && ProcessStatus.RUNNING == status) {
+            runtimeService.setVariable(processInstanceId, ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.COMPLETED.getStatus());
+        }
+        super.processCompleted(event);
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java
new file mode 100644
index 0000000..16e740d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/listener/UserTaskListener.java
@@ -0,0 +1,28 @@
+package org.ruoyi.flowable.listener;
+
+import org.flowable.engine.delegate.TaskListener;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐢ㄦ埛浠诲姟鐩戝惉鍣�
+ *
+ * @author KonBAI
+ * @since 2023/5/13
+ */
+@Component(value = "userTaskListener")
+public class UserTaskListener implements TaskListener {
+
+    /**
+     * 娉ㄥ叆瀛楁锛堝悕绉颁笌娴佺▼璁捐鏃跺瓧娈靛悕绉颁竴鑷达級
+     */
+    // private FixedValue field;
+
+    @Override
+    public void notify(DelegateTask delegateTask) {
+        //TODO 瀹炵幇浣犵殑浠诲姟鐩戝惉鍣ㄩ�昏緫
+        System.out.println("鎵ц浠诲姟鐩戝惉鍣�...");
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java
new file mode 100644
index 0000000..1aeaa50
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/BeanCopyUtils.java
@@ -0,0 +1,206 @@
+package org.ruoyi.flowable.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.SimpleCache;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.ruoyi.common.core.utils.StreamUtils;
+import org.springframework.cglib.beans.BeanCopier;
+import org.springframework.cglib.beans.BeanMap;
+import org.springframework.cglib.core.Converter;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * bean娣辨嫹璐濆伐鍏�(鍩轰簬 cglib 鎬ц兘浼樺紓)
+ * <p>
+ * 閲嶇偣 cglib 涓嶆敮鎸� 鎷疯礉鍒伴摼寮忓璞�
+ * 渚嬪: 婧愬璞� 鎷疯礉鍒� 鐩爣(閾惧紡瀵硅薄)
+ * 璇峰尯鍒嗗ソ`娴呮嫹璐漙鍜宍娣辨嫹璐漙鍐嶅仛浣跨敤
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class BeanCopyUtils {
+
+    /**
+     * 鍗曞璞″熀浜巆lass鍒涘缓鎷疯礉
+     *
+     * @param source 鏁版嵁鏉ユ簮瀹炰綋
+     * @param desc   鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> V copy(T source, Class<V> desc) {
+        if (ObjectUtil.isNull(source)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(desc)) {
+            return null;
+        }
+        final V target = ReflectUtil.newInstanceIfPossible(desc);
+        return copy(source, target);
+    }
+
+    /**
+     * 鍗曞璞″熀浜庡璞″垱寤烘嫹璐�
+     *
+     * @param source 鏁版嵁鏉ユ簮瀹炰綋
+     * @param desc   杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> V copy(T source, V desc) {
+        if (ObjectUtil.isNull(source)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(desc)) {
+            return null;
+        }
+        BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
+        beanCopier.copy(source, desc, null);
+        return desc;
+    }
+
+    /**
+     * 鍒楄〃瀵硅薄鍩轰簬class鍒涘缓鎷疯礉
+     *
+     * @param sourceList 鏁版嵁鏉ユ簮瀹炰綋鍒楄〃
+     * @param desc       鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
+        if (ObjectUtil.isNull(sourceList)) {
+            return null;
+        }
+        if (CollUtil.isEmpty(sourceList)) {
+            return CollUtil.newArrayList();
+        }
+        return StreamUtils.toList(sourceList, source -> {
+            V target = ReflectUtil.newInstanceIfPossible(desc);
+            copy(source, target);
+            return target;
+        });
+    }
+
+    /**
+     * bean鎷疯礉鍒癿ap
+     *
+     * @param bean 鏁版嵁鏉ユ簮瀹炰綋
+     * @return map瀵硅薄
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Map<String, Object> copyToMap(T bean) {
+        if (ObjectUtil.isNull(bean)) {
+            return null;
+        }
+        return BeanMap.create(bean);
+    }
+
+    /**
+     * map鎷疯礉鍒癰ean
+     *
+     * @param map       鏁版嵁鏉ユ簮
+     * @param beanClass bean绫�
+     * @return bean瀵硅薄
+     */
+    public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
+        if (MapUtil.isEmpty(map)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(beanClass)) {
+            return null;
+        }
+        T bean = ReflectUtil.newInstanceIfPossible(beanClass);
+        return mapToBean(map, bean);
+    }
+
+    /**
+     * map鎷疯礉鍒癰ean
+     *
+     * @param map  鏁版嵁鏉ユ簮
+     * @param bean bean瀵硅薄
+     * @return bean瀵硅薄
+     */
+    public static <T> T mapToBean(Map<String, Object> map, T bean) {
+        if (MapUtil.isEmpty(map)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(bean)) {
+            return null;
+        }
+        BeanMap.create(bean).putAll(map);
+        return bean;
+    }
+
+    /**
+     * map鎷疯礉鍒癿ap
+     *
+     * @param map   鏁版嵁鏉ユ簮
+     * @param clazz 杩斿洖鐨勫璞$被鍨�
+     * @return map瀵硅薄
+     */
+    public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
+        if (MapUtil.isEmpty(map)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(clazz)) {
+            return null;
+        }
+        Map<String, V> copyMap = new LinkedHashMap<>(map.size());
+        map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
+        return copyMap;
+    }
+
+    /**
+     * BeanCopier灞炴�х紦瀛�<br>
+     * 缂撳瓨鐢ㄤ簬闃叉澶氭鍙嶅皠閫犳垚鐨勬�ц兘闂
+     *
+     * @author Looly
+     * @since 5.4.1
+     */
+    public enum BeanCopierCache {
+        /**
+         * BeanCopier灞炴�х紦瀛樺崟渚�
+         */
+        INSTANCE;
+
+        private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
+
+        /**
+         * 鑾峰緱绫讳笌杞崲鍣ㄧ敓鎴愮殑key鍦▄@link BeanCopier}鐨凪ap涓搴旂殑鍏冪礌
+         *
+         * @param srcClass    婧怋ean鐨勭被
+         * @param targetClass 鐩爣Bean鐨勭被
+         * @param converter   杞崲鍣�
+         * @return Map涓搴旂殑BeanCopier
+         */
+        public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
+            final String key = genKey(srcClass, targetClass, converter);
+            return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
+        }
+
+        /**
+         * 鑾峰緱绫讳笌杞崲鍣ㄧ敓鎴愮殑key
+         *
+         * @param srcClass    婧怋ean鐨勭被
+         * @param targetClass 鐩爣Bean鐨勭被
+         * @param converter   杞崲鍣�
+         * @return 灞炴�у悕鍜孧ap鏄犲皠鐨刱ey
+         */
+        private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
+            final StringBuilder key = StrUtil.builder()
+                    .append(srcClass.getName()).append('#').append(targetClass.getName());
+            if (null != converter) {
+                key.append('#').append(converter.getClass().getName());
+            }
+            return key.toString();
+        }
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java
new file mode 100644
index 0000000..32a2aaf
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/JsonUtils.java
@@ -0,0 +1,115 @@
+package org.ruoyi.flowable.utils;
+
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import org.ruoyi.common.core.utils.SpringUtils;
+import org.ruoyi.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 宸ュ叿绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+
+    public static String toJsonString(Object object) {
+        if (ObjectUtil.isNull(object)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+        if (ArrayUtil.isEmpty(bytes)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(bytes, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Dict parseMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+        } catch (MismatchedInputException e) {
+            // 绫诲瀷涓嶅尮閰嶈鏄庝笉鏄痡son
+            return null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static List<Dict> parseArrayMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return new ArrayList<>();
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java
new file mode 100644
index 0000000..8593283
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ModelUtils.java
@@ -0,0 +1,374 @@
+package org.ruoyi.flowable.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.common.engine.impl.util.io.StringStreamSource;
+
+import java.util.*;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/26 19:04
+ */
+public class ModelUtils {
+
+    private static final BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
+
+    /**
+     * xml杞琤pmnModel瀵硅薄
+     *
+     * @param xml xml
+     * @return bpmnModel瀵硅薄
+     */
+    public static BpmnModel getBpmnModel(String xml) {
+        return bpmnXMLConverter.convertToBpmnModel(new StringStreamSource(xml), false, false);
+    }
+
+    /**
+     * bpmnModel杞瑇ml瀛楃涓�
+     *
+     * @deprecated 瀛樺湪浼氫涪澶� bpmn 杩炵嚎闂
+     * @param bpmnModel bpmnModel瀵硅薄
+     * @return xml瀛楃涓�
+     */
+    @Deprecated
+    public static String getBpmnXmlStr(BpmnModel bpmnModel) {
+        return StrUtil.utf8Str(getBpmnXml(bpmnModel));
+    }
+
+    /**
+     * bpmnModel杞瑇ml瀵硅薄
+     *
+     * @deprecated 瀛樺湪涓㈠け bpmn 杩炵嚎闂
+     * @param bpmnModel bpmnModel瀵硅薄
+     * @return xml
+     */
+    @Deprecated
+    public static byte[] getBpmnXml(BpmnModel bpmnModel) {
+        return bpmnXMLConverter.convertToXML(bpmnModel);
+    }
+
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧叆鍙h繛绾�
+     *
+     * @param source 璧峰鑺傜偣
+     * @return 鍏ュ彛杩炵嚎鍒楄〃
+     */
+    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
+        List<SequenceFlow> sequenceFlows = new ArrayList<>();
+        if (source instanceof FlowNode) {
+            sequenceFlows = ((FlowNode) source).getIncomingFlows();
+        }
+        return sequenceFlows;
+    }
+
+
+    /**
+     * 鏍规嵁鑺傜偣锛岃幏鍙栧嚭鍙h繛绾�
+     *
+     * @param source 璧峰鑺傜偣
+     * @return 鍑哄彛杩炵嚎鍒楄〃
+     */
+    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
+        List<SequenceFlow> sequenceFlows = new ArrayList<>();
+        if (source instanceof FlowNode) {
+            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
+        }
+        return sequenceFlows;
+    }
+
+    /**
+     * 鑾峰彇寮�濮嬭妭鐐�
+     *
+     * @param model bpmnModel瀵硅薄
+     * @return 寮�濮嬭妭鐐癸紙鏈壘鍒板紑濮嬭妭鐐癸紝杩斿洖null锛�
+     */
+    public static StartEvent getStartEvent(BpmnModel model) {
+        Process process = model.getMainProcess();
+        FlowElement startElement = process.getInitialFlowElement();
+        if (startElement instanceof StartEvent) {
+            return (StartEvent) startElement;
+        }
+        return getStartEvent(process.getFlowElements());
+    }
+
+    /**
+     * 鑾峰彇寮�濮嬭妭鐐�
+     *
+     * @param flowElements 娴佺▼鍏冪礌闆嗗悎
+     * @return 寮�濮嬭妭鐐癸紙鏈壘鍒板紑濮嬭妭鐐癸紝杩斿洖null锛�
+     */
+    public static StartEvent getStartEvent(Collection<FlowElement> flowElements) {
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof StartEvent) {
+                return (StartEvent) flowElement;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鑾峰彇缁撴潫鑺傜偣
+     *
+     * @param model bpmnModel瀵硅薄
+     * @return 缁撴潫鑺傜偣锛堟湭鎵惧埌寮�濮嬭妭鐐癸紝杩斿洖null锛�
+     */
+    public static EndEvent getEndEvent(BpmnModel model) {
+        Process process = model.getMainProcess();
+        return getEndEvent(process.getFlowElements());
+    }
+
+    /**
+     * 鑾峰彇缁撴潫鑺傜偣
+     *
+     * @param flowElements 娴佺▼鍏冪礌闆嗗悎
+     * @return 缁撴潫鑺傜偣锛堟湭鎵惧埌寮�濮嬭妭鐐癸紝杩斿洖null锛�
+     */
+    public static EndEvent getEndEvent(Collection<FlowElement> flowElements) {
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof EndEvent) {
+                return (EndEvent) flowElement;
+            }
+        }
+        return null;
+    }
+
+    public static UserTask getUserTaskByKey(BpmnModel model, String taskKey) {
+        Process process = model.getMainProcess();
+        FlowElement flowElement = process.getFlowElement(taskKey);
+        if (flowElement instanceof UserTask) {
+            return (UserTask) flowElement;
+        }
+        return null;
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍏冪礌淇℃伅
+     *
+     * @param model bpmnModel瀵硅薄
+     * @param flowElementId 鍏冪礌ID
+     * @return 鍏冪礌淇℃伅
+     */
+    public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
+        Process process = model.getMainProcess();
+        return process.getFlowElement(flowElementId);
+    }
+
+    /**
+     * 鑾峰彇鍏冪礌琛ㄥ崟Key锛堥檺寮�濮嬭妭鐐瑰拰鐢ㄦ埛鑺傜偣鍙敤锛�
+     *
+     * @param flowElement 鍏冪礌
+     * @return 琛ㄥ崟Key
+     */
+    public static String getFormKey(FlowElement flowElement) {
+        if (flowElement != null) {
+            if (flowElement instanceof StartEvent) {
+                return ((StartEvent) flowElement).getFormKey();
+            } else if (flowElement instanceof UserTask) {
+                return ((UserTask) flowElement).getFormKey();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鑾峰彇寮�濮嬭妭鐐瑰睘鎬у��
+     * @param model bpmnModel瀵硅薄
+     * @param name 灞炴�у悕
+     * @return 灞炴�у��
+     */
+    public static String getStartEventAttributeValue(BpmnModel model, String name) {
+        StartEvent startEvent = getStartEvent(model);
+        return getElementAttributeValue(startEvent, name);
+    }
+
+    /**
+     * 鑾峰彇缁撴潫鑺傜偣灞炴�у��
+     * @param model bpmnModel瀵硅薄
+     * @param name 灞炴�у悕
+     * @return 灞炴�у��
+     */
+    public static String getEndEventAttributeValue(BpmnModel model, String name) {
+        EndEvent endEvent = getEndEvent(model);
+        return getElementAttributeValue(endEvent, name);
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛浠诲姟鑺傜偣灞炴�у��
+     * @param model bpmnModel瀵硅薄
+     * @param taskKey 浠诲姟Key
+     * @param name 灞炴�у悕
+     * @return 灞炴�у��
+     */
+    public static String getUserTaskAttributeValue(BpmnModel model, String taskKey, String name) {
+        UserTask userTask = getUserTaskByKey(model, taskKey);
+        return getElementAttributeValue(userTask, name);
+    }
+
+    /**
+     * 鑾峰彇鍏冪礌灞炴�у��
+     * @param baseElement 娴佺▼鍏冪礌
+     * @param name 灞炴�у悕
+     * @return 灞炴�у��
+     */
+    public static String getElementAttributeValue(BaseElement baseElement, String name) {
+        if (baseElement != null) {
+            List<ExtensionAttribute> attributes = baseElement.getAttributes().get(name);
+            if (attributes != null && !attributes.isEmpty()) {
+                attributes.iterator().next().getValue();
+                Iterator<ExtensionAttribute> attrIterator = attributes.iterator();
+                if(attrIterator.hasNext()) {
+                    ExtensionAttribute attribute = attrIterator.next();
+                    return attribute.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    public static boolean isMultiInstance(BpmnModel model, String taskKey) {
+        UserTask userTask = getUserTaskByKey(model, taskKey);
+        if (ObjectUtil.isNotNull(userTask)) {
+            return userTask.hasMultiInstanceLoopCharacteristics();
+        }
+        return false;
+    }
+
+    /**
+     * 鑾峰彇鎵�鏈夌敤鎴蜂换鍔¤妭鐐�
+     *
+     * @param model bpmnModel瀵硅薄
+     * @return 鐢ㄦ埛浠诲姟鑺傜偣鍒楄〃
+     */
+    public static Collection<UserTask> getAllUserTaskEvent(BpmnModel model) {
+        Process process = model.getMainProcess();
+        Collection<FlowElement> flowElements = process.getFlowElements();
+        return getAllUserTaskEvent(flowElements, null);
+    }
+
+    /**
+     * 鑾峰彇鎵�鏈夌敤鎴蜂换鍔¤妭鐐�
+     * @param flowElements 娴佺▼鍏冪礌闆嗗悎
+     * @param allElements 鎵�鏈夋祦绋嬪厓绱犻泦鍚�
+     * @return 鐢ㄦ埛浠诲姟鑺傜偣鍒楄〃
+     */
+    public static Collection<UserTask> getAllUserTaskEvent(Collection<FlowElement> flowElements, Collection<UserTask> allElements) {
+        allElements = allElements == null ? new ArrayList<>() : allElements;
+        for (FlowElement flowElement : flowElements) {
+            if (flowElement instanceof UserTask) {
+                allElements.add((UserTask) flowElement);
+            }
+            if (flowElement instanceof SubProcess) {
+                // 缁х画娣卞叆瀛愭祦绋嬶紝杩涗竴姝ヨ幏鍙栧瓙娴佺▼
+                allElements = getAllUserTaskEvent(((SubProcess) flowElement).getFlowElements(), allElements);
+            }
+        }
+        return allElements;
+    }
+
+    /**
+     * 鏌ユ壘璧峰鑺傜偣涓嬩竴涓敤鎴蜂换鍔″垪琛ㄥ垪琛�
+     * @param source 璧峰鑺傜偣
+     * @return 缁撴灉
+     */
+    public static List<UserTask> findNextUserTasks(FlowElement source) {
+        return findNextUserTasks(source, null, null);
+    }
+
+    /**
+     * 鏌ユ壘璧峰鑺傜偣涓嬩竴涓敤鎴蜂换鍔″垪琛ㄥ垪琛�
+     * @param source 璧峰鑺傜偣
+     * @param hasSequenceFlow 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @param userTaskList 鐢ㄦ埛浠诲姟鍒楄〃
+     * @return 缁撴灉
+     */
+    public static List<UserTask> findNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
+        hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
+        userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
+        // 鑾峰彇鍑哄彛杩炵嚎
+        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
+        if (!sequenceFlows.isEmpty()) {
+            for (SequenceFlow sequenceFlow : sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                hasSequenceFlow.add(sequenceFlow.getId());
+                FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
+                if (targetFlowElement instanceof UserTask) {
+                    // 鑻ヨ妭鐐逛负鐢ㄦ埛浠诲姟锛屽姞鍏ュ埌缁撴灉鍒楄〃涓�
+                    userTaskList.add((UserTask) targetFlowElement);
+                } else {
+                    // 鑻ヨ妭鐐归潪鐢ㄦ埛浠诲姟锛岀户缁�掑綊鏌ユ壘涓嬩竴涓妭鐐�
+                    findNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
+                }
+            }
+        }
+        return userTaskList;
+    }
+
+    /**
+     * 杩唬浠庡悗鍚戝墠鎵弿锛屽垽鏂洰鏍囪妭鐐圭浉瀵逛簬褰撳墠鑺傜偣鏄惁鏄覆琛�
+     * 涓嶅瓨鍦ㄧ洿鎺ュ洖閫�鍒板瓙娴佺▼涓殑鎯呭喌锛屼絾瀛樺湪浠庡瓙娴佺▼鍑哄幓鍒扮埗娴佺▼鎯呭喌
+     * @param source 璧峰鑺傜偣
+     * @param target 鐩爣鑺傜偣
+     * @param visitedElements 宸茬粡缁忚繃鐨勮繛绾跨殑 ID锛岀敤浜庡垽鏂嚎璺槸鍚﹂噸澶�
+     * @return 缁撴灉
+     */
+    public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
+        visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
+        if (source instanceof StartEvent && isInEventSubprocess(source)) {
+            return false;
+        }
+
+        // 鏍规嵁绫诲瀷锛岃幏鍙栧叆鍙h繛绾�
+        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
+        if (sequenceFlows != null && sequenceFlows.size() > 0) {
+            // 寰幆鎵惧埌鐩爣鍏冪礌
+            for (SequenceFlow sequenceFlow: sequenceFlows) {
+                // 濡傛灉鍙戠幇杩炵嚎閲嶅锛岃鏄庡惊鐜簡锛岃烦杩囪繖涓惊鐜�
+                if (visitedElements.contains(sequenceFlow.getId())) {
+                    continue;
+                }
+                // 娣诲姞宸茬粡璧拌繃鐨勮繛绾�
+                visitedElements.add(sequenceFlow.getId());
+                FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
+                // 杩欐潯绾胯矾瀛樺湪鐩爣鑺傜偣锛岃繖鏉$嚎璺畬鎴愶紝杩涘叆涓嬩釜绾胯矾
+                if (target.getId().equals(sourceFlowElement.getId())) {
+                    continue;
+                }
+                // 濡傛灉鐩爣鑺傜偣涓哄苟琛岀綉鍏筹紝鍒欎笉缁х画
+                if (sourceFlowElement instanceof ParallelGateway) {
+                    return false;
+                }
+                // 鍚﹀垯灏辩户缁凯浠�
+                boolean isSequential = isSequentialReachable(sourceFlowElement, target, visitedElements);
+                if (!isSequential) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    protected static boolean isInEventSubprocess(FlowElement flowElement) {
+        FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
+        while (flowElementsContainer != null) {
+            if (flowElementsContainer instanceof EventSubProcess) {
+                return true;
+            }
+
+            if (flowElementsContainer instanceof FlowElement) {
+                flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
+            } else {
+                flowElementsContainer = null;
+            }
+        }
+        return false;
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java
new file mode 100644
index 0000000..84ba5f4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessFormUtils.java
@@ -0,0 +1,51 @@
+package org.ruoyi.flowable.utils;
+
+import cn.hutool.core.convert.Convert;
+import org.ruoyi.flowable.core.FormConf;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼琛ㄥ崟宸ュ叿绫�
+ *
+ * @author KonBAI
+ * @createTime 2022/8/7 17:09
+ */
+public class ProcessFormUtils {
+
+    private static final String CONFIG = "__config__";
+    private static final String MODEL = "__vModel__";
+
+    /**
+     * 濉厖琛ㄥ崟椤瑰唴瀹�
+     *
+     * @param formConf 琛ㄥ崟閰嶇疆淇℃伅
+     * @param data 琛ㄥ崟鍐呭
+     */
+    public static void fillFormData(FormConf formConf, Map<String, Object> data) {
+        for (Map<String, Object> field : formConf.getFields()) {
+            recursiveFillField(field, data);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static void recursiveFillField(final Map<String, Object> field, final Map<String, Object> data) {
+        if (!field.containsKey(CONFIG)) {
+            return;
+        }
+        Map<String, Object> configMap = (Map<String, Object>) field.get(CONFIG);
+        if (configMap.containsKey("children")) {
+            List<Map<String, Object>> childrens = (List<Map<String, Object>>) configMap.get("children");
+            for (Map<String, Object> children : childrens) {
+                recursiveFillField(children, data);
+            }
+        }
+        String modelKey = Convert.toStr(field.get(MODEL));
+        Object value = data.get(modelKey);
+        if (value != null) {
+            configMap.put("defaultValue", value);
+        }
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java
new file mode 100644
index 0000000..c6fcbac
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/ProcessUtils.java
@@ -0,0 +1,125 @@
+package org.ruoyi.flowable.utils;
+
+import org.ruoyi.common.core.utils.DateUtils;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.flowable.common.engine.api.query.Query;
+import org.flowable.common.engine.impl.db.SuspensionState;
+import org.flowable.engine.history.HistoricProcessInstanceQuery;
+import org.flowable.engine.repository.ProcessDefinitionQuery;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.api.history.HistoricTaskInstanceQuery;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * 娴佺▼宸ュ叿绫�
+ *
+ * @author konbai
+ * @since 2022/12/11 03:35
+ */
+public class ProcessUtils {
+
+    public static void buildProcessSearch(Query<?, ?> query, ProcessQuery process) {
+        if (query instanceof ProcessDefinitionQuery) {
+            buildProcessDefinitionSearch((ProcessDefinitionQuery) query, process);
+        } else if (query instanceof TaskQuery) {
+            buildTaskSearch((TaskQuery) query, process);
+        } else if (query instanceof HistoricTaskInstanceQuery) {
+            buildHistoricTaskInstanceSearch((HistoricTaskInstanceQuery) query, process);
+        } else if (query instanceof HistoricProcessInstanceQuery) {
+            buildHistoricProcessInstanceSearch((HistoricProcessInstanceQuery) query, process);
+        }
+    }
+
+    /**
+     * 鏋勫缓娴佺▼瀹氫箟鎼滅储
+     */
+    public static void buildProcessDefinitionSearch(ProcessDefinitionQuery query, ProcessQuery process) {
+        // 娴佺▼鏍囪瘑
+        if (StringUtils.isNotBlank(process.getProcessKey())) {
+            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
+        }
+        // 娴佺▼鍚嶇О
+        if (StringUtils.isNotBlank(process.getProcessName())) {
+            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
+        }
+        // 娴佺▼鍒嗙被
+        if (StringUtils.isNotBlank(process.getCategory())) {
+            query.processDefinitionCategory(process.getCategory());
+        }
+        // 娴佺▼鐘舵��
+        if (StringUtils.isNotBlank(process.getState())) {
+            if (SuspensionState.ACTIVE.toString().equals(process.getState())) {
+                query.active();
+            } else if (SuspensionState.SUSPENDED.toString().equals(process.getState())) {
+                query.suspended();
+            }
+        }
+    }
+
+    /**
+     * 鏋勫缓浠诲姟鎼滅储
+     */
+    public static void buildTaskSearch(TaskQuery query, ProcessQuery process) {
+        Map<String, Object> params = process.getParams();
+        if (StringUtils.isNotBlank(process.getProcessKey())) {
+            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
+        }
+        if (StringUtils.isNotBlank(process.getProcessName())) {
+            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
+        }
+        if (params.get("beginTime") != null && params.get("endTime") != null) {
+            query.taskCreatedAfter(DateUtils.parseDate(params.get("beginTime")));
+            query.taskCreatedBefore(DateUtils.parseDate(params.get("endTime")));
+        }
+        // 娴佺▼鍒嗙被
+        if (StringUtils.isNotBlank(process.getCategory())) {
+            query.processCategoryIn(Collections.singleton(process.getCategory()));
+        }
+    }
+
+    private static void buildHistoricTaskInstanceSearch(HistoricTaskInstanceQuery query, ProcessQuery process) {
+        Map<String, Object> params = process.getParams();
+        if (StringUtils.isNotBlank(process.getProcessKey())) {
+            query.processDefinitionKeyLike("%" + process.getProcessKey() + "%");
+        }
+        if (StringUtils.isNotBlank(process.getProcessName())) {
+            query.processDefinitionNameLike("%" + process.getProcessName() + "%");
+        }
+        if (params.get("beginTime") != null && params.get("endTime") != null) {
+            query.taskCompletedAfter(DateUtils.parseDate(params.get("beginTime")));
+            query.taskCompletedBefore(DateUtils.parseDate(params.get("endTime")));
+        }
+        // 娴佺▼鍒嗙被
+        if (StringUtils.isNotBlank(process.getCategory())) {
+            query.processCategoryIn(Collections.singleton(process.getCategory()));
+        }
+    }
+
+    /**
+     * 鏋勫缓鍘嗗彶娴佺▼瀹炰緥鎼滅储
+     */
+    public static void buildHistoricProcessInstanceSearch(HistoricProcessInstanceQuery query, ProcessQuery process) {
+        Map<String, Object> params = process.getParams();
+        // 娴佺▼鏍囪瘑
+        if (StringUtils.isNotBlank(process.getProcessKey())) {
+            query.processDefinitionKey(process.getProcessKey());
+        }
+        // 娴佺▼鍚嶇О
+        if (StringUtils.isNotBlank(process.getProcessName())) {
+            query.processDefinitionName(process.getProcessName());
+        }
+        // 娴佺▼鍚嶇О
+        if (StringUtils.isNotBlank(process.getCategory())) {
+            query.processDefinitionCategory(process.getCategory());
+        }
+        if (params.get("beginTime") != null && params.get("endTime") != null) {
+            query.startedAfter(DateUtils.parseDate(params.get("beginTime")));
+            query.startedBefore(DateUtils.parseDate(params.get("endTime")));
+        }
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java
new file mode 100644
index 0000000..ecffe18
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/StreamUtils.java
@@ -0,0 +1,253 @@
+package org.ruoyi.flowable.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import org.ruoyi.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 娴佸伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+    /**
+     * 灏哻ollection杩囨护
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   杩囨护鏂规硶
+     * @return 杩囨护鍚庣殑list
+     */
+    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection.stream().filter(function).collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function) {
+        return join(collection, function, ",");
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @param delimiter  鎷兼帴绗�
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+        if (CollUtil.isEmpty(collection)) {
+            return StringUtils.EMPTY;
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+    }
+
+    /**
+     * 灏哻ollection鎺掑簭
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param comparing  鎺掑簭鏂规硶
+     * @return 鎺掑簭鍚庣殑list
+     */
+    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection.stream().sorted(comparing).collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓虹被鍨嬩笉鍙樼殑map<br>
+     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        V绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param <V>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+    }
+
+    /**
+     * 灏咰ollection杞寲涓簃ap(value绫诲瀷涓巆ollection鐨勬硾鍨嬩笉鍚�)<br>
+     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        E绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param value      E绫诲瀷杞寲涓篤绫诲瀷鐨刲ambda鏂规硶
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @param <V>        map涓殑value绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収瑙勫垯(姣斿鏈夌浉鍚岀殑鐝骇id)鍒嗙被鎴恗ap<br>
+     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key        鍒嗙被鐨勮鍒�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream()
+                .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <E>        闆嗗悎鍏冪礌绫诲瀷
+     * @param <K>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream()
+                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <T>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @param <E>        collection涓殑娉涘瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+                .stream()
+                .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篖ist闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  List<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簂ist娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        List涓殑娉涘瀷
+     * @return 杞寲鍚庣殑list
+     */
+    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection
+                .stream()
+                .map(function)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篠et闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  Set<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簊et娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        Set涓殑娉涘瀷
+     * @return 杞寲鍚庣殑Set
+     */
+    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection) || function == null) {
+            return CollUtil.newHashSet();
+        }
+        return collection
+                .stream()
+                .map(function)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+    }
+
+
+    /**
+     * 鍚堝苟涓や釜鐩稿悓key绫诲瀷鐨刴ap
+     *
+     * @param map1  绗竴涓渶瑕佸悎骞剁殑 map
+     * @param map2  绗簩涓渶瑕佸悎骞剁殑 map
+     * @param merge 鍚堝苟鐨刲ambda锛屽皢key  value1 value2鍚堝苟鎴愭渶缁堢殑绫诲瀷,娉ㄦ剰value鍙兘涓虹┖鐨勬儏鍐�
+     * @param <K>   map涓殑key绫诲瀷
+     * @param <X>   绗竴涓� map鐨剉alue绫诲瀷
+     * @param <Y>   绗簩涓� map鐨剉alue绫诲瀷
+     * @param <V>   鏈�缁坢ap鐨剉alue绫诲瀷
+     * @return 鍚堝苟鍚庣殑map
+     */
+    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+        if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+            return MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map1)) {
+            map1 = MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map2)) {
+            map2 = MapUtil.newHashMap();
+        }
+        Set<K> key = new HashSet<>();
+        key.addAll(map1.keySet());
+        key.addAll(map2.keySet());
+        Map<K, V> map = new HashMap<>();
+        for (K t : key) {
+            X x = map1.get(t);
+            Y y = map2.get(t);
+            V z = merge.apply(x, y);
+            if (z != null) {
+                map.put(t, z);
+            }
+        }
+        return map;
+    }
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java
new file mode 100644
index 0000000..bbf3a50
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/utils/TaskUtils.java
@@ -0,0 +1,50 @@
+package org.ruoyi.flowable.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.flowable.common.constant.TaskConstants;
+
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 宸ヤ綔娴佷换鍔″伐鍏风被
+ *
+ * @author konbai
+ * @createTime 2022/4/24 12:42
+ */
+public class TaskUtils {
+    public static String getUserId() {
+
+        LoginUser user = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNotNull(user)) {
+            return  String.valueOf(user.getUserId());
+        }
+
+        return "";
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛缁勪俊鎭�
+     *
+     * @return candidateGroup
+     */
+    public static List<String> getCandidateGroup() {
+        List<String> list = new ArrayList<>();
+        LoginUser user = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNotNull(user)) {
+            if (ObjectUtil.isNotEmpty(user.getRoles())) {
+                user.getRoles().forEach(role -> list.add(TaskConstants.ROLE_GROUP_PREFIX + role.getRoleId() ));
+            }
+            if (ObjectUtil.isNotNull(user.getDeptId())) {
+                list.add(TaskConstants.DEPT_GROUP_PREFIX + user.getDeptId());
+            }
+        }
+
+        return list;
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java
new file mode 100644
index 0000000..72522d6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfCategoryController.java
@@ -0,0 +1,102 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.utils.MapstructUtils;
+import org.ruoyi.common.web.core.BaseController;
+import org.ruoyi.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.WfCategory;
+import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
+import org.ruoyi.flowable.workflow.service.IWfCategoryService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.Arrays;
+import java.util.List;
+import org.ruoyi.common.excel.utils.ExcelUtil;
+
+/**
+ * 娴佺▼鍒嗙被Controller
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/category")
+public class WfCategoryController extends BaseController {
+
+    private final IWfCategoryService categoryService;
+
+    /**
+     * 鏌ヨ娴佺▼鍒嗙被鍒楄〃
+     */
+    @GetMapping("/list")
+    public TableDataInfo<WfCategoryVo> list(WfCategory category) {
+        List<WfCategoryVo> list = categoryService.queryList(category);
+        return TableDataInfo.build(list);
+    }
+
+    /**
+     * 鏌ヨ鍏ㄩ儴鐨勬祦绋嬪垎绫诲垪琛�
+     */
+    @GetMapping("/listAll")
+    public R<List<WfCategoryVo>> listAll(WfCategory category) {
+        return R.ok(categoryService.queryList(category));
+    }
+
+    /**
+     * 瀵煎嚭娴佺▼鍒嗙被鍒楄〃
+     */
+    @PostMapping("/export")
+    public void export(@Validated WfCategory category, HttpServletResponse response) {
+        List<WfCategoryVo> list = categoryService.queryList(category);
+        List<WfCategoryVo> util = MapstructUtils.convert(list,WfCategoryVo.class);
+        ExcelUtil.exportExcel(util, "娴佺▼鍒嗙被", WfCategoryVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍒嗙被璇︾粏淇℃伅
+     * @param categoryId 鍒嗙被涓婚敭
+     */
+    @GetMapping("/{categoryId}")
+    public R<WfCategoryVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable("categoryId") Long categoryId) {
+        return R.ok(categoryService.queryById(categoryId));
+    }
+
+    /**
+     * 鏂板娴佺▼鍒嗙被
+     */
+    @PostMapping
+    public R add(@Validated @RequestBody WfCategory category) {
+        if (!categoryService.checkCategoryCodeUnique(category)) {
+            return R.fail("鏂板娴佺▼鍒嗙被'" + category.getCategoryName() + "'澶辫触锛屾祦绋嬬紪鐮佸凡瀛樺湪");
+        }
+        return R.ok(categoryService.insertCategory(category));
+    }
+
+    /**
+     * 淇敼娴佺▼鍒嗙被
+     */
+    @PutMapping()
+    public R edit(@Validated @RequestBody WfCategory category) {
+        if (!categoryService.checkCategoryCodeUnique(category)) {
+            return R.fail("淇敼娴佺▼鍒嗙被'" + category.getCategoryName() + "'澶辫触锛屾祦绋嬬紪鐮佸凡瀛樺湪");
+        }
+        return toAjax(categoryService.updateCategory(category));
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼鍒嗙被
+     * @param categoryIds 鍒嗙被涓婚敭涓�
+     */
+    @DeleteMapping("/{categoryIds}")
+    public R remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable Long[] categoryIds) {
+        return toAjax(categoryService.deleteWithValidByIds(Arrays.asList(categoryIds), true));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java
new file mode 100644
index 0000000..5e974d7
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfDeployController.java
@@ -0,0 +1,99 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.web.core.BaseController;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.utils.JsonUtils;
+import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
+import org.ruoyi.flowable.workflow.service.IWfDeployService;
+import org.springframework.web.bind.annotation.*;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 娴佺▼閮ㄧ讲
+ *
+ * @author KonBAI
+ * @createTime 2022/3/24 20:57
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/deploy")
+public class WfDeployController extends BaseController {
+
+    private final IWfDeployService deployService;
+    private final IWfDeployFormService deployFormService;
+
+    /**
+     * 鏌ヨ娴佺▼閮ㄧ讲鍒楄〃
+     */
+    @GetMapping("/list")
+    public TableDataInfo<WfDeployVo> list(ProcessQuery processQuery, PageQuery pageQuery) {
+        return deployService.queryPageList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼閮ㄧ讲鐗堟湰鍒楄〃
+     */
+    @GetMapping("/publishList")
+    public TableDataInfo<WfDeployVo> publishList(@RequestParam String processKey, PageQuery pageQuery) {
+        return deployService.queryPublishList(processKey, pageQuery);
+    }
+
+    /**
+     * 婵�娲绘垨鎸傝捣娴佺▼
+     *
+     * @param state 鐘舵�侊紙active:婵�娲� suspended:鎸傝捣锛�
+     * @param definitionId 娴佺▼瀹氫箟ID
+     */
+    @PutMapping(value = "/changeState")
+    public R<Void> changeState(@RequestParam String state, @RequestParam String definitionId) {
+        deployService.updateState(definitionId, state);
+        return R.ok("鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 璇诲彇xml鏂囦欢
+     * @param definitionId 娴佺▼瀹氫箟ID
+     * @return
+     */
+    @GetMapping("/bpmnXml/{definitionId}")
+    public R<String> getBpmnXml(@PathVariable(value = "definitionId") String definitionId) {
+        return R.ok( deployService.queryBpmnXmlById(definitionId),null);
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼妯″瀷
+     * @param deployIds 娴佺▼閮ㄧ讲ids
+     */
+    @DeleteMapping("/{deployIds}")
+    public R<String> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable String[] deployIds) {
+        deployService.deleteByIds(Arrays.asList(deployIds));
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鏌ヨ娴佺▼閮ㄧ讲鍏宠仈琛ㄥ崟淇℃伅
+     *
+     * @param deployId 娴佺▼閮ㄧ讲id
+     */
+    @GetMapping("/form/{deployId}")
+    public R<?> start(@PathVariable(value = "deployId") String deployId) {
+        WfFormVo formVo = deployFormService.selectDeployFormByDeployId(deployId);
+        if (Objects.isNull(formVo)) {
+            return R.fail("璇峰厛閰嶇疆娴佺▼琛ㄥ崟");
+        }
+        return R.ok(JsonUtils.parseObject(formVo.getContent(), Map.class));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java
new file mode 100644
index 0000000..89fdddb
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfFormController.java
@@ -0,0 +1,101 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.utils.MapstructUtils;
+import org.ruoyi.common.core.validate.QueryGroup;
+import org.ruoyi.common.excel.utils.ExcelUtil;
+import org.ruoyi.common.web.core.BaseController;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
+import org.ruoyi.flowable.workflow.service.IWfFormService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 娴佺▼琛ㄥ崟Controller
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/form")
+public class WfFormController extends BaseController {
+
+    private final IWfFormService formService;
+
+    private final IWfDeployFormService deployFormService;
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟鍒楄〃
+     */
+    @GetMapping("/list")
+    public TableDataInfo<WfFormVo> list(@Validated(QueryGroup.class) WfFormBo bo, PageQuery pageQuery) {
+        return formService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭娴佺▼琛ㄥ崟鍒楄〃
+     */
+    @PostMapping("/export")
+    public void export(@Validated WfFormBo bo, HttpServletResponse response) {
+        List<WfFormVo> list = formService.queryList(bo);
+        List<WfFormVo> convert = MapstructUtils.convert(list, WfFormVo.class);
+        ExcelUtil.exportExcel( convert, "娴佺▼琛ㄥ崟", WfFormVo.class, response);
+    }
+
+    /**
+     * 鑾峰彇娴佺▼琛ㄥ崟璇︾粏淇℃伅
+     * @param formId 涓婚敭
+     */
+    @GetMapping(value = "/{formId}")
+    public R getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable("formId") Long formId) {
+        return R.ok(formService.queryById(formId));
+    }
+
+    /**
+     * 鏂板娴佺▼琛ㄥ崟
+     */
+    @PostMapping
+    public R add(@RequestBody WfFormBo bo) {
+        return toAjax(formService.insertForm(bo));
+    }
+
+    /**
+     * 淇敼娴佺▼琛ㄥ崟
+     */
+    @PutMapping
+    public R edit(@RequestBody WfFormBo bo) {
+        return toAjax(formService.updateForm(bo));
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼琛ㄥ崟
+     * @param formIds 涓婚敭涓�
+     */
+    @DeleteMapping("/{formIds}")
+    public R remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable Long[] formIds) {
+        return toAjax(formService.deleteWithValidByIds(Arrays.asList(formIds)) ? 1 : 0);
+    }
+
+
+    /**
+     * 鎸傝浇娴佺▼琛ㄥ崟
+     */
+    @PostMapping("/addDeployForm")
+    public R addDeployForm(@RequestBody WfDeployForm deployForm) {
+        return toAjax(deployFormService.insertWfDeployForm(deployForm));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java
new file mode 100644
index 0000000..76659e6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfInstanceController.java
@@ -0,0 +1,71 @@
+package org.ruoyi.flowable.workflow.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.service.IWfInstanceService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 宸ヤ綔娴佹祦绋嬪疄渚嬬鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/instance")
+public class WfInstanceController {
+
+    private final IWfInstanceService instanceService;
+
+    /**
+     * 婵�娲绘垨鎸傝捣娴佺▼瀹炰緥
+     *
+     * @param state 1:婵�娲�,2:鎸傝捣
+     * @param instanceId 娴佺▼瀹炰緥ID
+     */
+    @PostMapping(value = "/updateState")
+    public R updateState(@RequestParam Integer state, @RequestParam String instanceId) {
+        instanceService.updateState(state, instanceId);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 缁撴潫娴佺▼瀹炰緥
+     *
+     * @param bo 娴佺▼浠诲姟涓氬姟瀵硅薄
+     */
+    @PostMapping(value = "/stopProcessInstance")
+    public R stopProcessInstance(@RequestBody WfTaskBo bo) {
+        instanceService.stopProcessInstance(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼瀹炰緥
+     *
+     * @param instanceId 娴佺▼瀹炰緥ID
+     * @param deleteReason 鍒犻櫎鍘熷洜
+     */
+    @Deprecated
+    @DeleteMapping(value = "/delete")
+    public R delete(@RequestParam String instanceId, String deleteReason) {
+        instanceService.delete(instanceId, deleteReason);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鏌ヨ娴佺▼瀹炰緥璇︽儏淇℃伅
+     *
+     * @param procInsId 娴佺▼瀹炰緥ID
+     * @param deployId 娴佺▼閮ㄧ讲ID
+     */
+    @GetMapping("/detail")
+    public R detail(String procInsId, String deployId) {
+        return R.ok(instanceService.queryDetailProcess(procInsId, deployId));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java
new file mode 100644
index 0000000..a3eb642
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfModelController.java
@@ -0,0 +1,165 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.utils.MapstructUtils;
+import org.ruoyi.common.core.validate.AddGroup;
+import org.ruoyi.common.core.validate.EditGroup;
+import org.ruoyi.common.excel.utils.ExcelUtil;
+import org.ruoyi.common.web.core.BaseController;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.WfCategory;
+import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfModelExportVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
+import org.ruoyi.flowable.workflow.service.IWfCategoryService;
+import org.ruoyi.flowable.workflow.service.IWfModelService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 宸ヤ綔娴佹祦绋嬫ā鍨嬬鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/6/21 9:09
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/model")
+public class WfModelController extends BaseController {
+
+    private final IWfModelService modelService;
+    private final IWfCategoryService categoryService;
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷鍒楄〃
+     *
+     * @param modelBo 娴佺▼妯″瀷瀵硅薄
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping("/list")
+    public TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery) {
+        return modelService.list(modelBo, pageQuery);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷鍒楄〃
+     *
+     * @param modelBo 娴佺▼妯″瀷瀵硅薄
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping("/historyList")
+    public TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery) {
+        return modelService.historyList(modelBo, pageQuery);
+    }
+
+    /**
+     * 鑾峰彇娴佺▼妯″瀷璇︾粏淇℃伅
+     *
+     * @param modelId 妯″瀷涓婚敭
+     */
+    @GetMapping(value = "/{modelId}")
+    public R<WfModelVo> getInfo(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable("modelId") String modelId) {
+        return R.ok(modelService.getModel(modelId));
+    }
+
+    /**
+     * 鑾峰彇娴佺▼琛ㄥ崟璇︾粏淇℃伅
+     *
+     * @param modelId 妯″瀷涓婚敭
+     */
+    @GetMapping(value = "/bpmnXml/{modelId}")
+    public R<String> getBpmnXml(@NotNull(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable("modelId") String modelId) {
+        return R.ok(modelService.queryBpmnXmlById(modelId),"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鏂板娴佺▼妯″瀷
+     */
+    @PostMapping
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody WfModelBo modelBo) {
+        modelService.insertModel(modelBo);
+        return R.ok("鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 淇敼娴佺▼妯″瀷
+     */
+    @PutMapping
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody WfModelBo modelBo) {
+        modelService.updateModel(modelBo);
+        return R.ok("鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 淇濆瓨娴佺▼妯″瀷
+     */
+    @PostMapping("/save")
+    public R<String> save(@RequestBody WfModelBo modelBo) {
+        modelService.saveModel(modelBo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 璁句负鏈�鏂版祦绋嬫ā鍨�
+     * @param modelId
+     * @return
+     */
+    @PostMapping("/latest")
+    public R<?> latest(@RequestParam String modelId) {
+        modelService.latestModel(modelId);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼妯″瀷
+     *
+     * @param modelIds 娴佺▼妯″瀷涓婚敭涓�
+     */
+    @DeleteMapping("/{modelIds}")
+    public R<String> remove(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable String[] modelIds) {
+        modelService.deleteByIds(Arrays.asList(modelIds));
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 閮ㄧ讲娴佺▼妯″瀷
+     *
+     * @param modelId 娴佺▼妯″瀷涓婚敭
+     */
+    @PostMapping("/deploy")
+    public R deployModel(@RequestParam String modelId) {
+        return toAjax(modelService.deployModel(modelId));
+    }
+
+    /**
+     * 瀵煎嚭娴佺▼妯″瀷鏁版嵁
+     */
+    @PostMapping("/export")
+    public void export(WfModelBo modelBo, HttpServletResponse response) {
+        List<WfModelVo> list =  modelService.list(modelBo);
+        List<WfModelExportVo> listVo = BeanUtil.copyToList(list, WfModelExportVo.class);
+        List<WfCategoryVo> categoryVos = categoryService.queryList(new WfCategory());
+        Map<String, String> categoryMap = categoryVos.stream()
+                .collect(Collectors.toMap(WfCategoryVo::getCode, WfCategoryVo::getCategoryName));
+        for (WfModelExportVo exportVo : listVo) {
+            exportVo.setCategoryName(categoryMap.get(exportVo.getCategory()));
+        }
+        List<WfModelExportVo> convert = MapstructUtils.convert(listVo, WfModelExportVo.class);
+        ExcelUtil.exportExcel(convert, "娴佺▼妯″瀷鏁版嵁", WfModelExportVo.class, response);
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java
new file mode 100644
index 0000000..5da16b9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfProcessController.java
@@ -0,0 +1,239 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.utils.MapstructUtils;
+import org.ruoyi.common.excel.utils.ExcelUtil;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.common.web.core.BaseController;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
+import org.ruoyi.flowable.workflow.domain.vo.*;
+import org.ruoyi.flowable.workflow.service.IWfCopyService;
+import org.ruoyi.flowable.workflow.service.IWfProcessService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 宸ヤ綔娴佹祦绋嬬鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/24 18:54
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/process")
+public class WfProcessController extends BaseController {
+
+    private final IWfProcessService processService;
+    private final IWfCopyService copyService;
+
+    /**
+     * 鏌ヨ鍙彂璧锋祦绋嬪垪琛�
+     *
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping(value = "/list")
+    public TableDataInfo<WfDefinitionVo> startProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        return processService.selectPageStartProcessList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鎴戞嫢鏈夌殑娴佺▼
+     */
+    @GetMapping(value = "/ownList")
+    public TableDataInfo<WfTaskVo> ownProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        return processService.selectPageOwnProcessList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鑾峰彇寰呭姙鍒楄〃
+     */
+    @GetMapping(value = "/todoList")
+    public TableDataInfo<WfTaskVo> todoProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        return processService.selectPageTodoProcessList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鑾峰彇寰呯鍒楄〃
+     *
+     * @param processQuery 娴佺▼涓氬姟瀵硅薄
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping(value = "/claimList")
+    public TableDataInfo<WfTaskVo> claimProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        return processService.selectPageClaimProcessList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鑾峰彇宸插姙鍒楄〃
+     *
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping(value = "/finishedList")
+    public TableDataInfo<WfTaskVo> finishedProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        return processService.selectPageFinishedProcessList(processQuery, pageQuery);
+    }
+
+    /**
+     * 鑾峰彇鎶勯�佸垪琛�
+     *
+     * @param copyBo 娴佺▼鎶勯�佸璞�
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    @GetMapping(value = "/copyList")
+    public TableDataInfo<WfCopyVo> copyProcessList(WfCopyBo copyBo, PageQuery pageQuery) {
+        // 鑾峰彇褰撳墠鐨勭敤鎴�
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNotNull(loginUser))
+        {
+            copyBo.setUserId(loginUser.getUserId());
+        }
+        return copyService.selectPageList(copyBo, pageQuery);
+    }
+
+    /**
+     * 瀵煎嚭鍙彂璧锋祦绋嬪垪琛�
+     */
+    @PostMapping("/startExport")
+    public void startExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
+        List<WfDefinitionVo> list = processService.selectStartProcessList(processQuery);
+
+        List<WfDefinitionVo> convert = MapstructUtils.convert(list, WfDefinitionVo.class);
+        ExcelUtil.exportExcel(convert, "鍙彂璧锋祦绋�", WfDefinitionVo.class, response);
+    }
+
+    /**
+     * 瀵煎嚭鎴戞嫢鏈夋祦绋嬪垪琛�
+     */
+    @PostMapping("/ownExport")
+    public void ownExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
+        List<WfTaskVo> list = processService.selectOwnProcessList(processQuery);
+        List<WfOwnTaskExportVo> listVo = BeanUtil.copyToList(list, WfOwnTaskExportVo.class);
+        for (WfOwnTaskExportVo exportVo : listVo) {
+            exportVo.setStatus(ObjectUtil.isNull(exportVo.getFinishTime()) ? "杩涜涓�" : "宸插畬鎴�");
+        }
+        List<WfOwnTaskExportVo> convert = MapstructUtils.convert(listVo, WfOwnTaskExportVo.class);
+        ExcelUtil.exportExcel(convert, "鎴戞嫢鏈夌殑娴佺▼", WfOwnTaskExportVo.class, response);
+    }
+
+    /**
+     * 瀵煎嚭寰呭姙娴佺▼鍒楄〃
+     */
+    @PostMapping("/todoExport")
+    public void todoExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
+        List<WfTaskVo> list = processService.selectTodoProcessList(processQuery);
+        List<WfTodoTaskExportVo> listVo = BeanUtil.copyToList(list, WfTodoTaskExportVo.class);
+        List<WfTodoTaskExportVo> convert = MapstructUtils.convert(listVo, WfTodoTaskExportVo.class);
+        ExcelUtil.exportExcel(convert, "寰呭姙娴佺▼", WfTodoTaskExportVo.class, response);
+    }
+
+    /**
+     * 瀵煎嚭寰呯娴佺▼鍒楄〃
+     */
+    @PostMapping("/claimExport")
+    public void claimExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
+        List<WfTaskVo> list = processService.selectClaimProcessList(processQuery);
+        List<WfClaimTaskExportVo> listVo = BeanUtil.copyToList(list, WfClaimTaskExportVo.class);
+
+        List<WfClaimTaskExportVo> convert = MapstructUtils.convert(listVo, WfClaimTaskExportVo.class);
+        ExcelUtil.exportExcel(convert, "寰呯娴佺▼", WfClaimTaskExportVo.class, response);
+    }
+
+    /**
+     * 瀵煎嚭宸插姙娴佺▼鍒楄〃
+     */
+    @PostMapping("/finishedExport")
+    public void finishedExport(@Validated ProcessQuery processQuery, HttpServletResponse response) {
+        List<WfTaskVo> list = processService.selectFinishedProcessList(processQuery);
+        List<WfFinishedTaskExportVo> listVo = BeanUtil.copyToList(list, WfFinishedTaskExportVo.class);
+        List<WfFinishedTaskExportVo> convert = MapstructUtils.convert(listVo, WfFinishedTaskExportVo.class);
+        ExcelUtil.exportExcel(convert, "宸插姙娴佺▼", WfFinishedTaskExportVo.class, response);
+
+    }
+
+    /**
+     * 瀵煎嚭鎶勯�佹祦绋嬪垪琛�
+     */
+    @PostMapping("/copyExport")
+    public void copyExport(WfCopyBo copyBo, HttpServletResponse response) {
+        // 鑾峰彇褰撳墠鐨勭敤鎴�
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        if (ObjectUtil.isNotNull(loginUser))
+        {
+            copyBo.setUserId(loginUser.getUserId());
+        }
+        List<WfCopyVo> list = copyService.selectList(copyBo);
+        List<WfCopyVo> convert = MapstructUtils.convert(list, WfCopyVo.class);
+        ExcelUtil.exportExcel(convert, "鎶勯�佹祦绋�", WfCopyVo.class, response);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼閮ㄧ讲鍏宠仈琛ㄥ崟淇℃伅
+     *
+     * @param definitionId 娴佺▼瀹氫箟id
+     * @param deployId 娴佺▼閮ㄧ讲id
+     */
+    @GetMapping("/getProcessForm")
+    public R<?> getForm(@RequestParam(value = "definitionId") String definitionId,
+                        @RequestParam(value = "deployId") String deployId,
+                        @RequestParam(value = "procInsId", required = false) String procInsId) {
+        return R.ok(processService.selectFormContent(definitionId, deployId, procInsId));
+    }
+
+    /**
+     * 鏍规嵁娴佺▼瀹氫箟id鍚姩娴佺▼瀹炰緥
+     *
+     * @param processDefId 娴佺▼瀹氫箟id
+     * @param variables 鍙橀噺闆嗗悎,json瀵硅薄
+     */
+    @PostMapping("/start/{processDefId}")
+    public R start(@PathVariable(value = "processDefId") String processDefId, @RequestBody Map<String, Object> variables) {
+        processService.startProcessByDefId(processDefId, variables);
+        return R.ok("娴佺▼鍚姩鎴愬姛");
+
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼瀹炰緥
+     *
+     * @param instanceIds 娴佺▼瀹炰緥ID涓�
+     */
+    @DeleteMapping("/instance/{instanceIds}")
+    public R<Void> delete(@PathVariable String[] instanceIds) {
+        processService.deleteProcessByIds(instanceIds);
+        return R.ok("鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 璇诲彇xml鏂囦欢
+     * @param processDefId 娴佺▼瀹氫箟ID
+     */
+    @GetMapping("/bpmnXml/{processDefId}")
+    public R<String> getBpmnXml(@PathVariable(value = "processDefId") String processDefId) {
+        return R.ok(processService.queryBpmnXmlById(processDefId),null );
+    }
+
+    /**
+     * 鏌ヨ娴佺▼璇︽儏淇℃伅
+     *
+     * @param procInsId 娴佺▼瀹炰緥ID
+     * @param taskId 浠诲姟ID
+     */
+    @GetMapping("/detail")
+    public R detail(String procInsId, String taskId) {
+        return R.ok(processService.queryProcessDetail(procInsId, taskId));
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java
new file mode 100644
index 0000000..3ebd7fe
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/controller/WfTaskController.java
@@ -0,0 +1,178 @@
+package org.ruoyi.flowable.workflow.controller;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.service.IWfTaskService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * 宸ヤ綔娴佷换鍔$鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/task")
+public class WfTaskController {
+
+    private final IWfTaskService flowTaskService;
+
+    /**
+     * 鍙栨秷娴佺▼
+     */
+    @PostMapping(value = "/stopProcess")
+    public R stopProcess(@RequestBody WfTaskBo bo) {
+        flowTaskService.stopProcess(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鎾ゅ洖娴佺▼
+     */
+    @PostMapping(value = "/revokeProcess")
+    public R revokeProcess(@RequestBody WfTaskBo bo) {
+        flowTaskService.revokeProcess(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍙橀噺
+     * @param taskId 娴佺▼浠诲姟Id
+     */
+    @GetMapping(value = "/processVariables/{taskId}")
+    public R processVariables(@PathVariable(value = "taskId") String taskId) {
+        return R.ok(flowTaskService.getProcessVariables(taskId));
+    }
+
+    /**
+     * 瀹℃壒浠诲姟
+     */
+    @PostMapping(value = "/complete")
+    public R complete(@RequestBody WfTaskBo bo) {
+        flowTaskService.complete(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鎷掔粷浠诲姟
+     */
+    @PostMapping(value = "/reject")
+    public R taskReject(@RequestBody WfTaskBo taskBo) {
+        flowTaskService.taskReject(taskBo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 閫�鍥炰换鍔�
+     */
+    @PostMapping(value = "/return")
+    public R taskReturn(@RequestBody WfTaskBo bo) {
+        flowTaskService.taskReturn(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鑾峰彇鎵�鏈夊彲鍥為��鐨勮妭鐐�
+     */
+    @PostMapping(value = "/returnList")
+    public R findReturnTaskList(@RequestBody WfTaskBo bo) {
+        return R.ok(flowTaskService.findReturnTaskList(bo));
+    }
+
+    /**
+     * 鍒犻櫎浠诲姟
+     */
+    @DeleteMapping(value = "/delete")
+    public R delete(@RequestBody WfTaskBo bo) {
+        flowTaskService.deleteTask(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 璁ら/绛炬敹浠诲姟
+     */
+    @PostMapping(value = "/claim")
+    public R claim(@RequestBody WfTaskBo bo) {
+        flowTaskService.claim(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鍙栨秷璁ら/绛炬敹浠诲姟
+     */
+    @PostMapping(value = "/unClaim")
+    public R unClaim(@RequestBody WfTaskBo bo) {
+        flowTaskService.unClaim(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 濮旀淳浠诲姟
+     */
+    @PostMapping(value = "/delegate")
+    public R delegate(@RequestBody WfTaskBo bo) {
+        if (ObjectUtil.hasNull(bo.getTaskId(), bo.getUserId())) {
+            return R.fail("鍙傛暟閿欒锛�");
+        }
+        flowTaskService.delegateTask(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 杞姙浠诲姟
+     */
+    @PostMapping(value = "/transfer")
+    public R transfer(@RequestBody WfTaskBo bo) {
+        if (ObjectUtil.hasNull(bo.getTaskId(), bo.getUserId())) {
+            return R.fail("鍙傛暟閿欒锛�");
+        }
+        flowTaskService.transferTask(bo);
+        return R.ok(null,"鎿嶄綔鎴愬姛");
+    }
+
+    /**
+     * 鐢熸垚娴佺▼鍥�
+     *
+     * @param processId 浠诲姟ID
+     */
+    @RequestMapping("/diagram/{processId}")
+    public void genProcessDiagram(HttpServletResponse response,
+                                  @PathVariable("processId") String processId) {
+        InputStream inputStream = flowTaskService.diagram(processId);
+        OutputStream os = null;
+        BufferedImage image = null;
+        try {
+            image = ImageIO.read(inputStream);
+            response.setContentType("image/png");
+            os = response.getOutputStream();
+            if (image != null) {
+                ImageIO.write(image, "png", os);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (os != null) {
+                    os.flush();
+                    os.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java
new file mode 100644
index 0000000..e53bb41
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCategory.java
@@ -0,0 +1,52 @@
+package org.ruoyi.flowable.workflow.domain;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
+/**
+ * 娴佺▼鍒嗙被瀵硅薄 wf_category
+ *
+ * @author KonBAI
+ * @date 2022-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("wf_category")
+public class WfCategory extends BaseEntity {
+
+    private static final long serialVersionUID=1L;
+
+    /**
+     * 鍒嗙被ID
+     */
+    @TableId(value = "category_id", type = IdType.AUTO)
+    private Long categoryId;
+    /**
+     * 鍒嗙被鍚嶇О
+     */
+    @NotBlank(message = "鍒嗙被鍚嶇О涓嶈兘涓虹┖")
+    private String categoryName;
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @NotBlank(message = "鍒嗙被缂栫爜涓嶈兘涓虹┖")
+    private String code;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+    /**
+     * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�
+     */
+    @TableLogic
+    private String delFlag;
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java
new file mode 100644
index 0000000..a456073
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfCopy.java
@@ -0,0 +1,78 @@
+package org.ruoyi.flowable.workflow.domain;
+
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
+/**
+ * 娴佺▼鎶勯�佸璞� wf_copy
+ *
+ * @author KonBAI
+ * @date 2022-05-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("wf_copy")
+public class WfCopy extends BaseEntity {
+
+    private static final long serialVersionUID=1L;
+
+    /**
+     * 鎶勯�佷富閿�
+     */
+    @TableId(value = "copy_id", type = IdType.AUTO)
+    private Long copyId;
+    /**
+     * 鎶勯�佹爣棰�
+     */
+    private String title;
+    /**
+     * 娴佺▼涓婚敭
+     */
+    private String processId;
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    private String processName;
+    /**
+     * 娴佺▼鍒嗙被涓婚敭
+     */
+    private String categoryId;
+    /**
+     * 閮ㄧ讲涓婚敭
+     */
+    private String deploymentId;
+    /**
+     * 娴佺▼瀹炰緥涓婚敭
+     */
+    private String instanceId;
+    /**
+     * 浠诲姟涓婚敭
+     */
+    private String taskId;
+    /**
+     * 鐢ㄦ埛涓婚敭
+     */
+    private Long userId;
+    /**
+     * 鍙戣捣浜篒d
+     */
+    private Long originatorId;
+    /**
+     * 鍙戣捣浜哄悕绉�
+     */
+    private String originatorName;
+    /**
+     * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�
+     */
+    @TableLogic
+    private String delFlag;
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java
new file mode 100644
index 0000000..061b16e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfDeployForm.java
@@ -0,0 +1,48 @@
+package org.ruoyi.flowable.workflow.domain;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟瀵硅薄 sys_instance_form
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Data
+@TableName("wf_deploy_form")
+public class WfDeployForm {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼閮ㄧ讲涓婚敭
+     */
+    private String deployId;
+
+    /**
+     * 琛ㄥ崟Key
+     */
+    private String formKey;
+
+    /**
+     * 鑺傜偣Key
+     */
+    private String nodeKey;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    private String formName;
+
+    /**
+     * 鑺傜偣鍚嶇О
+     */
+    private String nodeName;
+
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    private String content;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java
new file mode 100644
index 0000000..3d634a5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/WfForm.java
@@ -0,0 +1,44 @@
+package org.ruoyi.flowable.workflow.domain;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
+/**
+ * 娴佺▼琛ㄥ崟瀵硅薄 wf_form
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("wf_form")
+public class WfForm extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 琛ㄥ崟涓婚敭
+     */
+    @TableId(value = "form_id", type = IdType.AUTO)
+    private Long formId;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    private String formName;
+
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    private String content;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java
new file mode 100644
index 0000000..3eb62ba
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/base/BaseEntity.java
@@ -0,0 +1,123 @@
+package org.ruoyi.flowable.workflow.domain.base;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity鍩虹被
+ *
+ * @author ruoyi
+ */
+public class BaseEntity implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 鎼滅储鍊� */
+    @JsonIgnore
+    @TableField(exist = false)
+    private String searchValue;
+
+    /** 鍒涘缓鑰� */
+    private String createBy;
+
+    /** 鍒涘缓鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 鏇存柊鑰� */
+    private String updateBy;
+
+    /** 鏇存柊鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 澶囨敞 */
+    private String remark;
+
+    /** 璇锋眰鍙傛暟 */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    @TableField(exist = false)
+    private Map<String, Object> params;
+
+    public String getSearchValue()
+    {
+        return searchValue;
+    }
+
+    public void setSearchValue(String searchValue)
+    {
+        this.searchValue = searchValue;
+    }
+
+    public String getCreateBy()
+    {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy)
+    {
+        this.createBy = createBy;
+    }
+
+    public Date getCreateTime()
+    {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime)
+    {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateBy()
+    {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy)
+    {
+        this.updateBy = updateBy;
+    }
+
+    public Date getUpdateTime()
+    {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime)
+    {
+        this.updateTime = updateTime;
+    }
+
+    public String getRemark()
+    {
+        return remark;
+    }
+
+    public void setRemark(String remark)
+    {
+        this.remark = remark;
+    }
+
+    public Map<String, Object> getParams()
+    {
+        if (params == null)
+        {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+
+    public void setParams(Map<String, Object> params)
+    {
+        this.params = params;
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java
new file mode 100644
index 0000000..b3b18cc
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfCopyBo.java
@@ -0,0 +1,76 @@
+package org.ruoyi.flowable.workflow.domain.bo;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.common.core.validate.AddGroup;
+import org.ruoyi.common.core.validate.EditGroup;
+import org.ruoyi.core.domain.BaseEntity;
+
+/**
+ * 娴佺▼鎶勯�佷笟鍔″璞� wf_copy
+ *
+ * @author ruoyi
+ * @date 2022-05-19
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WfCopyBo extends BaseEntity {
+
+    /**
+     * 鎶勯�佷富閿�
+     */
+    @NotNull(message = "鎶勯�佷富閿笉鑳戒负绌�", groups = { EditGroup.class })
+    private Long copyId;
+
+    /**
+     * 鎶勯�佹爣棰�
+     */
+    @NotNull(message = "鎶勯�佹爣棰樹笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+    private String title;
+
+    /**
+     * 娴佺▼涓婚敭
+     */
+    @NotBlank(message = "娴佺▼涓婚敭涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String processId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @NotBlank(message = "娴佺▼鍚嶇О涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String processName;
+
+    /**
+     * 娴佺▼鍒嗙被涓婚敭
+     */
+    @NotBlank(message = "娴佺▼鍒嗙被涓婚敭涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String categoryId;
+
+    /**
+     * 浠诲姟涓婚敭
+     */
+    @NotBlank(message = "浠诲姟涓婚敭涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String taskId;
+
+    /**
+     * 鐢ㄦ埛涓婚敭
+     */
+    @NotNull(message = "鐢ㄦ埛涓婚敭涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private Long userId;
+
+    /**
+     * 鍙戣捣浜篒d
+     */
+    @NotNull(message = "鍙戣捣浜轰富閿笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+    private Long originatorId;
+    /**
+     * 鍙戣捣浜哄悕绉�
+     */
+    @NotNull(message = "鍙戣捣浜哄悕绉颁笉鑳戒负绌�", groups = { AddGroup.class, EditGroup.class })
+    private String originatorName;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java
new file mode 100644
index 0000000..3e5272d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfDesignerBo.java
@@ -0,0 +1,38 @@
+package org.ruoyi.flowable.workflow.domain.bo;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.ruoyi.common.core.validate.AddGroup;
+import org.ruoyi.common.core.validate.EditGroup;
+
+
+/**
+ * 娴佺▼璁捐涓氬姟瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Data
+public class WfDesignerBo {
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @NotNull(message = "娴佺▼鍚嶇О", groups = { AddGroup.class, EditGroup.class })
+    private String name;
+
+    /**
+     * 娴佺▼鍒嗙被
+     */
+    @NotBlank(message = "娴佺▼鍒嗙被", groups = { AddGroup.class, EditGroup.class })
+    private String category;
+
+    /**
+     * XML瀛楃涓�
+     */
+    @NotBlank(message = "XML瀛楃涓�", groups = { AddGroup.class, EditGroup.class })
+    private String xml;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java
new file mode 100644
index 0000000..d687cff
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfFormBo.java
@@ -0,0 +1,46 @@
+package org.ruoyi.flowable.workflow.domain.bo;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.common.core.validate.AddGroup;
+import org.ruoyi.common.core.validate.EditGroup;
+import org.ruoyi.core.domain.BaseEntity;
+
+/**
+ * 娴佺▼琛ㄥ崟涓氬姟瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WfFormBo extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 琛ㄥ崟涓婚敭
+     */
+    @NotNull(message = "琛ㄥ崟ID涓嶈兘涓虹┖", groups = { EditGroup.class })
+    private Long formId;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    @NotBlank(message = "琛ㄥ崟鍚嶇О涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String formName;
+
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    @NotBlank(message = "琛ㄥ崟鍐呭涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String content;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java
new file mode 100644
index 0000000..d67e02f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfModelBo.java
@@ -0,0 +1,59 @@
+package org.ruoyi.flowable.workflow.domain.bo;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.ruoyi.common.core.validate.AddGroup;
+import org.ruoyi.common.core.validate.EditGroup;
+
+/**
+ * 娴佺▼妯″瀷瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/6/21 9:16
+ */
+@Data
+public class WfModelBo {
+    /**
+     * 妯″瀷涓婚敭
+     */
+    @NotNull(message = "妯″瀷涓婚敭涓嶈兘涓虹┖", groups = { EditGroup.class })
+    private String modelId;
+    /**
+     * 妯″瀷鍚嶇О
+     */
+    @NotNull(message = "妯″瀷鍚嶇О涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String modelName;
+    /**
+     * 妯″瀷Key
+     */
+    @NotNull(message = "妯″瀷Key涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String modelKey;
+    /**
+     * 娴佺▼鍒嗙被
+     */
+    @NotBlank(message = "娴佺▼鍒嗙被涓嶈兘涓虹┖", groups = { AddGroup.class, EditGroup.class })
+    private String category;
+    /**
+     * 鎻忚堪
+     */
+    private String description;
+    /**
+     * 琛ㄥ崟绫诲瀷
+     */
+    private Integer formType;
+    /**
+     * 琛ㄥ崟涓婚敭
+     */
+    private Long formId;
+    /**
+     * 娴佺▼xml
+     */
+    private String bpmnXml;
+    /**
+     * 鏄惁淇濆瓨涓烘柊鐗堟湰
+     */
+    private Boolean newVersion;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java
new file mode 100644
index 0000000..90bd826
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/bo/WfTaskBo.java
@@ -0,0 +1,65 @@
+package org.ruoyi.flowable.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼浠诲姟涓氬姟瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Data
+public class WfTaskBo {
+    /**
+     * 浠诲姟Id
+     */
+    private String taskId;
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    private String taskName;
+    /**
+     * 鐢ㄦ埛Id
+     */
+    private String userId;
+    /**
+     * 浠诲姟鎰忚
+     */
+    private String comment;
+    /**
+     * 娴佺▼瀹炰緥Id
+     */
+    private String procInsId;
+    /**
+     * 鑺傜偣
+     */
+    private String targetKey;
+    /**
+     * 娴佺▼鍙橀噺淇℃伅
+     */
+    private Map<String, Object> variables;
+    /**
+     * 瀹℃壒浜�
+     */
+    private String assignee;
+    /**
+     * 鍊欓�変汉
+     */
+    private List<String> candidateUsers;
+    /**
+     * 瀹℃壒缁�
+     */
+    private List<String> candidateGroups;
+    /**
+     * 鎶勯�佺敤鎴稩d
+     */
+    private String copyUserIds;
+    /**
+     * 涓嬩竴鑺傜偣瀹℃壒浜�
+     */
+    private String nextUserIds;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java
new file mode 100644
index 0000000..6669d12
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfCommentDto.java
@@ -0,0 +1,27 @@
+package org.ruoyi.flowable.workflow.domain.dto;
+
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Data
+@Builder
+public class WfCommentDto implements Serializable {
+
+    /**
+     * 鎰忚绫诲埆 0 姝e父鎰忚  1 閫�鍥炴剰瑙� 2 椹冲洖鎰忚
+     */
+    private String type;
+
+    /**
+     * 鎰忚鍐呭
+     */
+    private String comment;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java
new file mode 100644
index 0000000..0381229
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfMetaInfoDto.java
@@ -0,0 +1,32 @@
+package org.ruoyi.flowable.workflow.domain.dto;
+
+
+import lombok.Data;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/21 9:16
+ */
+@Data
+public class WfMetaInfoDto {
+
+    /**
+     * 鍒涘缓鑰咃紙username锛�
+     */
+    private String createUser;
+
+    /**
+     * 娴佺▼鎻忚堪
+     */
+    private String description;
+    /**
+     * 琛ㄥ崟绫诲瀷
+     */
+    private Integer formType;
+    /**
+     * 琛ㄥ崟缂栧彿
+     */
+    private Long formId;
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java
new file mode 100644
index 0000000..976a666
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/dto/WfNextDto.java
@@ -0,0 +1,26 @@
+package org.ruoyi.flowable.workflow.domain.dto;
+
+
+import lombok.Data;
+import org.ruoyi.system.domain.SysRole;
+import org.ruoyi.system.domain.SysUser;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 鍔ㄦ�佷汉鍛樸�佺粍
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Data
+public class WfNextDto implements Serializable {
+
+    private String type;
+
+    private String vars;
+
+    private List<SysUser> userList;
+
+    private List<SysRole> roleList;
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java
new file mode 100644
index 0000000..6e978d3
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCategoryVo.java
@@ -0,0 +1,48 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+
+
+/**
+ * 娴佺▼鍒嗙被瑙嗗浘瀵硅薄 flow_category
+ *
+ * @author KonBAI
+ * @date 2022-01-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfCategoryVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鍒嗙被ID
+     */
+    @ExcelProperty(value = "鍒嗙被ID")
+    private Long categoryId;
+
+    /**
+     * 鍒嗙被鍚嶇О
+     */
+    @ExcelProperty(value = "鍒嗙被鍚嶇О")
+    private String categoryName;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @ExcelProperty(value = "鍒嗙被缂栫爜")
+    private String code;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String remark;
+
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java
new file mode 100644
index 0000000..946c9d9
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfClaimTaskExportVo.java
@@ -0,0 +1,59 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 寰呯娴佺▼瀵硅薄瀵煎嚭VO
+ *
+ * @author konbai
+ */
+@Data
+@NoArgsConstructor
+public class WfClaimTaskExportVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟缂栧彿
+     */
+    @ExcelProperty(value = "浠诲姟缂栧彿")
+    private String taskId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String procDefName;
+
+    /**
+     * 浠诲姟鑺傜偣
+     */
+    @ExcelProperty(value = "浠诲姟鑺傜偣")
+    private String taskName;
+
+    /**
+     * 娴佺▼鐗堟湰
+     */
+    @ExcelProperty(value = "娴佺▼鐗堟湰")
+    private int procDefVersion;
+
+    /**
+     * 娴佺▼鍙戣捣浜哄悕绉�
+     */
+    @ExcelProperty(value = "娴佺▼鍙戣捣浜�")
+    private String startUserName;
+
+    /**
+     * 鎺ユ敹鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "鎺ユ敹鏃堕棿")
+    private Date createTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java
new file mode 100644
index 0000000..475cbd6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCommentVo.java
@@ -0,0 +1,33 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 娴佺▼鎵瑰瑙嗗浘瀵硅薄
+ *
+ * @author konbai
+ * @createTime 2022/4/4 02:03
+ */
+@Data
+public class WfCommentVo {
+
+    /**
+     * 瀹℃壒绫诲埆
+     */
+    private String type;
+
+    /**
+     * 鎵瑰鍐呭
+     */
+    private String message;
+
+    /**
+     * 鎵瑰鏃堕棿
+     */
+    private Date time;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java
new file mode 100644
index 0000000..fa26a96
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfCopyVo.java
@@ -0,0 +1,94 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+/**
+ * 娴佺▼鎶勯�佽鍥惧璞� wf_copy
+ *
+ * @author ruoyi
+ * @date 2022-05-19
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfCopyVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎶勯�佷富閿�
+     */
+    @ExcelProperty(value = "鎶勯�佷富閿�")
+    private Long copyId;
+
+    /**
+     * 鎶勯�佹爣棰�
+     */
+    @ExcelProperty(value = "鎶勯�佹爣棰�")
+    private String title;
+
+    /**
+     * 娴佺▼涓婚敭
+     */
+    @ExcelProperty(value = "娴佺▼涓婚敭")
+    private String processId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String processName;
+
+    /**
+     * 娴佺▼鍒嗙被涓婚敭
+     */
+    @ExcelProperty(value = "娴佺▼鍒嗙被涓婚敭")
+    private String categoryId;
+
+    /**
+     * 閮ㄧ讲涓婚敭
+     */
+    @ExcelProperty(value = "閮ㄧ讲涓婚敭")
+    private String deploymentId;
+
+    /**
+     * 娴佺▼瀹炰緥涓婚敭
+     */
+    @ExcelProperty(value = "娴佺▼瀹炰緥涓婚敭")
+    private String instanceId;
+
+    /**
+     * 浠诲姟涓婚敭
+     */
+    @ExcelProperty(value = "浠诲姟涓婚敭")
+    private String taskId;
+
+    /**
+     * 鐢ㄦ埛涓婚敭
+     */
+    @ExcelProperty(value = "鐢ㄦ埛涓婚敭")
+    private Long userId;
+
+    /**
+     * 鍙戣捣浜篒d
+     */
+    @ExcelProperty(value = "鍙戣捣浜轰富閿�")
+    private Long originatorId;
+
+    /**
+     * 鍙戣捣浜哄悕绉�
+     */
+    @ExcelProperty(value = "鍙戣捣浜哄悕绉�")
+    private String originatorName;
+
+    /**
+     * 鎶勯�佹椂闂达紙鍒涘缓鏃堕棿锛�
+     */
+    @ExcelProperty(value = "鎶勯�佹椂闂�")
+    private Date createTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java
new file mode 100644
index 0000000..313519b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDefinitionVo.java
@@ -0,0 +1,83 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+
+/**
+ * 娴佺▼瀹氫箟瑙嗗浘瀵硅薄 workflow_definition
+ *
+ * @author KonBAI
+ * @date 2022-01-17
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfDefinitionVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹氫箟ID
+     */
+    @ExcelProperty(value = "娴佺▼瀹氫箟ID")
+    private String definitionId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String processName;
+
+    /**
+     * 娴佺▼Key
+     */
+    @ExcelProperty(value = "娴佺▼Key")
+    private String processKey;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @ExcelProperty(value = "鍒嗙被缂栫爜")
+    private String category;
+
+    /**
+     * 鐗堟湰
+     */
+    @ExcelProperty(value = "鐗堟湰")
+    private Integer version;
+
+    /**
+     * 琛ㄥ崟ID
+     */
+    @ExcelProperty(value = "琛ㄥ崟ID")
+    private Long formId;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    @ExcelProperty(value = "琛ㄥ崟鍚嶇О")
+    private String formName;
+
+    /**
+     * 閮ㄧ讲ID
+     */
+    @ExcelProperty(value = "閮ㄧ讲ID")
+    private String deploymentId;
+
+    /**
+     * 娴佺▼鏄惁鏆傚仠锛坱rue:鎸傝捣 false:婵�娲� 锛�
+     */
+    @ExcelProperty(value = "娴佺▼鏄惁鎸傝捣")
+    private Boolean suspended;
+
+    /**
+     * 閮ㄧ讲鏃堕棿
+     */
+    @ExcelProperty(value = "閮ㄧ讲鏃堕棿")
+    private Date deploymentTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java
new file mode 100644
index 0000000..6220768
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployFormVo.java
@@ -0,0 +1,47 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import lombok.Data;
+
+/**
+ * 閮ㄧ讲瀹炰緥鍜岃〃鍗曞叧鑱旇鍥惧璞�
+ *
+ * @author KonBAI
+ * @createTime 2022/7/17 18:29
+ */
+@Data
+public class WfDeployFormVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼閮ㄧ讲涓婚敭
+     */
+    private String deployId;
+
+    /**
+     * 琛ㄥ崟Key
+     */
+    private String formKey;
+
+    /**
+     * 鑺傜偣Key
+     */
+    private String nodeKey;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    private String formName;
+
+    /**
+     * 鑺傜偣鍚嶇О
+     */
+    private String nodeName;
+
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    private String content;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java
new file mode 100644
index 0000000..eae572a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDeployVo.java
@@ -0,0 +1,81 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+
+/**
+ * 娴佺▼閮ㄧ讲瑙嗗浘瀵硅薄
+ *
+ * @author KonBAI
+ * @date 2022-06-30
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfDeployVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹氫箟ID
+     */
+    @ExcelProperty(value = "娴佺▼瀹氫箟ID")
+    private String definitionId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String processName;
+
+    /**
+     * 娴佺▼Key
+     */
+    @ExcelProperty(value = "娴佺▼Key")
+    private String processKey;
+
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @ExcelProperty(value = "鍒嗙被缂栫爜")
+    private String category;
+
+    /**
+     * 鐗堟湰
+     */
+    private Integer version;
+
+    /**
+     * 琛ㄥ崟ID
+     */
+    @ExcelProperty(value = "琛ㄥ崟ID")
+    private Long formId;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    @ExcelProperty(value = "琛ㄥ崟鍚嶇О")
+    private String formName;
+
+    /**
+     * 閮ㄧ讲ID
+     */
+    @ExcelProperty(value = "閮ㄧ讲ID")
+    private String deploymentId;
+
+    /**
+     * 娴佺▼瀹氫箟鐘舵��: 1:婵�娲� , 2:涓
+     */
+    @ExcelProperty(value = "娴佺▼瀹氫箟鐘舵��: 1:婵�娲� , 2:涓")
+    private Boolean suspended;
+
+    /**
+     * 閮ㄧ讲鏃堕棿
+     */
+    @ExcelProperty(value = "閮ㄧ讲鏃堕棿")
+    private Date deploymentTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java
new file mode 100644
index 0000000..f5095d4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfDetailVo.java
@@ -0,0 +1,49 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Data;
+import org.ruoyi.flowable.core.FormConf;
+
+import java.util.List;
+
+/**
+ * 娴佺▼璇︽儏瑙嗗浘瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/8/7 15:01
+ */
+@Data
+public class WfDetailVo {
+
+    /**
+     * 浠诲姟琛ㄥ崟淇℃伅
+     */
+    private FormConf taskFormData;
+
+    /**
+     * 鍘嗗彶娴佺▼鑺傜偣淇℃伅
+     */
+    private List<WfProcNodeVo> historyProcNodeList;
+
+    /**
+     * 娴佺▼琛ㄥ崟鍒楄〃
+     */
+    private List<FormConf> processFormList;
+
+    /**
+     * 娴佺▼XML
+     */
+    private String bpmnXml;
+
+    private WfViewerVo flowViewer;
+
+    /**
+     * 鏄惁瀛樺湪浠诲姟琛ㄥ崟淇℃伅
+     * @return true:瀛樺湪锛沠alse:涓嶅瓨鍦�
+     */
+    public Boolean isExistTaskForm() {
+        return ObjectUtil.isNotEmpty(this.taskFormData);
+    }
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java
new file mode 100644
index 0000000..273deb1
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFinishedTaskExportVo.java
@@ -0,0 +1,71 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 宸插姙娴佺▼瀵硅薄瀵煎嚭VO
+ *
+ * @author konbai
+ */
+@Data
+@NoArgsConstructor
+public class WfFinishedTaskExportVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟缂栧彿
+     */
+    @ExcelProperty(value = "浠诲姟缂栧彿")
+    private String taskId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String procDefName;
+
+    /**
+     * 浠诲姟鑺傜偣
+     */
+    @ExcelProperty(value = "浠诲姟鑺傜偣")
+    private String taskName;
+
+    /**
+     * 娴佺▼鐗堟湰
+     */
+    @ExcelProperty(value = "娴佺▼鐗堟湰")
+    private int procDefVersion;
+
+    /**
+     * 娴佺▼鍙戣捣浜哄悕绉�
+     */
+    @ExcelProperty(value = "娴佺▼鍙戣捣浜�")
+    private String startUserName;
+
+    /**
+     * 鎺ユ敹鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "鎺ユ敹鏃堕棿")
+    private Date createTime;
+
+    /**
+     * 瀹℃壒鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "瀹℃壒鏃堕棿")
+    private Date finishTime;
+
+    /**
+     * 浠诲姟鑰楁椂
+     */
+    @ExcelProperty(value = "浠诲姟鑰楁椂")
+    private String duration;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java
new file mode 100644
index 0000000..1810def
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfFormVo.java
@@ -0,0 +1,43 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * 娴佺▼鍒嗙被瑙嗗浘瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfFormVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 琛ㄥ崟涓婚敭
+     */
+    @ExcelProperty(value = "琛ㄥ崟ID")
+    private Long formId;
+
+    /**
+     * 琛ㄥ崟鍚嶇О
+     */
+    @ExcelProperty(value = "琛ㄥ崟鍚嶇О")
+    private String formName;
+
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    @ExcelProperty(value = "琛ㄥ崟鍐呭")
+    private String content;
+
+    /**
+     * 澶囨敞
+     */
+    @ExcelProperty(value = "澶囨敞")
+    private String remark;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java
new file mode 100644
index 0000000..21716e5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelExportVo.java
@@ -0,0 +1,61 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 娴佺▼妯″瀷瀵硅薄瀵煎嚭VO
+ *
+ * @author konbai
+ */
+@Data
+@NoArgsConstructor
+public class WfModelExportVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 妯″瀷ID
+     */
+    @ExcelProperty(value = "妯″瀷ID")
+    private String modelId;
+    /**
+     * 妯″瀷Key
+     */
+    @ExcelProperty(value = "妯″瀷Key")
+    private String modelKey;
+    /**
+     * 妯″瀷鍚嶇О
+     */
+    @ExcelProperty(value = "妯″瀷鍚嶇О")
+    private String modelName;
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    @ExcelProperty(value = "鍒嗙被缂栫爜")
+    private String category;
+    /**
+     * 娴佺▼鍒嗙被
+     */
+    @ExcelProperty(value = "娴佺▼鍒嗙被")
+    private String categoryName;
+    /**
+     * 妯″瀷鐗堟湰
+     */
+    @ExcelProperty(value = "妯″瀷鐗堟湰")
+    private Integer version;
+    /**
+     * 妯″瀷鎻忚堪
+     */
+    @ExcelProperty(value = "妯″瀷鎻忚堪")
+    private String description;
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ExcelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java
new file mode 100644
index 0000000..661e639
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfModelVo.java
@@ -0,0 +1,61 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 娴佺▼妯″瀷瑙嗗浘瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/6/21 9:16
+ */
+@Data
+public class WfModelVo {
+    /**
+     * 妯″瀷ID
+     */
+    private String modelId;
+    /**
+     * 妯″瀷鍚嶇О
+     */
+    private String modelName;
+    /**
+     * 妯″瀷Key
+     */
+    private String modelKey;
+    /**
+     * 鍒嗙被缂栫爜
+     */
+    private String category;
+    /**
+     * 鐗堟湰
+     */
+    private Integer version;
+    /**
+     * 琛ㄥ崟绫诲瀷
+     */
+    private Integer formType;
+    /**
+     * 琛ㄥ崟ID
+     */
+    private Long formId;
+    /**
+     * 妯″瀷鎻忚堪
+     */
+    private String description;
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createTime;
+    /**
+     * 娴佺▼xml
+     */
+    private String bpmnXml;
+    /**
+     * 琛ㄥ崟鍐呭
+     */
+    private String content;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java
new file mode 100644
index 0000000..f9e0121
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfOwnTaskExportVo.java
@@ -0,0 +1,77 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鎴戞嫢鏈夋祦绋嬪璞″鍑篤O
+ *
+ * @author konbai
+ */
+@Data
+@NoArgsConstructor
+public class WfOwnTaskExportVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 娴佺▼瀹炰緥ID
+     */
+    @ExcelProperty(value = "娴佺▼缂栧彿")
+    private String procInsId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String procDefName;
+
+    /**
+     * 娴佺▼绫诲埆
+     */
+    @ExcelProperty(value = "娴佺▼绫诲埆")
+    private String category;
+
+    /**
+     * 娴佺▼鐗堟湰
+     */
+    @ExcelProperty(value = "娴佺▼鐗堟湰")
+    private int procDefVersion;
+
+    /**
+     * 鎻愪氦鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "鎻愪氦鏃堕棿")
+    private Date createTime;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    @ExcelProperty(value = "娴佺▼鐘舵��")
+    private String status;
+
+    /**
+     * 浠诲姟鑰楁椂
+     */
+    @ExcelProperty(value = "浠诲姟鑰楁椂")
+    private String duration;
+
+    /**
+     * 褰撳墠鑺傜偣
+     */
+    @ExcelProperty(value = "褰撳墠鑺傜偣")
+    private String taskName;
+
+    /**
+     * 浠诲姟瀹屾垚鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date finishTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java
new file mode 100644
index 0000000..2edd852
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfProcNodeVo.java
@@ -0,0 +1,69 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.flowable.engine.task.Comment;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 宸ヤ綔娴佽妭鐐瑰厓绱犺鍥惧璞�
+ *
+ * @author KonBAI
+ * @createTime 2022/9/11 22:04
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfProcNodeVo implements Serializable {
+    /**
+     * 娴佺▼ID
+     */
+    private String procDefId;
+    /**
+     * 娲诲姩ID
+     */
+    private String activityId;
+    /**
+     * 娲诲姩鍚嶇О
+     */
+    private String activityName;
+    /**
+     * 娲诲姩绫诲瀷
+     */
+    private String activityType;
+    /**
+     * 娲诲姩鑰楁椂
+     */
+    private String duration;
+    /**
+     * 鎵ц浜篒d
+     */
+    private Long assigneeId;
+    /**
+     * 鎵ц浜哄悕绉�
+     */
+    private String assigneeName;
+    /**
+     * 鍊欓�夋墽琛屼汉
+     */
+    private String candidate;
+    /**
+     * 浠诲姟鎰忚
+     */
+    private List<Comment> commentList;
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    /**
+     * 缁撴潫鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java
new file mode 100644
index 0000000..80ea902
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTaskVo.java
@@ -0,0 +1,134 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.flowable.engine.task.Comment;
+import org.ruoyi.flowable.workflow.domain.dto.WfCommentDto;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 宸ヤ綔娴佷换鍔¤鍥惧璞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class WfTaskVo implements Serializable {
+    /**
+     * 浠诲姟缂栧彿
+     */
+    private String taskId;
+    /**
+     * 浠诲姟鍚嶇О
+     */
+    private String taskName;
+    /**
+     * 浠诲姟Key
+     */
+    private String taskDefKey;
+    /**
+     * 浠诲姟鎵ц浜篒d
+     */
+    private Long assigneeId;
+    /**
+     * 閮ㄩ棬鍚嶇О
+     */
+    @Deprecated
+    private String deptName;
+    /**
+     * 娴佺▼鍙戣捣浜洪儴闂ㄥ悕绉�
+     */
+    @Deprecated
+    private String startDeptName;
+    /**
+     * 浠诲姟鎵ц浜哄悕绉�
+     */
+    private String assigneeName;
+    /**
+     * 娴佺▼鍙戣捣浜篒d
+     */
+    private Long startUserId;
+    /**
+     * 娴佺▼鍙戣捣浜哄悕绉�
+     */
+    private String startUserName;
+    /**
+     * 娴佺▼绫诲瀷
+     */
+    private String category;
+    /**
+     * 娴佺▼鍙橀噺淇℃伅
+     */
+    private Object procVars;
+    /**
+     * 灞�閮ㄥ彉閲忎俊鎭�
+     */
+    private Object taskLocalVars;
+    /**
+     * 娴佺▼閮ㄧ讲缂栧彿
+     */
+    private String deployId;
+    /**
+     * 娴佺▼ID
+     */
+    private String procDefId;
+    /**
+     * 娴佺▼key
+     */
+    private String procDefKey;
+    /**
+     * 娴佺▼瀹氫箟鍚嶇О
+     */
+    private String procDefName;
+    /**
+     * 娴佺▼瀹氫箟鍐呯疆浣跨敤鐗堟湰
+     */
+    private int procDefVersion;
+    /**
+     * 娴佺▼瀹炰緥ID
+     */
+    private String procInsId;
+    /**
+     * 鍘嗗彶娴佺▼瀹炰緥ID
+     */
+    private String hisProcInsId;
+    /**
+     * 浠诲姟鑰楁椂
+     */
+    private String duration;
+    /**
+     * 浠诲姟鎰忚
+     */
+    private WfCommentDto comment;
+    /**
+     * 浠诲姟鎰忚
+     */
+    private List<Comment> commentList;
+    /**
+     * 鍊欓�夋墽琛屼汉
+     */
+    private String candidate;
+    /**
+     * 浠诲姟鍒涘缓鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    /**
+     * 浠诲姟瀹屾垚鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date finishTime;
+
+    /**
+     * 娴佺▼鐘舵��
+     */
+    private String processStatus;
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java
new file mode 100644
index 0000000..59081ce
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfTodoTaskExportVo.java
@@ -0,0 +1,58 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 寰呭姙娴佺▼瀵硅薄瀵煎嚭VO
+ *
+ * @author konbai
+ */
+@Data
+@NoArgsConstructor
+public class WfTodoTaskExportVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟缂栧彿
+     */
+    @ExcelProperty(value = "浠诲姟缂栧彿")
+    private String taskId;
+
+    /**
+     * 娴佺▼鍚嶇О
+     */
+    @ExcelProperty(value = "娴佺▼鍚嶇О")
+    private String procDefName;
+
+    /**
+     * 浠诲姟鑺傜偣
+     */
+    @ExcelProperty(value = "浠诲姟鑺傜偣")
+    private String taskName;
+
+    /**
+     * 娴佺▼鐗堟湰
+     */
+    @ExcelProperty(value = "娴佺▼鐗堟湰")
+    private int procDefVersion;
+
+    /**
+     * 娴佺▼鍙戣捣浜哄悕绉�
+     */
+    @ExcelProperty(value = "娴佺▼鍙戣捣浜�")
+    private String startUserName;
+
+    /**
+     * 鎺ユ敹鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "鎺ユ敹鏃堕棿")
+    private Date createTime;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java
new file mode 100644
index 0000000..adc81fa
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/domain/vo/WfViewerVo.java
@@ -0,0 +1,42 @@
+package org.ruoyi.flowable.workflow.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+/**
+ * 浠诲姟杩借釜瑙嗗浘瀵硅薄
+ *
+ * @author KonBAI
+ * @createTime 2022/1/8 19:42
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ExcelIgnoreUnannotated
+public class WfViewerVo {
+
+    /**
+     * 鑾峰彇娴佺▼瀹炰緥鐨勫巻鍙茶妭鐐癸紙鍘婚噸锛�
+     */
+    private Set<String> finishedTaskSet;
+
+    /**
+     * 宸插畬鎴�
+     */
+    private Set<String> finishedSequenceFlowSet;
+
+    /**
+     * 鑾峰彇娴佺▼瀹炰緥褰撳墠姝e湪寰呭姙鐨勮妭鐐癸紙鍘婚噸锛�
+     */
+    private Set<String> unfinishedTaskSet;
+
+    /**
+     * 宸叉嫆缁�
+     */
+    private Set<String> rejectedTaskSet;
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java
new file mode 100644
index 0000000..f04291f
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/handler/MultiInstanceHandler.java
@@ -0,0 +1,75 @@
+package org.ruoyi.flowable.workflow.handler;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.common.constant.ProcessConstants;
+import org.ruoyi.system.domain.SysUser;
+import org.ruoyi.system.domain.bo.SysUserBo;
+import org.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 澶氬疄渚嬪鐞嗙被
+ *
+ * @author KonBAI
+ */
+@AllArgsConstructor
+@Component("multiInstanceHandler")
+public class MultiInstanceHandler {
+    @Autowired
+    private ISysUserService remoteUserService;
+    public Set<String> getUserIds(DelegateExecution execution) {
+        Set<String> candidateUserIds = new LinkedHashSet<>();
+        FlowElement flowElement = execution.getCurrentFlowElement();
+        if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {
+            UserTask userTask = (UserTask) flowElement;
+            String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
+            if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
+                // 娣诲姞鍊欓�夌敤鎴穒d
+                candidateUserIds.addAll(userTask.getCandidateUsers());
+            } else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
+                // 鑾峰彇缁勭殑ID锛岃鑹睮D闆嗗悎鎴栭儴闂↖D闆嗗悎
+                List<Long> groups = userTask.getCandidateGroups().stream()
+                        .map(item -> Long.parseLong(item.substring(4)))
+                        .collect(Collectors.toList());
+                List<Long> userIds = new ArrayList<>();
+                if ("ROLES".equals(dataType)) {
+                    //todo 閫氳繃瑙掕壊id锛岃幏鍙栨墍鏈夌敤鎴穒d闆嗗悎
+                    R<List<SysUser>> userList = remoteUserService.selectUserByRoleIds(groups);
+                    if(!ObjectUtil.isNull(userList) && !ObjectUtil.isNull(userList.getData())) {
+                        List<SysUser> userListData = userList.getData();
+                        for(SysUser user : userListData) {
+                            userIds.add(user.getUserId());
+                        }
+                    }
+                } else if ("DEPTS".equals(dataType)) {
+                    //todo 閫氳繃閮ㄩ棬id锛岃幏鍙栨墍鏈夌敤鎴穒d闆嗗悎
+                    R<List<SysUser>> userList = remoteUserService.selectUserByDeptIds(groups);
+                    if(!ObjectUtil.isNull(userList) && ! ObjectUtil.isNull(userList.getData())) {
+                        List<SysUser> userListData = userList.getData();
+                        for(SysUser user : userListData) {
+                            userIds.add(user.getUserId());
+                        }
+                    }
+                }
+                // 娣诲姞鍊欓�夌敤鎴穒d
+                userIds.forEach(id -> candidateUserIds.add(String.valueOf(id)));
+            }
+        }
+        return candidateUserIds;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java
new file mode 100644
index 0000000..f4c4a95
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCategoryMapper.java
@@ -0,0 +1,19 @@
+package org.ruoyi.flowable.workflow.mapper;
+
+
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
+import org.ruoyi.flowable.workflow.domain.WfCategory;
+import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
+
+/**
+ * 娴佺▼鍒嗙被Mapper鎺ュ彛
+ *
+ * @author KonBAI
+ * @date 2022-01-15
+ */
+@Mapper
+public interface WfCategoryMapper extends BaseMapperPlus<WfCategoryMapper, WfCategory, WfCategoryVo> {
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java
new file mode 100644
index 0000000..fb332ca
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfCopyMapper.java
@@ -0,0 +1,18 @@
+package org.ruoyi.flowable.workflow.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
+import org.ruoyi.flowable.workflow.domain.WfCopy;
+import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
+
+/**
+ * 娴佺▼鎶勯�丮apper鎺ュ彛
+ *
+ * @author KonBAI
+ * @date 2022-05-19
+ */
+@Mapper
+public interface WfCopyMapper extends BaseMapperPlus<WfCopyMapper, WfCopy, WfCopyVo> {
+
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java
new file mode 100644
index 0000000..a39c78d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfDeployFormMapper.java
@@ -0,0 +1,18 @@
+package org.ruoyi.flowable.workflow.mapper;
+
+
+import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.vo.WfDeployFormVo;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟Mapper鎺ュ彛
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Mapper
+public interface WfDeployFormMapper extends BaseMapperPlus<WfDeployFormMapper, WfDeployForm, WfDeployFormVo> {
+
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java
new file mode 100644
index 0000000..0dedfda
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/mapper/WfFormMapper.java
@@ -0,0 +1,24 @@
+package org.ruoyi.flowable.workflow.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.ruoyi.flowable.core.mapper.BaseMapperPlus;
+import org.ruoyi.flowable.workflow.domain.WfForm;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 娴佺▼琛ㄥ崟Mapper鎺ュ彛
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@Mapper
+public interface WfFormMapper extends BaseMapperPlus<WfFormMapper, WfForm, WfFormVo> {
+
+    List<WfFormVo> selectFormVoList(@Param(Constants.WRAPPER) Wrapper<WfForm> queryWrapper);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java
new file mode 100644
index 0000000..3104021
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCategoryService.java
@@ -0,0 +1,59 @@
+package org.ruoyi.flowable.workflow.service;
+
+import org.ruoyi.flowable.workflow.domain.WfCategory;
+import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴佺▼鍒嗙被Service鎺ュ彛
+ *
+ * @author KonBAI
+ * @date 2022-01-15
+ */
+public interface IWfCategoryService {
+    /**
+     * 鏌ヨ鍗曚釜
+     * @return
+     */
+    WfCategoryVo queryById(Long categoryId);
+
+
+    /**
+     * 鏌ヨ鍒楄〃
+     */
+    List<WfCategoryVo> queryList(WfCategory category);
+
+    /**
+     * 鏂板娴佺▼鍒嗙被
+     *
+     * @param category 娴佺▼鍒嗙被淇℃伅
+     * @return 缁撴灉
+     */
+    int insertCategory(WfCategory category);
+
+    /**
+     * 缂栬緫娴佺▼鍒嗙被
+     * @param category 娴佺▼鍒嗙被淇℃伅
+     * @return 缁撴灉
+     */
+    int updateCategory(WfCategory category);
+
+    /**
+     * 鏍¢獙骞跺垹闄ゆ暟鎹�
+     * @param ids 涓婚敭闆嗗悎
+     * @param isValid 鏄惁鏍¢獙,true-鍒犻櫎鍓嶆牎楠�,false-涓嶆牎楠�
+     * @return 缁撴灉
+     */
+    int deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 鏍¢獙鍒嗙被缂栫爜鏄惁鍞竴
+     *
+     * @param category 娴佺▼鍒嗙被
+     * @return 缁撴灉
+     */
+    boolean checkCategoryCodeUnique(WfCategory category);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java
new file mode 100644
index 0000000..f78204a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfCopyService.java
@@ -0,0 +1,50 @@
+package org.ruoyi.flowable.workflow.service;
+
+
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
+
+import java.util.List;
+
+/**
+ * 娴佺▼鎶勯�丼ervice鎺ュ彛
+ *
+ * @author KonBAI
+ * @date 2022-05-19
+ */
+public interface IWfCopyService {
+
+    /**
+     * 鏌ヨ娴佺▼鎶勯��
+     *
+     * @param copyId 娴佺▼鎶勯�佷富閿�
+     * @return 娴佺▼鎶勯��
+     */
+    WfCopyVo queryById(Long copyId);
+
+    /**
+     * 鏌ヨ娴佺▼鎶勯�佸垪琛�
+     *
+     * @param wfCopy 娴佺▼鎶勯��
+     * @return 娴佺▼鎶勯�侀泦鍚�
+     */
+    TableDataInfo<WfCopyVo> selectPageList(WfCopyBo wfCopy, PageQuery pageQuery);
+    /**
+     * 鏌ヨ娴佺▼鎶勯�佸垪琛�
+     *
+     * @param wfCopy 娴佺▼鎶勯��
+     * @return 娴佺▼鎶勯�侀泦鍚�
+     */
+    List<WfCopyVo> selectList(WfCopyBo wfCopy);
+
+    /**
+     * 鎶勯��
+     * @param taskBo
+     * @return
+     */
+    Boolean makeCopy(WfTaskBo taskBo);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java
new file mode 100644
index 0000000..0008c09
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployFormService.java
@@ -0,0 +1,39 @@
+package org.ruoyi.flowable.workflow.service;
+
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.flowable.bpmn.model.BpmnModel;
+
+/**
+ * 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟Service鎺ュ彛
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+public interface IWfDeployFormService {
+
+    /**
+     * 鏂板娴佺▼瀹炰緥鍏宠仈琛ㄥ崟
+     *
+     * @param wfDeployForm 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟
+     * @return 缁撴灉
+     */
+    int insertWfDeployForm(WfDeployForm wfDeployForm);
+
+    /**
+     * 淇濆瓨娴佺▼瀹炰緥鍏宠仈琛ㄥ崟
+     * @param deployId 閮ㄧ讲ID
+     * @param bpmnModel bpmnModel瀵硅薄
+     * @return
+     */
+    boolean saveInternalDeployForm(String deployId, BpmnModel bpmnModel);
+
+    /**
+     * 鏌ヨ娴佺▼鎸傜潃鐨勮〃鍗�
+     *
+     * @param deployId
+     * @return
+     */
+    @Deprecated
+    WfFormVo selectDeployFormByDeployId(String deployId);
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java
new file mode 100644
index 0000000..87c6b1b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfDeployService.java
@@ -0,0 +1,27 @@
+package org.ruoyi.flowable.workflow.service;
+
+
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
+
+import java.util.List;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/30 9:03
+ */
+public interface IWfDeployService {
+
+    TableDataInfo<WfDeployVo> queryPageList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    TableDataInfo<WfDeployVo> queryPublishList(String processKey, PageQuery pageQuery);
+
+    void updateState(String definitionId, String stateCode);
+
+    String queryBpmnXmlById(String definitionId);
+
+    void deleteByIds(List<String> deployIds);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java
new file mode 100644
index 0000000..4476490
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfFormService.java
@@ -0,0 +1,67 @@
+package org.ruoyi.flowable.workflow.service;
+
+
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 琛ㄥ崟
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+public interface IWfFormService {
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟
+     *
+     * @param formId 娴佺▼琛ㄥ崟ID
+     * @return 娴佺▼琛ㄥ崟
+     */
+    WfFormVo queryById(Long formId);
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟鍒楄〃
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 娴佺▼琛ㄥ崟闆嗗悎
+     */
+    TableDataInfo<WfFormVo> queryPageList(WfFormBo bo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟鍒楄〃
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 娴佺▼琛ㄥ崟闆嗗悎
+     */
+    List<WfFormVo> queryList(WfFormBo bo);
+
+    /**
+     * 鏂板娴佺▼琛ㄥ崟
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 缁撴灉
+     */
+    int insertForm(WfFormBo bo);
+
+    /**
+     * 淇敼娴佺▼琛ㄥ崟
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 缁撴灉
+     */
+    int updateForm(WfFormBo bo);
+
+    /**
+     * 鎵归噺鍒犻櫎娴佺▼琛ㄥ崟
+     *
+     * @param formIds 闇�瑕佸垹闄ょ殑娴佺▼琛ㄥ崟ID
+     * @return 缁撴灉
+     */
+    Boolean deleteWithValidByIds(Collection<Long> formIds);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java
new file mode 100644
index 0000000..4c9f3e5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfInstanceService.java
@@ -0,0 +1,53 @@
+package org.ruoyi.flowable.workflow.service;
+
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.flowable.engine.history.HistoricProcessInstance;
+
+import java.util.Map;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+public interface IWfInstanceService {
+
+    /**
+     * 缁撴潫娴佺▼瀹炰緥
+     *
+     * @param vo
+     */
+    void stopProcessInstance(WfTaskBo vo);
+
+    /**
+     * 婵�娲绘垨鎸傝捣娴佺▼瀹炰緥
+     *
+     * @param state      鐘舵��
+     * @param instanceId 娴佺▼瀹炰緥ID
+     */
+    void updateState(Integer state, String instanceId);
+
+    /**
+     * 鍒犻櫎娴佺▼瀹炰緥ID
+     *
+     * @param instanceId   娴佺▼瀹炰緥ID
+     * @param deleteReason 鍒犻櫎鍘熷洜
+     */
+    void delete(String instanceId, String deleteReason);
+
+    /**
+     * 鏍规嵁瀹炰緥ID鏌ヨ鍘嗗彶瀹炰緥鏁版嵁
+     *
+     * @param processInstanceId
+     * @return
+     */
+    HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId);
+
+
+    /**
+     * 鏌ヨ娴佺▼璇︽儏淇℃伅
+     * @param procInsId 娴佺▼瀹炰緥ID
+     * @param deployId 娴佺▼閮ㄧ讲ID
+     */
+    Map<String, Object> queryDetailProcess(String procInsId, String deployId);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java
new file mode 100644
index 0000000..78dc29b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfModelService.java
@@ -0,0 +1,72 @@
+package org.ruoyi.flowable.workflow.service;
+
+
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/21 9:11
+ */
+public interface IWfModelService {
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷鍒楄〃
+     */
+    TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷鍒楄〃
+     */
+    List<WfModelVo> list(WfModelBo modelBo);
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷鍒楄〃
+     */
+    TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ娴佺▼妯″瀷璇︽儏淇℃伅
+     */
+    WfModelVo getModel(String modelId);
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟璇︾粏淇℃伅
+     */
+    String queryBpmnXmlById(String modelId);
+
+    /**
+     * 鏂板妯″瀷淇℃伅
+     */
+    void insertModel(WfModelBo modelBo);
+
+    /**
+     * 淇敼妯″瀷淇℃伅
+     */
+    void updateModel(WfModelBo modelBo);
+
+    /**
+     * 淇濆瓨娴佺▼妯″瀷淇℃伅
+     */
+    void saveModel(WfModelBo modelBo);
+
+    /**
+     * 璁句负鏈�鏂版祦绋嬫ā鍨�
+     */
+    void latestModel(String modelId);
+
+    /**
+     * 鍒犻櫎娴佺▼妯″瀷
+     */
+    void deleteByIds(Collection<String> ids);
+
+    /**
+     * 閮ㄧ讲娴佺▼妯″瀷
+     */
+    boolean deployModel(String modelId);
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java
new file mode 100644
index 0000000..78d0d15
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfProcessService.java
@@ -0,0 +1,118 @@
+package org.ruoyi.flowable.workflow.service;
+
+
+import org.ruoyi.flowable.core.FormConf;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.vo.WfDefinitionVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfDetailVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfTaskVo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/24 18:57
+ */
+public interface IWfProcessService {
+
+    /**
+     * 鏌ヨ鍙彂璧锋祦绋嬪垪琛�
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return
+     */
+    TableDataInfo<WfDefinitionVo> selectPageStartProcessList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ鍙彂璧锋祦绋嬪垪琛�
+     */
+    List<WfDefinitionVo> selectStartProcessList(ProcessQuery processQuery);
+
+    /**
+     * 鏌ヨ鎴戠殑娴佺▼鍒楄〃
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    TableDataInfo<WfTaskVo> selectPageOwnProcessList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ鎴戠殑娴佺▼鍒楄〃
+     */
+    List<WfTaskVo> selectOwnProcessList(ProcessQuery processQuery);
+
+    /**
+     * 鏌ヨ浠e姙浠诲姟鍒楄〃
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ浠e姙浠诲姟鍒楄〃
+     */
+    List<WfTaskVo> selectTodoProcessList(ProcessQuery processQuery);
+
+    /**
+     * 鏌ヨ寰呯浠诲姟鍒楄〃
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    TableDataInfo<WfTaskVo> selectPageClaimProcessList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ寰呯浠诲姟鍒楄〃
+     */
+    List<WfTaskVo> selectClaimProcessList(ProcessQuery processQuery);
+
+    /**
+     * 鏌ヨ宸插姙浠诲姟鍒楄〃
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     */
+    TableDataInfo<WfTaskVo> selectPageFinishedProcessList(ProcessQuery processQuery, PageQuery pageQuery);
+
+    /**
+     * 鏌ヨ宸插姙浠诲姟鍒楄〃
+     */
+    List<WfTaskVo> selectFinishedProcessList(ProcessQuery processQuery);
+
+    /**
+     * 鏌ヨ娴佺▼閮ㄧ讲鍏宠仈琛ㄥ崟淇℃伅
+     * @param definitionId 娴佺▼瀹氫箟ID
+     * @param deployId 閮ㄧ讲ID
+     */
+    FormConf selectFormContent(String definitionId, String deployId, String procInsId);
+
+    /**
+     * 鍚姩娴佺▼瀹炰緥
+     * @param procDefId 娴佺▼瀹氫箟ID
+     * @param variables 鎵╁睍鍙傛暟
+     */
+    void startProcessByDefId(String procDefId, Map<String, Object> variables);
+
+    /**
+     * 閫氳繃DefinitionKey鍚姩娴佺▼
+     * @param procDefKey 娴佺▼瀹氫箟Key
+     * @param variables 鎵╁睍鍙傛暟
+     */
+    void startProcessByDefKey(String procDefKey, Map<String, Object> variables);
+
+    /**
+     * 鍒犻櫎娴佺▼瀹炰緥
+     */
+    void deleteProcessByIds(String[] instanceIds);
+
+
+    /**
+     * 璇诲彇xml鏂囦欢
+     * @param processDefId 娴佺▼瀹氫箟ID
+     */
+    String queryBpmnXmlById(String processDefId);
+
+
+    /**
+     * 鏌ヨ娴佺▼浠诲姟璇︽儏淇℃伅
+     * @param procInsId 娴佺▼瀹炰緥ID
+     * @param taskId 浠诲姟ID
+     */
+    WfDetailVo queryProcessDetail(String procInsId, String taskId);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java
new file mode 100644
index 0000000..b92342b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/IWfTaskService.java
@@ -0,0 +1,118 @@
+package org.ruoyi.flowable.workflow.service;
+
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.engine.runtime.ProcessInstance;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+public interface IWfTaskService {
+
+    /**
+     * 瀹℃壒浠诲姟
+     *
+     * @param task 璇锋眰瀹炰綋鍙傛暟
+     */
+    void complete(WfTaskBo task);
+
+    /**
+     * 鎷掔粷浠诲姟
+     *
+     * @param taskBo
+     */
+    void taskReject(WfTaskBo taskBo);
+
+
+    /**
+     * 閫�鍥炰换鍔�
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void taskReturn(WfTaskBo bo);
+
+    /**
+     * 鑾峰彇鎵�鏈夊彲鍥為��鐨勮妭鐐�
+     *
+     * @param bo
+     * @return
+     */
+    List<FlowElement> findReturnTaskList(WfTaskBo bo);
+
+    /**
+     * 鍒犻櫎浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void deleteTask(WfTaskBo bo);
+
+    /**
+     * 璁ら/绛炬敹浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void claim(WfTaskBo bo);
+
+    /**
+     * 鍙栨秷璁ら/绛炬敹浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void unClaim(WfTaskBo bo);
+
+    /**
+     * 濮旀淳浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void delegateTask(WfTaskBo bo);
+
+
+    /**
+     * 杞姙浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    void transferTask(WfTaskBo bo);
+
+    /**
+     * 鍙栨秷鐢宠
+     * @param bo
+     * @return
+     */
+    void stopProcess(WfTaskBo bo);
+
+    /**
+     * 鎾ゅ洖娴佺▼
+     * @param bo
+     * @return
+     */
+    void revokeProcess(WfTaskBo bo);
+
+    /**
+     * 鑾峰彇娴佺▼杩囩▼鍥�
+     * @param processId
+     * @return
+     */
+    InputStream diagram(String processId);
+
+    /**
+     * 鑾峰彇娴佺▼鍙橀噺
+     * @param taskId 浠诲姟ID
+     * @return 娴佺▼鍙橀噺
+     */
+    Map<String, Object> getProcessVariables(String taskId);
+
+    /**
+     * 鍚姩绗竴涓换鍔�
+     * @param processInstance 娴佺▼瀹炰緥
+     * @param variables 娴佺▼鍙傛暟
+     */
+    void startFirstTask(ProcessInstance processInstance, Map<String, Object> variables);
+}
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java
new file mode 100644
index 0000000..1d99c58
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCategoryServiceImpl.java
@@ -0,0 +1,84 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.workflow.domain.WfCategory;
+import org.ruoyi.flowable.workflow.domain.vo.WfCategoryVo;
+import org.ruoyi.flowable.workflow.mapper.WfCategoryMapper;
+import org.ruoyi.flowable.workflow.service.IWfCategoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼鍒嗙被Service涓氬姟灞傚鐞�
+ *
+ * @author KonBAI
+ * @date 2022-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class WfCategoryServiceImpl implements IWfCategoryService {
+
+    private final WfCategoryMapper baseMapper;
+
+    @Override
+    public WfCategoryVo queryById(Long categoryId){
+        return baseMapper.selectVoById(categoryId);
+    }
+
+
+    @Override
+    public List<WfCategoryVo> queryList(WfCategory category) {
+        LambdaQueryWrapper<WfCategory> lqw = buildQueryWrapper(category);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<WfCategory> buildQueryWrapper(WfCategory category) {
+        Map<String, Object> params = category.getParams();
+        LambdaQueryWrapper<WfCategory> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(category.getCategoryName()), WfCategory::getCategoryName, category.getCategoryName());
+        lqw.eq(StringUtils.isNotBlank(category.getCode()), WfCategory::getCode, category.getCode());
+        return lqw;
+    }
+
+    @Override
+    public int insertCategory(WfCategory categoryBo) {
+        WfCategory add = BeanUtil.toBean(categoryBo, WfCategory.class);
+        return baseMapper.insert(add);
+    }
+
+    @Override
+    public int updateCategory(WfCategory categoryBo) {
+        WfCategory update = BeanUtil.toBean(categoryBo, WfCategory.class);
+        return baseMapper.updateById(update);
+    }
+
+    @Override
+    public int deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 鍋氫竴浜涗笟鍔′笂鐨勬牎楠�,鍒ゆ柇鏄惁闇�瑕佹牎楠�
+        }
+        return baseMapper.deleteBatchIds(ids);
+    }
+
+    /**
+     * 鏍¢獙鍒嗙被缂栫爜鏄惁鍞竴
+     *
+     * @param category 娴佺▼鍒嗙被
+     * @return 缁撴灉
+     */
+    @Override
+    public boolean checkCategoryCodeUnique(WfCategory category) {
+        boolean exist = baseMapper.exists(new LambdaQueryWrapper<WfCategory>()
+            .eq(WfCategory::getCode, category.getCode())
+            .ne(ObjectUtil.isNotNull(category.getCategoryId()), WfCategory::getCategoryId, category.getCategoryId()));
+        return !exist;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java
new file mode 100644
index 0000000..58a1835
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfCopyServiceImpl.java
@@ -0,0 +1,131 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.annotation.Resource;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.utils.ObjectUtils;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.WfCopy;
+import org.ruoyi.flowable.workflow.domain.bo.WfCopyBo;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfCopyVo;
+import org.ruoyi.flowable.workflow.mapper.WfCopyMapper;
+import org.ruoyi.flowable.workflow.service.IWfCopyService;
+import lombok.RequiredArgsConstructor;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.repository.Deployment;
+import org.springframework.stereotype.Service;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼鎶勯�丼ervice涓氬姟灞傚鐞�
+ *
+ * @author KonBAI
+ * @date 2022-05-19
+ */
+@RequiredArgsConstructor
+@Service
+public class WfCopyServiceImpl implements IWfCopyService {
+
+    private final WfCopyMapper baseMapper;
+
+    private final HistoryService historyService;
+    @Resource
+    protected RepositoryService repositoryService;
+    /**
+     * 鏌ヨ娴佺▼鎶勯��
+     *
+     * @param copyId 娴佺▼鎶勯�佷富閿�
+     * @return 娴佺▼鎶勯��
+     */
+    @Override
+    public WfCopyVo queryById(Long copyId){
+        return baseMapper.selectVoById(copyId);
+    }
+    /**
+     * 鏌ヨ娴佺▼鎶勯�佸垪琛�
+     *
+     * @param bo 娴佺▼鎶勯��
+     * @return 娴佺▼鎶勯��
+     */
+    @Override
+    public TableDataInfo<WfCopyVo> selectPageList(WfCopyBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<WfCopy> lqw = buildQueryWrapper(bo);
+        lqw.orderByDesc(WfCopy::getCreateTime);
+        Page<WfCopyVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼鎶勯�佸垪琛�
+     *
+     * @param bo 娴佺▼鎶勯��
+     * @return 娴佺▼鎶勯��
+     */
+    @Override
+    public List<WfCopyVo> selectList(WfCopyBo bo) {
+        LambdaQueryWrapper<WfCopy> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<WfCopy> buildQueryWrapper(WfCopyBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<WfCopy> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getUserId() != null, WfCopy::getUserId, bo.getUserId());
+        lqw.like(StringUtils.isNotBlank(bo.getProcessName()), WfCopy::getProcessName, bo.getProcessName());
+        lqw.like(StringUtils.isNotBlank(bo.getOriginatorName()), WfCopy::getOriginatorName, bo.getOriginatorName());
+        return lqw;
+    }
+
+    @Override
+    public Boolean makeCopy(WfTaskBo taskBo) {
+        if (StringUtils.isBlank(taskBo.getCopyUserIds())) {
+            // 鑻ユ妱閫佺敤鎴蜂负绌猴紝鍒欎笉闇�瑕佸鐞嗭紝杩斿洖鎴愬姛
+            return true;
+        }
+        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+            .processInstanceId(taskBo.getProcInsId()).singleResult();
+        String[] ids = taskBo.getCopyUserIds().split(",");
+        List<WfCopy> copyList = new ArrayList<>(ids.length);
+        // 鑾峰彇褰撳墠鐨勭敤鎴�
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        Long originatorId = null;
+        String originatorName = null;
+        if (ObjectUtils.isNotEmpty(loginUser))
+        {
+            originatorId = loginUser.getUserId();
+            originatorName = loginUser.getNickName();
+        }
+        // 娴佺▼閮ㄧ讲瀹炰緥淇℃伅
+        Deployment deployment = repositoryService.createDeploymentQuery()
+                .deploymentId(historicProcessInstance.getDeploymentId()).singleResult();
+        String title = historicProcessInstance.getProcessDefinitionName() + "-" + taskBo.getTaskName();
+        for (String id : ids) {
+            Long userId = Long.valueOf(id);
+            WfCopy copy = new WfCopy();
+            copy.setTitle(title);
+            copy.setProcessId(historicProcessInstance.getProcessDefinitionId());
+            copy.setProcessName(historicProcessInstance.getProcessDefinitionName());
+            copy.setDeploymentId(historicProcessInstance.getDeploymentId());
+            copy.setInstanceId(taskBo.getProcInsId());
+            copy.setTaskId(taskBo.getTaskId());
+            copy.setUserId(userId);
+            copy.setOriginatorId(originatorId);
+            copy.setOriginatorName(originatorName);
+            copy.setCreateTime(new Date());
+            copy.setCategoryId(deployment.getCategory());
+            copyList.add(copy);
+        }
+        return baseMapper.insertBatch(copyList);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java
new file mode 100644
index 0000000..57890dd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployFormServiceImpl.java
@@ -0,0 +1,134 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.utils.ModelUtils;
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.WfForm;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
+import org.ruoyi.flowable.workflow.mapper.WfFormMapper;
+import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
+import lombok.RequiredArgsConstructor;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.FlowNode;
+import org.flowable.bpmn.model.StartEvent;
+import org.flowable.bpmn.model.UserTask;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟Service涓氬姟灞傚鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@RequiredArgsConstructor
+@Service
+public class WfDeployFormServiceImpl implements IWfDeployFormService {
+
+    private final WfDeployFormMapper baseMapper;
+
+    private final WfFormMapper formMapper;
+
+    /**
+     * 鏂板娴佺▼瀹炰緥鍏宠仈琛ㄥ崟
+     *
+     * @param deployForm 娴佺▼瀹炰緥鍏宠仈琛ㄥ崟
+     * @return 缁撴灉
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int insertWfDeployForm(WfDeployForm deployForm) {
+        // 鍒犻櫎閮ㄧ讲娴佺▼鍜岃〃鍗曠殑鍏宠仈鍏崇郴
+        baseMapper.delete(new LambdaQueryWrapper<WfDeployForm>().eq(WfDeployForm::getDeployId, deployForm.getDeployId()));
+        // 鏂板閮ㄧ讲娴佺▼鍜岃〃鍗曞叧绯�
+        return baseMapper.insert(deployForm);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveInternalDeployForm(String deployId, BpmnModel bpmnModel) {
+        List<WfDeployForm> deployFormList = new ArrayList<>();
+        // 鑾峰彇寮�濮嬭妭鐐�
+        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
+        if (ObjectUtil.isNull(startEvent)) {
+            throw new RuntimeException("寮�濮嬭妭鐐逛笉瀛樺湪锛岃妫�鏌ユ祦绋嬭璁℃槸鍚︽湁璇紒");
+        }
+        // 淇濆瓨寮�濮嬭妭鐐硅〃鍗曚俊鎭�
+        WfDeployForm startDeployForm = buildDeployForm(deployId, startEvent);
+        if (ObjectUtil.isNotNull(startDeployForm)) {
+            deployFormList.add(startDeployForm);
+        }
+        // 淇濆瓨鐢ㄦ埛鑺傜偣琛ㄥ崟淇℃伅
+        Collection<UserTask> userTasks = ModelUtils.getAllUserTaskEvent(bpmnModel);
+        if (CollUtil.isNotEmpty(userTasks)) {
+            for (UserTask userTask : userTasks) {
+                WfDeployForm userTaskDeployForm = buildDeployForm(deployId, userTask);
+                if (ObjectUtil.isNotNull(userTaskDeployForm)) {
+                    deployFormList.add(userTaskDeployForm);
+                }
+            }
+        }
+        // 鎵归噺鏂板閮ㄧ讲娴佺▼鍜岃〃鍗曞叧鑱斾俊鎭�
+        return baseMapper.insertBatch(deployFormList);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼鎸傜潃鐨勮〃鍗�
+     *
+     * @param deployId
+     * @return
+     */
+    @Override
+    public WfFormVo selectDeployFormByDeployId(String deployId) {
+        QueryWrapper<WfForm> wrapper = Wrappers.query();
+        wrapper.eq("t2.deploy_id", deployId);
+        List<WfFormVo> list = formMapper.selectFormVoList(wrapper);
+        if (ObjectUtil.isNotEmpty(list)) {
+            if (list.size() != 1) {
+                throw new ServiceException("琛ㄥ崟淇℃伅鏌ヨ閿欒");
+            } else {
+                return list.get(0);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 鏋勫缓閮ㄧ讲琛ㄥ崟鍏宠仈淇℃伅瀵硅薄
+     * @param deployId 閮ㄧ讲ID
+     * @param node 鑺傜偣淇℃伅
+     * @return 閮ㄧ讲琛ㄥ崟鍏宠仈瀵硅薄銆傝嫢鏃犺〃鍗曚俊鎭紙formKey锛夛紝鍒欒繑鍥瀗ull
+     */
+    private WfDeployForm buildDeployForm(String deployId, FlowNode node) {
+        String formKey = ModelUtils.getFormKey(node);
+        if (StringUtils.isEmpty(formKey)) {
+            return null;
+        }
+        Long formId = Convert.toLong(StringUtils.substringAfter(formKey, "key_"));
+        WfForm wfForm = formMapper.selectById(formId);
+        if (ObjectUtil.isNull(wfForm)) {
+            throw new ServiceException("琛ㄥ崟淇℃伅鏌ヨ閿欒");
+        }
+        WfDeployForm deployForm = new WfDeployForm();
+        deployForm.setDeployId(deployId);
+        deployForm.setFormKey(formKey);
+        deployForm.setNodeKey(node.getId());
+        deployForm.setFormName(wfForm.getFormName());
+        deployForm.setNodeName(node.getName());
+        deployForm.setContent(wfForm.getContent());
+        return deployForm;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java
new file mode 100644
index 0000000..b947c28
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfDeployServiceImpl.java
@@ -0,0 +1,146 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.utils.ProcessUtils;
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.vo.WfDeployVo;
+import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
+import org.ruoyi.flowable.workflow.service.IWfDeployService;
+import lombok.RequiredArgsConstructor;
+import org.flowable.common.engine.impl.db.SuspensionState;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.repository.ProcessDefinitionQuery;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/30 9:04
+ */
+@RequiredArgsConstructor
+@Service
+public class WfDeployServiceImpl implements IWfDeployService {
+
+    private final RepositoryService repositoryService;
+    private final WfDeployFormMapper deployFormMapper;
+
+    @Override
+    public TableDataInfo<WfDeployVo> queryPageList(ProcessQuery processQuery, PageQuery pageQuery) {
+        // 娴佺▼瀹氫箟鍒楄〃鏁版嵁鏌ヨ
+        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
+            .latestVersion()
+            .orderByProcessDefinitionKey()
+            .asc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
+        long pageTotal = processDefinitionQuery.count();
+        if (pageTotal <= 0) {
+            return TableDataInfo.build();
+        }
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<ProcessDefinition> definitionList = processDefinitionQuery.listPage(offset, pageQuery.getPageSize());
+
+        List<WfDeployVo> deployVoList = new ArrayList<>(definitionList.size());
+        for (ProcessDefinition processDefinition : definitionList) {
+            String deploymentId = processDefinition.getDeploymentId();
+            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
+            WfDeployVo vo = new WfDeployVo();
+            vo.setDefinitionId(processDefinition.getId());
+            vo.setProcessKey(processDefinition.getKey());
+            vo.setProcessName(processDefinition.getName());
+            vo.setVersion(processDefinition.getVersion());
+            vo.setCategory(processDefinition.getCategory());
+            vo.setDeploymentId(processDefinition.getDeploymentId());
+            vo.setSuspended(processDefinition.isSuspended());
+            // 娴佺▼閮ㄧ讲淇℃伅
+            vo.setCategory(deployment.getCategory());
+            vo.setDeploymentTime(deployment.getDeploymentTime());
+            deployVoList.add(vo);
+        }
+        Page<WfDeployVo> page = new Page<>();
+        page.setRecords(deployVoList);
+        page.setTotal(pageTotal);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public TableDataInfo<WfDeployVo> queryPublishList(String processKey, PageQuery pageQuery) {
+        // 鍒涘缓鏌ヨ鏉′欢
+        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
+            .processDefinitionKey(processKey)
+            .orderByProcessDefinitionVersion()
+            .desc();
+        long pageTotal = processDefinitionQuery.count();
+        if (pageTotal <= 0) {
+            return TableDataInfo.build();
+        }
+        // 鏍规嵁鏌ヨ鏉′欢锛屾煡璇㈡墍鏈夌増鏈�
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<ProcessDefinition> processDefinitionList = processDefinitionQuery
+            .listPage(offset, pageQuery.getPageSize());
+        List<WfDeployVo> deployVoList = processDefinitionList.stream().map(item -> {
+            WfDeployVo vo = new WfDeployVo();
+            vo.setDefinitionId(item.getId());
+            vo.setProcessKey(item.getKey());
+            vo.setProcessName(item.getName());
+            vo.setVersion(item.getVersion());
+            vo.setCategory(item.getCategory());
+            vo.setDeploymentId(item.getDeploymentId());
+            vo.setSuspended(item.isSuspended());
+            return vo;
+        }).collect(Collectors.toList());
+        Page<WfDeployVo> page = new Page<>();
+        page.setRecords(deployVoList);
+        page.setTotal(pageTotal);
+        return TableDataInfo.build(page);
+    }
+
+    /**
+     * 婵�娲绘垨鎸傝捣娴佺▼
+     *
+     * @param state 鐘舵��
+     * @param definitionId 娴佺▼瀹氫箟ID
+     */
+    @Override
+    public void updateState(String definitionId, String state) {
+        if (SuspensionState.ACTIVE.toString().equals(state)) {
+            // 婵�娲�
+            repositoryService.activateProcessDefinitionById(definitionId, true, null);
+        } else if (SuspensionState.SUSPENDED.toString().equals(state)) {
+            // 鎸傝捣
+            repositoryService.suspendProcessDefinitionById(definitionId, true, null);
+        }
+    }
+
+    @Override
+    public String queryBpmnXmlById(String definitionId) {
+        InputStream inputStream = repositoryService.getProcessModel(definitionId);
+        try {
+            return IoUtil.readUtf8(inputStream);
+        } catch (IORuntimeException exception) {
+            throw new RuntimeException("鍔犺浇xml鏂囦欢寮傚父");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteByIds(List<String> deployIds) {
+        for (String deployId : deployIds) {
+            repositoryService.deleteDeployment(deployId, true);
+            deployFormMapper.delete(new LambdaQueryWrapper<WfDeployForm>().eq(WfDeployForm::getDeployId, deployId));
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java
new file mode 100644
index 0000000..e147b5c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfFormServiceImpl.java
@@ -0,0 +1,118 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.workflow.domain.WfForm;
+import org.ruoyi.flowable.workflow.domain.bo.WfFormBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.mapper.WfFormMapper;
+import org.ruoyi.flowable.workflow.service.IWfFormService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 娴佺▼琛ㄥ崟Service涓氬姟灞傚鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/7 22:07
+ */
+@RequiredArgsConstructor
+@Service
+public class WfFormServiceImpl implements IWfFormService {
+
+    private final WfFormMapper baseMapper;
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟
+     *
+     * @param formId 娴佺▼琛ㄥ崟ID
+     * @return 娴佺▼琛ㄥ崟
+     */
+    @Override
+    public WfFormVo queryById(Long formId) {
+        return baseMapper.selectVoById(formId);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟鍒楄〃
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 娴佺▼琛ㄥ崟
+     */
+    @Override
+    public TableDataInfo<WfFormVo> queryPageList(WfFormBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<WfForm> lqw = buildQueryWrapper(bo);
+        Page<WfFormVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 鏌ヨ娴佺▼琛ㄥ崟鍒楄〃
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 娴佺▼琛ㄥ崟
+     */
+    @Override
+    public List<WfFormVo> queryList(WfFormBo bo) {
+        LambdaQueryWrapper<WfForm> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    /**
+     * 鏂板娴佺▼琛ㄥ崟
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 缁撴灉
+     */
+    @Override
+    public int insertForm(WfFormBo bo) {
+        WfForm wfForm = new WfForm();
+        wfForm.setFormName(bo.getFormName());
+        wfForm.setContent(bo.getContent());
+        wfForm.setRemark(bo.getRemark());
+        return baseMapper.insert(wfForm);
+    }
+
+    /**
+     * 淇敼娴佺▼琛ㄥ崟
+     *
+     * @param bo 娴佺▼琛ㄥ崟
+     * @return 缁撴灉
+     */
+    @Override
+    public int updateForm(WfFormBo bo) {
+        return baseMapper.update(new WfForm(), new LambdaUpdateWrapper<WfForm>()
+            .set(StrUtil.isNotBlank(bo.getFormName()), WfForm::getFormName, bo.getFormName())
+            .set(StrUtil.isNotBlank(bo.getContent()), WfForm::getContent, bo.getContent())
+            .set(StrUtil.isNotBlank(bo.getRemark()), WfForm::getRemark, bo.getRemark())
+            .eq(WfForm::getFormId, bo.getFormId()));
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎娴佺▼琛ㄥ崟
+     *
+     * @param ids 闇�瑕佸垹闄ょ殑娴佺▼琛ㄥ崟ID
+     * @return 缁撴灉
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids) {
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<WfForm> buildQueryWrapper(WfFormBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<WfForm> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getFormName()), WfForm::getFormName, bo.getFormName());
+        return lqw;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java
new file mode 100644
index 0000000..b0d1bf8
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfInstanceServiceImpl.java
@@ -0,0 +1,232 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.BetweenFormatter;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.common.constant.TaskConstants;
+import org.ruoyi.flowable.factory.FlowServiceFactory;
+import org.ruoyi.flowable.utils.JsonUtils;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfTaskVo;
+import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
+import org.ruoyi.flowable.workflow.service.IWfInstanceService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.common.engine.api.FlowableObjectNotFoundException;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.task.Comment;
+import org.flowable.identitylink.api.history.HistoricIdentityLink;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.ruoyi.system.domain.SysDept;
+import org.ruoyi.system.domain.SysRole;
+import org.ruoyi.system.domain.SysUser;
+import org.ruoyi.system.domain.vo.SysDeptVo;
+import org.ruoyi.system.domain.vo.SysRoleVo;
+import org.ruoyi.system.domain.vo.SysUserVo;
+import org.ruoyi.system.service.ISysDeptService;
+import org.ruoyi.system.service.ISysRoleService;
+import org.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 宸ヤ綔娴佹祦绋嬪疄渚嬬鐞�
+ *
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class WfInstanceServiceImpl extends FlowServiceFactory implements IWfInstanceService {
+
+    private final IWfDeployFormService deployFormService;
+    private final ISysUserService userService;
+    private final ISysRoleService roleService;
+    private final ISysDeptService deptService;
+    @Autowired
+    private ISysUserService remoteUserService;
+
+    /**
+     * 缁撴潫娴佺▼瀹炰緥
+     *
+     * @param vo
+     */
+    @Override
+    public void stopProcessInstance(WfTaskBo vo) {
+        String taskId = vo.getTaskId();
+
+    }
+
+    /**
+     * 婵�娲绘垨鎸傝捣娴佺▼瀹炰緥
+     *
+     * @param state      鐘舵��
+     * @param instanceId 娴佺▼瀹炰緥ID
+     */
+    @Override
+    public void updateState(Integer state, String instanceId) {
+
+        // 婵�娲�
+        if (state == 1) {
+            runtimeService.activateProcessInstanceById(instanceId);
+        }
+        // 鎸傝捣
+        if (state == 2) {
+            runtimeService.suspendProcessInstanceById(instanceId);
+        }
+    }
+
+    /**
+     * 鍒犻櫎娴佺▼瀹炰緥ID
+     *
+     * @param instanceId   娴佺▼瀹炰緥ID
+     * @param deleteReason 鍒犻櫎鍘熷洜
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(String instanceId, String deleteReason) {
+
+        // 鏌ヨ鍘嗗彶鏁版嵁
+        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId);
+        if (historicProcessInstance.getEndTime() != null) {
+            historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
+            return;
+        }
+        // 鍒犻櫎娴佺▼瀹炰緥
+        runtimeService.deleteProcessInstance(instanceId, deleteReason);
+        // 鍒犻櫎鍘嗗彶娴佺▼瀹炰緥
+        historyService.deleteHistoricProcessInstance(instanceId);
+    }
+
+    /**
+     * 鏍规嵁瀹炰緥ID鏌ヨ鍘嗗彶瀹炰緥鏁版嵁
+     *
+     * @param processInstanceId
+     * @return
+     */
+    @Override
+    public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
+        HistoricProcessInstance historicProcessInstance =
+                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+        if (Objects.isNull(historicProcessInstance)) {
+            throw new FlowableObjectNotFoundException("娴佺▼瀹炰緥涓嶅瓨鍦�: " + processInstanceId);
+        }
+        return historicProcessInstance;
+    }
+
+
+    /**
+     * 娴佺▼鍘嗗彶娴佽浆璁板綍
+     *
+     * @param procInsId 娴佺▼瀹炰緥Id
+     * @return
+     */
+    @Override
+    public Map<String, Object> queryDetailProcess(String procInsId, String deployId) {
+        Map<String, Object> map = new HashMap<>();
+        if (StringUtils.isNotBlank(procInsId)) {
+            List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery()
+                .processInstanceId(procInsId)
+                .orderByHistoricTaskInstanceStartTime().desc()
+                .list();
+            List<Comment> commentList = taskService.getProcessInstanceComments(procInsId);
+            List<WfTaskVo> taskVoList = new ArrayList<>(taskInstanceList.size());
+            taskInstanceList.forEach(taskInstance -> {
+                WfTaskVo taskVo = new WfTaskVo();
+                taskVo.setProcDefId(taskInstance.getProcessDefinitionId());
+                taskVo.setTaskId(taskInstance.getId());
+                taskVo.setTaskDefKey(taskInstance.getTaskDefinitionKey());
+                taskVo.setTaskName(taskInstance.getName());
+                taskVo.setCreateTime(taskInstance.getStartTime());
+                taskVo.setFinishTime(taskInstance.getEndTime());
+                if (StringUtils.isNotBlank(taskInstance.getAssignee())) {
+                    Long userId = Long.parseLong(taskInstance.getAssignee());
+                    SysUserVo user = userService.selectUserById(userId);
+                    if(!ObjectUtil.isNull(user)) {
+                        taskVo.setAssigneeId(userId);
+                        taskVo.setAssigneeName(user.getNickName());
+                    }
+                }
+                // 灞曠ず瀹℃壒浜哄憳
+                List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(taskInstance.getId());
+                StringBuilder stringBuilder = new StringBuilder();
+                for (HistoricIdentityLink identityLink : linksForTask) {
+                    if ("candidate".equals(identityLink.getType())) {
+                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
+                            Long userId = Long.parseLong(identityLink.getUserId());
+                            String nickName ="";
+                            SysUserVo user = userService.selectUserById(userId);
+                            if(!ObjectUtil.isNull(user)) {
+                                nickName=user.getNickName();
+                            }
+                            stringBuilder.append(nickName).append(",");
+                        }
+                        if (StringUtils.isNotBlank(identityLink.getGroupId())) {
+                            if (identityLink.getGroupId().startsWith(TaskConstants.ROLE_GROUP_PREFIX)) {
+                                Long roleId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.ROLE_GROUP_PREFIX));
+                                //finish do 鏌ヨ瑙掕壊鍚嶇О
+                                SysRoleVo roleR=roleService.selectRoleById(roleId);
+                                if (!ObjectUtil.isNull(roleR)) {
+                                    stringBuilder.append(roleR.getRoleName()).append(",");
+                                }
+                            } else if (identityLink.getGroupId().startsWith(TaskConstants.DEPT_GROUP_PREFIX)) {
+                                Long deptId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.DEPT_GROUP_PREFIX));
+                                //finish do 鏌ヨ閮ㄩ棬鍚嶇О
+                                SysDeptVo deptR=deptService.selectDeptById(deptId);
+                                if (!ObjectUtil.isNull(deptR)) {
+                                    stringBuilder.append(deptR.getDeptName()).append(",");
+                                }
+                            }
+                        }
+                    }
+                }
+                if (StringUtils.isNotBlank(stringBuilder)) {
+                    taskVo.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1));
+                }
+                if (ObjectUtil.isNotNull(taskInstance.getDurationInMillis())) {
+                    taskVo.setDuration(DateUtil.formatBetween(taskInstance.getDurationInMillis(), BetweenFormatter.Level.SECOND));
+                }
+                // 鑾峰彇鎰忚璇勮鍐呭
+                if (CollUtil.isNotEmpty(commentList)) {
+                    List<Comment> comments = new ArrayList<>();
+                    // commentList.stream().filter(comment -> taskInstance.getId().equals(comment.getTaskId())).collect(Collectors.toList());
+                    for (Comment comment : commentList) {
+                        if (comment.getTaskId().equals(taskInstance.getId())) {
+                            comments.add(comment);
+                            // taskVo.setComment(WfCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build());
+                        }
+                    }
+                    taskVo.setCommentList(comments);
+                }
+                taskVoList.add(taskVo);
+            });
+            map.put("flowList", taskVoList);
+//            // 鏌ヨ褰撳墠浠诲姟鏄惁瀹屾垚
+//            List<Task> taskList = taskService.createTaskQuery().processInstanceId(procInsId).list();
+//            if (CollectionUtils.isNotEmpty(taskList)) {
+//                map.put("finished", true);
+//            } else {
+//                map.put("finished", false);
+//            }
+        }
+        // 绗竴娆$敵璇疯幏鍙栧垵濮嬪寲琛ㄥ崟
+        if (StringUtils.isNotBlank(deployId)) {
+            WfFormVo formVo = deployFormService.selectDeployFormByDeployId(deployId);
+            if (Objects.isNull(formVo)) {
+                throw new ServiceException("璇峰厛閰嶇疆娴佺▼琛ㄥ崟");
+            }
+            map.put("formData", JsonUtils.parseObject(formVo.getContent(), Map.class));
+        }
+        return map;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java
new file mode 100644
index 0000000..a6378f8
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfModelServiceImpl.java
@@ -0,0 +1,364 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.flowable.common.constant.ProcessConstants;
+import org.ruoyi.flowable.common.enums.FormType;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.factory.FlowServiceFactory;
+import org.ruoyi.flowable.utils.JsonUtils;
+import org.ruoyi.flowable.utils.ModelUtils;
+import org.ruoyi.flowable.workflow.domain.bo.WfModelBo;
+import org.ruoyi.flowable.workflow.domain.dto.WfMetaInfoDto;
+import org.ruoyi.flowable.workflow.domain.vo.WfFormVo;
+import org.ruoyi.flowable.workflow.domain.vo.WfModelVo;
+import org.ruoyi.flowable.workflow.service.IWfDeployFormService;
+import org.ruoyi.flowable.workflow.service.IWfFormService;
+import org.ruoyi.flowable.workflow.service.IWfModelService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.StartEvent;
+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.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/6/21 9:11
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class WfModelServiceImpl extends FlowServiceFactory implements IWfModelService {
+
+    private final IWfFormService formService;
+    private final IWfDeployFormService deployFormService;
+
+    @Override
+    public TableDataInfo<WfModelVo> list(WfModelBo modelBo, PageQuery pageQuery) {
+        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
+        // 鏋勫缓鏌ヨ鏉′欢
+        if (StringUtils.isNotBlank(modelBo.getModelKey())) {
+            modelQuery.modelKey(modelBo.getModelKey());
+        }
+        if (StringUtils.isNotBlank(modelBo.getModelName())) {
+            modelQuery.modelNameLike("%" + modelBo.getModelName() + "%");
+        }
+        if (StringUtils.isNotBlank(modelBo.getCategory())) {
+            modelQuery.modelCategory(modelBo.getCategory());
+        }
+        // 鎵ц鏌ヨ
+        long pageTotal = modelQuery.count();
+        if (pageTotal <= 0) {
+            return TableDataInfo.build();
+        }
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<Model> modelList = modelQuery.listPage(offset, pageQuery.getPageSize());
+        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
+        modelList.forEach(model -> {
+            WfModelVo modelVo = new WfModelVo();
+            modelVo.setModelId(model.getId());
+            modelVo.setModelName(model.getName());
+            modelVo.setModelKey(model.getKey());
+            modelVo.setCategory(model.getCategory());
+            modelVo.setCreateTime(model.getCreateTime());
+            modelVo.setVersion(model.getVersion());
+            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
+            if (metaInfo != null) {
+                modelVo.setDescription(metaInfo.getDescription());
+                modelVo.setFormType(metaInfo.getFormType());
+                modelVo.setFormId(metaInfo.getFormId());
+            }
+            modelVoList.add(modelVo);
+        });
+        Page<WfModelVo> page = new Page<>();
+        page.setRecords(modelVoList);
+        page.setTotal(pageTotal);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfModelVo> list(WfModelBo modelBo) {
+        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
+        // 鏋勫缓鏌ヨ鏉′欢
+        if (StringUtils.isNotBlank(modelBo.getModelKey())) {
+            modelQuery.modelKey(modelBo.getModelKey());
+        }
+        if (StringUtils.isNotBlank(modelBo.getModelName())) {
+            modelQuery.modelNameLike("%" + modelBo.getModelName() + "%");
+        }
+        if (StringUtils.isNotBlank(modelBo.getCategory())) {
+            modelQuery.modelCategory(modelBo.getCategory());
+        }
+        List<Model> modelList = modelQuery.list();
+        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
+        modelList.forEach(model -> {
+            WfModelVo modelVo = new WfModelVo();
+            modelVo.setModelId(model.getId());
+            modelVo.setModelName(model.getName());
+            modelVo.setModelKey(model.getKey());
+            modelVo.setCategory(model.getCategory());
+            modelVo.setCreateTime(model.getCreateTime());
+            modelVo.setVersion(model.getVersion());
+            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
+            if (metaInfo != null) {
+                modelVo.setDescription(metaInfo.getDescription());
+                modelVo.setFormType(metaInfo.getFormType());
+                modelVo.setFormId(metaInfo.getFormId());
+            }
+            modelVoList.add(modelVo);
+        });
+        return modelVoList;
+    }
+
+    @Override
+    public TableDataInfo<WfModelVo> historyList(WfModelBo modelBo, PageQuery pageQuery) {
+        ModelQuery modelQuery = repositoryService.createModelQuery()
+            .modelKey(modelBo.getModelKey())
+            .orderByModelVersion()
+            .desc();
+        // 鎵ц鏌ヨ锛堜笉鏄剧ず鏈�鏂扮増锛�-1锛�
+        long pageTotal = modelQuery.count() - 1;
+        if (pageTotal <= 0) {
+            return TableDataInfo.build();
+        }
+        // offset+1锛屽幓鎺夋渶鏂扮増
+        int offset = 1 + pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<Model> modelList = modelQuery.listPage(offset, pageQuery.getPageSize());
+        List<WfModelVo> modelVoList = new ArrayList<>(modelList.size());
+        modelList.forEach(model -> {
+            WfModelVo modelVo = new WfModelVo();
+            modelVo.setModelId(model.getId());
+            modelVo.setModelName(model.getName());
+            modelVo.setModelKey(model.getKey());
+            modelVo.setCategory(model.getCategory());
+            modelVo.setCreateTime(model.getCreateTime());
+            modelVo.setVersion(model.getVersion());
+            WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
+            if (metaInfo != null) {
+                modelVo.setDescription(metaInfo.getDescription());
+                modelVo.setFormType(metaInfo.getFormType());
+                modelVo.setFormId(metaInfo.getFormId());
+            }
+            modelVoList.add(modelVo);
+        });
+        Page<WfModelVo> page = new Page<>();
+        page.setRecords(modelVoList);
+        page.setTotal(pageTotal);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public WfModelVo getModel(String modelId) {
+        // 鑾峰彇娴佺▼妯″瀷
+        Model model = repositoryService.getModel(modelId);
+        if (ObjectUtil.isNull(model)) {
+            throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+        }
+        // 鑾峰彇娴佺▼鍥�
+        String bpmnXml = queryBpmnXmlById(modelId);
+        WfModelVo modelVo = new WfModelVo();
+        modelVo.setModelId(model.getId());
+        modelVo.setModelName(model.getName());
+        modelVo.setModelKey(model.getKey());
+        modelVo.setCategory(model.getCategory());
+        modelVo.setCreateTime(model.getCreateTime());
+        modelVo.setVersion(model.getVersion());
+        modelVo.setBpmnXml(bpmnXml);
+        WfMetaInfoDto metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
+        if (metaInfo != null) {
+            modelVo.setDescription(metaInfo.getDescription());
+            modelVo.setFormType(metaInfo.getFormType());
+            modelVo.setFormId(metaInfo.getFormId());
+            if (FormType.PROCESS.getType().equals(metaInfo.getFormType())) {
+                WfFormVo wfFormVo = formService.queryById(metaInfo.getFormId());
+                modelVo.setContent(wfFormVo.getContent());
+            }
+        }
+        return modelVo;
+    }
+
+    @Override
+    public String queryBpmnXmlById(String modelId) {
+        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
+        return StrUtil.utf8Str(bpmnBytes);
+    }
+
+    @Override
+    public void insertModel(WfModelBo modelBo) {
+        Model model = repositoryService.newModel();
+        model.setName(modelBo.getModelName());
+        model.setKey(modelBo.getModelKey());
+        model.setCategory(modelBo.getCategory());
+        String metaInfo = buildMetaInfo(new WfMetaInfoDto(), modelBo.getDescription());
+        model.setMetaInfo(metaInfo);
+        // 淇濆瓨娴佺▼妯″瀷
+        repositoryService.saveModel(model);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateModel(WfModelBo modelBo) {
+        // 鏍规嵁妯″瀷Key鏌ヨ妯″瀷淇℃伅
+        Model model = repositoryService.getModel(modelBo.getModelId());
+        if (ObjectUtil.isNull(model)) {
+            throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+        }
+        model.setCategory(modelBo.getCategory());
+        WfMetaInfoDto metaInfoDto = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfoDto.class);
+        String metaInfo = buildMetaInfo(metaInfoDto, modelBo.getDescription());
+        model.setMetaInfo(metaInfo);
+        // 淇濆瓨娴佺▼妯″瀷
+        repositoryService.saveModel(model);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveModel(WfModelBo modelBo) {
+        // 鏌ヨ妯″瀷淇℃伅
+        Model model = repositoryService.getModel(modelBo.getModelId());
+        if (ObjectUtil.isNull(model)) {
+            throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+        }
+        BpmnModel bpmnModel = ModelUtils.getBpmnModel(modelBo.getBpmnXml());
+        if (ObjectUtil.isEmpty(bpmnModel)) {
+            throw new RuntimeException("鑾峰彇妯″瀷璁捐澶辫触锛�");
+        }
+        String processName = bpmnModel.getMainProcess().getName();
+        // 鑾峰彇寮�濮嬭妭鐐�
+        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
+        if (ObjectUtil.isNull(startEvent)) {
+            throw new RuntimeException("寮�濮嬭妭鐐逛笉瀛樺湪锛岃妫�鏌ユ祦绋嬭璁℃槸鍚︽湁璇紒");
+        }
+        // 鑾峰彇寮�濮嬭妭鐐归厤缃殑琛ㄥ崟Key
+        if (StrUtil.isBlank(startEvent.getFormKey())) {
+            throw new RuntimeException("璇烽厤缃祦绋嬭〃鍗�");
+        }
+        Model newModel;
+        if (Boolean.TRUE.equals(modelBo.getNewVersion())) {
+            newModel = repositoryService.newModel();
+            newModel.setName(processName);
+            newModel.setKey(model.getKey());
+            newModel.setCategory(model.getCategory());
+            newModel.setMetaInfo(model.getMetaInfo());
+            newModel.setVersion(model.getVersion() + 1);
+        } else {
+            newModel = model;
+            // 璁剧疆娴佺▼鍚嶇О
+            newModel.setName(processName);
+        }
+        // 淇濆瓨娴佺▼妯″瀷
+        repositoryService.saveModel(newModel);
+        // 淇濆瓨 BPMN XML
+        byte[] bpmnXmlBytes = StringUtils.getBytes(modelBo.getBpmnXml(), StandardCharsets.UTF_8);
+        repositoryService.addModelEditorSource(newModel.getId(), bpmnXmlBytes);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void latestModel(String modelId) {
+        // 鑾峰彇娴佺▼妯″瀷
+        Model model = repositoryService.getModel(modelId);
+        if (ObjectUtil.isNull(model)) {
+            throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+        }
+        Integer latestVersion = repositoryService.createModelQuery()
+            .modelKey(model.getKey())
+            .latestVersion()
+            .singleResult()
+            .getVersion();
+        if (model.getVersion().equals(latestVersion)) {
+            throw new RuntimeException("褰撳墠鐗堟湰宸叉槸鏈�鏂扮増锛�");
+        }
+        // 鑾峰彇 BPMN XML
+        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
+        Model newModel = repositoryService.newModel();
+        newModel.setName(model.getName());
+        newModel.setKey(model.getKey());
+        newModel.setCategory(model.getCategory());
+        newModel.setMetaInfo(model.getMetaInfo());
+        newModel.setVersion(latestVersion + 1);
+        // 淇濆瓨娴佺▼妯″瀷
+        repositoryService.saveModel(newModel);
+        // 淇濆瓨 BPMN XML
+        repositoryService.addModelEditorSource(newModel.getId(), bpmnBytes);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteByIds(Collection<String> ids) {
+        ids.forEach(id -> {
+            Model model = repositoryService.getModel(id);
+            if (ObjectUtil.isNull(model)) {
+                throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+            }
+            repositoryService.deleteModel(id);
+        });
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deployModel(String modelId) {
+        // 鑾峰彇娴佺▼妯″瀷
+        Model model = repositoryService.getModel(modelId);
+        if (ObjectUtil.isNull(model)) {
+            throw new RuntimeException("娴佺▼妯″瀷涓嶅瓨鍦紒");
+        }
+        // 鑾峰彇娴佺▼鍥�
+        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
+        if (ArrayUtil.isEmpty(bpmnBytes)) {
+            throw new RuntimeException("璇峰厛璁捐娴佺▼鍥撅紒");
+        }
+        String bpmnXml = StringUtils.toEncodedString(bpmnBytes, StandardCharsets.UTF_8);
+        BpmnModel bpmnModel = ModelUtils.getBpmnModel(bpmnXml);
+        String processName = model.getName() + ProcessConstants.SUFFIX;
+        // 閮ㄧ讲娴佺▼
+        Deployment deployment = repositoryService.createDeployment()
+            .name(model.getName())
+            .key(model.getKey())
+            .category(model.getCategory())
+            .addBytes(processName, bpmnBytes)
+            .deploy();
+        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
+            .deploymentId(deployment.getId())
+            .singleResult();
+        // 淇敼娴佺▼瀹氫箟鐨勫垎绫伙紝渚夸簬鎼滅储娴佺▼
+        repositoryService.setProcessDefinitionCategory(procDef.getId(), model.getCategory());
+        // 淇濆瓨閮ㄧ讲琛ㄥ崟
+        return deployFormService.saveInternalDeployForm(deployment.getId(), bpmnModel);
+    }
+
+    /**
+     * 鏋勫缓妯″瀷鎵╁睍淇℃伅
+     * @return
+     */
+    private String buildMetaInfo(WfMetaInfoDto metaInfo, String description) {
+        // 鍙湁闈炵┖锛屾墠杩涜璁剧疆锛岄伩鍏嶆洿鏂版椂鐨勮鐩�
+        if (StringUtils.isNotEmpty(description)) {
+            metaInfo.setDescription(description);
+        }
+        if (StringUtils.isNotEmpty(metaInfo.getCreateUser())) {
+            // 鑾峰彇褰撳墠鐨勭敤鎴�
+            LoginUser loginUser = LoginHelper.getLoginUser();
+            if (ObjectUtil.isNotNull(loginUser)){
+                metaInfo.setCreateUser(loginUser.getUsername());
+            }
+        }
+        return JsonUtils.toJsonString(metaInfo);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java
new file mode 100644
index 0000000..1d65780
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfProcessServiceImpl.java
@@ -0,0 +1,1037 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.BetweenFormatter;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.DateUtils;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.flowable.common.constant.ProcessConstants;
+import org.ruoyi.flowable.common.constant.TaskConstants;
+import org.ruoyi.flowable.common.enums.ProcessStatus;
+import org.ruoyi.flowable.core.FormConf;
+import org.ruoyi.flowable.core.domain.ProcessQuery;
+import org.ruoyi.flowable.core.domain.model.PageQuery;
+import org.ruoyi.flowable.core.page.TableDataInfo;
+import org.ruoyi.flowable.factory.FlowServiceFactory;
+import org.ruoyi.flowable.flow.FlowableUtils;
+import org.ruoyi.flowable.utils.*;
+import org.ruoyi.flowable.workflow.domain.WfDeployForm;
+import org.ruoyi.flowable.workflow.domain.vo.*;
+import org.ruoyi.flowable.workflow.mapper.WfDeployFormMapper;
+import org.ruoyi.flowable.workflow.service.IWfProcessService;
+import org.ruoyi.flowable.workflow.service.IWfTaskService;
+import lombok.RequiredArgsConstructor;
+import org.flowable.bpmn.constants.BpmnXMLConstants;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricActivityInstanceQuery;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.history.HistoricProcessInstanceQuery;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.repository.ProcessDefinitionQuery;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.engine.task.Comment;
+import org.flowable.identitylink.api.history.HistoricIdentityLink;
+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.variable.api.history.HistoricVariableInstance;
+import org.ruoyi.system.domain.SysDept;
+import org.ruoyi.system.domain.SysRole;
+import org.ruoyi.system.domain.SysUser;
+import org.ruoyi.system.domain.vo.SysDeptVo;
+import org.ruoyi.system.domain.vo.SysRoleVo;
+import org.ruoyi.system.domain.vo.SysUserVo;
+import org.ruoyi.system.service.ISysDeptService;
+import org.ruoyi.system.service.ISysRoleService;
+import org.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.InputStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/24 18:57
+ */
+@RequiredArgsConstructor
+@Service
+public class WfProcessServiceImpl extends FlowServiceFactory implements IWfProcessService {
+
+    private final IWfTaskService wfTaskService;
+    private final ISysUserService userService;
+    private final WfDeployFormMapper deployFormMapper;
+    private final ISysDeptService deptService;
+    private final ISysRoleService roleService;
+    @Autowired
+    private ISysUserService remoteUserService;
+    @Autowired
+    private ISysDeptService remoteDeptService;
+    /**
+     * 娴佺▼瀹氫箟鍒楄〃
+     *
+     * @param pageQuery 鍒嗛〉鍙傛暟
+     * @return 娴佺▼瀹氫箟鍒嗛〉鍒楄〃鏁版嵁
+     */
+    @Override
+    public TableDataInfo<WfDefinitionVo> selectPageStartProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        Page<WfDefinitionVo> page = new Page<>();
+        // 娴佺▼瀹氫箟鍒楄〃鏁版嵁鏌ヨ
+        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
+            .latestVersion()
+            .active()
+            .orderByProcessDefinitionKey()
+            .asc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
+        long pageTotal = processDefinitionQuery.count();
+        if (pageTotal <= 0) {
+            return TableDataInfo.build();
+        }
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<ProcessDefinition> definitionList = processDefinitionQuery.listPage(offset, pageQuery.getPageSize());
+
+        List<WfDefinitionVo> definitionVoList = new ArrayList<>();
+        for (ProcessDefinition processDefinition : definitionList) {
+            String deploymentId = processDefinition.getDeploymentId();
+            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
+            WfDefinitionVo vo = new WfDefinitionVo();
+            vo.setDefinitionId(processDefinition.getId());
+            vo.setProcessKey(processDefinition.getKey());
+            vo.setProcessName(processDefinition.getName());
+            vo.setVersion(processDefinition.getVersion());
+            vo.setDeploymentId(processDefinition.getDeploymentId());
+            vo.setSuspended(processDefinition.isSuspended());
+            // 娴佺▼瀹氫箟鏃堕棿
+            vo.setCategory(deployment.getCategory());
+            vo.setDeploymentTime(deployment.getDeploymentTime());
+            definitionVoList.add(vo);
+        }
+        page.setRecords(definitionVoList);
+        page.setTotal(pageTotal);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfDefinitionVo> selectStartProcessList(ProcessQuery processQuery) {
+        // 娴佺▼瀹氫箟鍒楄〃鏁版嵁鏌ヨ
+        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
+                .latestVersion()
+                .active()
+                .orderByProcessDefinitionKey()
+                .asc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
+
+        List<ProcessDefinition> definitionList = processDefinitionQuery.list();
+
+        List<WfDefinitionVo> definitionVoList = new ArrayList<>();
+        for (ProcessDefinition processDefinition : definitionList) {
+            String deploymentId = processDefinition.getDeploymentId();
+            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
+            WfDefinitionVo vo = new WfDefinitionVo();
+            vo.setDefinitionId(processDefinition.getId());
+            vo.setProcessKey(processDefinition.getKey());
+            vo.setProcessName(processDefinition.getName());
+            vo.setVersion(processDefinition.getVersion());
+            vo.setDeploymentId(processDefinition.getDeploymentId());
+            vo.setSuspended(processDefinition.isSuspended());
+            // 娴佺▼瀹氫箟鏃堕棿
+            vo.setCategory(deployment.getCategory());
+            vo.setDeploymentTime(deployment.getDeploymentTime());
+            definitionVoList.add(vo);
+        }
+        return definitionVoList;
+    }
+
+    @Override
+    public TableDataInfo<WfTaskVo> selectPageOwnProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        Page<WfTaskVo> page = new Page<>();
+        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
+            .startedBy(TaskUtils.getUserId())
+            .orderByProcessInstanceStartTime()
+            .desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(historicProcessInstanceQuery, processQuery);
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery
+            .listPage(offset, pageQuery.getPageSize());
+        page.setTotal(historicProcessInstanceQuery.count());
+        List<WfTaskVo> taskVoList = new ArrayList<>();
+        for (HistoricProcessInstance hisIns : historicProcessInstances) {
+            WfTaskVo taskVo = new WfTaskVo();
+            // 鑾峰彇娴佺▼鐘舵��
+            HistoricVariableInstance processStatusVariable = historyService.createHistoricVariableInstanceQuery()
+                .processInstanceId(hisIns.getId())
+                .variableName(ProcessConstants.PROCESS_STATUS_KEY)
+                .singleResult();
+            String processStatus = null;
+            if (ObjectUtil.isNotNull(processStatusVariable)) {
+                processStatus = Convert.toStr(processStatusVariable.getValue());
+            }
+            // 鍏煎鏃ф祦绋�
+            if (processStatus == null) {
+                processStatus = ObjectUtil.isNull(hisIns.getEndTime()) ? ProcessStatus.RUNNING.getStatus() : ProcessStatus.COMPLETED.getStatus();
+            }
+            taskVo.setProcessStatus(processStatus);
+            taskVo.setCreateTime(hisIns.getStartTime());
+            taskVo.setFinishTime(hisIns.getEndTime());
+            taskVo.setProcInsId(hisIns.getId());
+
+            // 璁$畻鑰楁椂
+            if (Objects.nonNull(hisIns.getEndTime())) {
+                taskVo.setDuration(DateUtils.getDatePoor(hisIns.getEndTime(), hisIns.getStartTime()));
+            } else {
+                taskVo.setDuration(DateUtils.getDatePoor(DateUtils.getNowDate(), hisIns.getStartTime()));
+            }
+            // 娴佺▼閮ㄧ讲瀹炰緥淇℃伅
+            Deployment deployment = repositoryService.createDeploymentQuery()
+                .deploymentId(hisIns.getDeploymentId()).singleResult();
+            taskVo.setDeployId(hisIns.getDeploymentId());
+            taskVo.setProcDefId(hisIns.getProcessDefinitionId());
+            taskVo.setProcDefName(hisIns.getProcessDefinitionName());
+            taskVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
+            taskVo.setCategory(deployment.getCategory());
+            // 褰撳墠鎵�澶勬祦绋�
+            List<Task> taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).includeIdentityLinks().list();
+            if (CollUtil.isNotEmpty(taskList)) {
+                taskVo.setTaskName(taskList.stream().map(Task::getName).filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")));
+            }
+            taskVoList.add(taskVo);
+        }
+        page.setRecords(taskVoList);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfTaskVo> selectOwnProcessList(ProcessQuery processQuery) {
+        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
+                .startedBy(TaskUtils.getUserId())
+                .orderByProcessInstanceStartTime()
+                .desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(historicProcessInstanceQuery, processQuery);
+        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.list();
+        List<WfTaskVo> taskVoList = new ArrayList<>();
+        for (HistoricProcessInstance hisIns : historicProcessInstances) {
+            WfTaskVo taskVo = new WfTaskVo();
+            taskVo.setCreateTime(hisIns.getStartTime());
+            taskVo.setFinishTime(hisIns.getEndTime());
+            taskVo.setProcInsId(hisIns.getId());
+
+            // 璁$畻鑰楁椂
+            if (Objects.nonNull(hisIns.getEndTime())) {
+                taskVo.setDuration(DateUtils.getDatePoor(hisIns.getEndTime(), hisIns.getStartTime()));
+            } else {
+                taskVo.setDuration(DateUtils.getDatePoor(DateUtils.getNowDate(), hisIns.getStartTime()));
+            }
+            // 娴佺▼閮ㄧ讲瀹炰緥淇℃伅
+            Deployment deployment = repositoryService.createDeploymentQuery()
+                    .deploymentId(hisIns.getDeploymentId()).singleResult();
+            taskVo.setDeployId(hisIns.getDeploymentId());
+            taskVo.setProcDefId(hisIns.getProcessDefinitionId());
+            taskVo.setProcDefName(hisIns.getProcessDefinitionName());
+            taskVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
+            taskVo.setCategory(deployment.getCategory());
+            // 褰撳墠鎵�澶勬祦绋�
+            List<Task> taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).includeIdentityLinks().list();
+            if (CollUtil.isNotEmpty(taskList)) {
+                taskVo.setTaskName(taskList.stream().map(Task::getName).filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")));
+            }
+            taskVoList.add(taskVo);
+        }
+        return taskVoList;
+    }
+
+    @Override
+    public TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        Page<WfTaskVo> page = new Page<>();
+        TaskQuery taskQuery = taskService.createTaskQuery()
+            .active()
+            .includeProcessVariables()
+            .taskCandidateOrAssigned(TaskUtils.getUserId())
+            .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
+            .orderByTaskCreateTime().desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
+        page.setTotal(taskQuery.count());
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize());
+        List<WfTaskVo> flowList = new ArrayList<>();
+        for (Task task : taskList) {
+            WfTaskVo flowTask = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            flowTask.setTaskId(task.getId());
+            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
+            flowTask.setCreateTime(task.getCreateTime());
+            flowTask.setProcDefId(task.getProcessDefinitionId());
+            flowTask.setTaskName(task.getName());
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionId(task.getProcessDefinitionId())
+                .singleResult();
+            flowTask.setDeployId(pd.getDeploymentId());
+            flowTask.setProcDefName(pd.getName());
+            flowTask.setProcDefVersion(pd.getVersion());
+            flowTask.setProcInsId(task.getProcessInstanceId());
+            flowTask.setCategory(pd.getCategory());
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceId(task.getProcessInstanceId())
+                .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            flowTask.setStartUserId(userId);
+            flowTask.setStartUserName(nickName);
+
+            // 娴佺▼鍙橀噺
+            flowTask.setProcVars(task.getProcessVariables());
+
+            flowList.add(flowTask);
+        }
+        page.setRecords(flowList);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfTaskVo> selectTodoProcessList(ProcessQuery processQuery) {
+        TaskQuery taskQuery = taskService.createTaskQuery()
+                .active()
+                .includeProcessVariables()
+                .taskCandidateOrAssigned(TaskUtils.getUserId())
+                .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
+                .orderByTaskCreateTime().desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
+        List<Task> taskList = taskQuery.list();
+        List<WfTaskVo> taskVoList = new ArrayList<>();
+        for (Task task : taskList) {
+            WfTaskVo taskVo = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            taskVo.setTaskId(task.getId());
+            taskVo.setTaskDefKey(task.getTaskDefinitionKey());
+            taskVo.setCreateTime(task.getCreateTime());
+            taskVo.setProcDefId(task.getProcessDefinitionId());
+            taskVo.setTaskName(task.getName());
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                    .processDefinitionId(task.getProcessDefinitionId())
+                    .singleResult();
+            taskVo.setDeployId(pd.getDeploymentId());
+            taskVo.setProcDefName(pd.getName());
+            taskVo.setProcDefVersion(pd.getVersion());
+            taskVo.setProcInsId(task.getProcessInstanceId());
+
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                    .processInstanceId(task.getProcessInstanceId())
+                    .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            taskVo.setStartUserId(userId);
+            taskVo.setStartUserName(nickName);
+
+            taskVoList.add(taskVo);
+        }
+        return taskVoList;
+    }
+
+    @Override
+    public TableDataInfo<WfTaskVo> selectPageClaimProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        Page<WfTaskVo> page = new Page<>();
+        TaskQuery taskQuery = taskService.createTaskQuery()
+            .active()
+            .includeProcessVariables()
+            .taskCandidateUser(TaskUtils.getUserId())
+            .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
+            .orderByTaskCreateTime().desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
+        page.setTotal(taskQuery.count());
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize());
+        List<WfTaskVo> flowList = new ArrayList<>();
+        for (Task task : taskList) {
+            WfTaskVo flowTask = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            flowTask.setTaskId(task.getId());
+            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
+            flowTask.setCreateTime(task.getCreateTime());
+            flowTask.setProcDefId(task.getProcessDefinitionId());
+            flowTask.setTaskName(task.getName());
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionId(task.getProcessDefinitionId())
+                .singleResult();
+            flowTask.setDeployId(pd.getDeploymentId());
+            flowTask.setProcDefName(pd.getName());
+            flowTask.setProcDefVersion(pd.getVersion());
+            flowTask.setProcInsId(task.getProcessInstanceId());
+
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceId(task.getProcessInstanceId())
+                .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            flowTask.setStartUserId(userId);
+            flowTask.setStartUserName(nickName);
+
+            flowList.add(flowTask);
+        }
+        page.setRecords(flowList);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfTaskVo> selectClaimProcessList(ProcessQuery processQuery) {
+        TaskQuery taskQuery = taskService.createTaskQuery()
+                .active()
+                .includeProcessVariables()
+                .taskCandidateUser(TaskUtils.getUserId())
+                .taskCandidateGroupIn(TaskUtils.getCandidateGroup())
+                .orderByTaskCreateTime().desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
+        List<Task> taskList = taskQuery.list();
+        List<WfTaskVo> flowList = new ArrayList<>();
+        for (Task task : taskList) {
+            WfTaskVo flowTask = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            flowTask.setTaskId(task.getId());
+            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
+            flowTask.setCreateTime(task.getCreateTime());
+            flowTask.setProcDefId(task.getProcessDefinitionId());
+            flowTask.setTaskName(task.getName());
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                    .processDefinitionId(task.getProcessDefinitionId())
+                    .singleResult();
+            flowTask.setDeployId(pd.getDeploymentId());
+            flowTask.setProcDefName(pd.getName());
+            flowTask.setProcDefVersion(pd.getVersion());
+            flowTask.setProcInsId(task.getProcessInstanceId());
+
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                    .processInstanceId(task.getProcessInstanceId())
+                    .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            flowTask.setStartUserId(userId);
+            flowTask.setStartUserName(nickName);
+
+            flowList.add(flowTask);
+        }
+        return flowList;
+    }
+
+    @Override
+    public TableDataInfo<WfTaskVo> selectPageFinishedProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
+        Page<WfTaskVo> page = new Page<>();
+        HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
+            .includeProcessVariables()
+            .finished()
+            .taskAssignee(TaskUtils.getUserId())
+            .orderByHistoricTaskInstanceEndTime()
+            .desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskInstanceQuery, processQuery);
+        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
+        List<HistoricTaskInstance> historicTaskInstanceList = taskInstanceQuery.listPage(offset, pageQuery.getPageSize());
+        List<WfTaskVo> hisTaskList = new ArrayList<>();
+        for (HistoricTaskInstance histTask : historicTaskInstanceList) {
+            WfTaskVo flowTask = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            flowTask.setTaskId(histTask.getId());
+            // 瀹℃壒浜哄憳淇℃伅
+            flowTask.setCreateTime(histTask.getCreateTime());
+            flowTask.setFinishTime(histTask.getEndTime());
+            flowTask.setDuration(DateUtil.formatBetween(histTask.getDurationInMillis(), BetweenFormatter.Level.SECOND));
+            flowTask.setProcDefId(histTask.getProcessDefinitionId());
+            flowTask.setTaskDefKey(histTask.getTaskDefinitionKey());
+            flowTask.setTaskName(histTask.getName());
+
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionId(histTask.getProcessDefinitionId())
+                .singleResult();
+            flowTask.setDeployId(pd.getDeploymentId());
+            flowTask.setProcDefName(pd.getName());
+            flowTask.setProcDefVersion(pd.getVersion());
+            flowTask.setProcInsId(histTask.getProcessInstanceId());
+            flowTask.setHisProcInsId(histTask.getProcessInstanceId());
+            flowTask.setCategory(pd.getCategory());
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                .processInstanceId(histTask.getProcessInstanceId())
+                .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            flowTask.setStartUserId(userId);
+            flowTask.setStartUserName(nickName);
+
+            // 娴佺▼鍙橀噺
+            flowTask.setProcVars(histTask.getProcessVariables());
+
+            hisTaskList.add(flowTask);
+        }
+        page.setTotal(taskInstanceQuery.count());
+        page.setRecords(hisTaskList);
+//        Map<String, Object> result = new HashMap<>();
+//        result.put("result",page);
+//        result.put("finished",true);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public List<WfTaskVo> selectFinishedProcessList(ProcessQuery processQuery) {
+        HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
+                .includeProcessVariables()
+                .finished()
+                .taskAssignee(TaskUtils.getUserId())
+                .orderByHistoricTaskInstanceEndTime()
+                .desc();
+        // 鏋勫缓鎼滅储鏉′欢
+        ProcessUtils.buildProcessSearch(taskInstanceQuery, processQuery);
+        List<HistoricTaskInstance> historicTaskInstanceList = taskInstanceQuery.list();
+        List<WfTaskVo> hisTaskList = new ArrayList<>();
+        for (HistoricTaskInstance histTask : historicTaskInstanceList) {
+            WfTaskVo flowTask = new WfTaskVo();
+            // 褰撳墠娴佺▼淇℃伅
+            flowTask.setTaskId(histTask.getId());
+            // 瀹℃壒浜哄憳淇℃伅
+            flowTask.setCreateTime(histTask.getCreateTime());
+            flowTask.setFinishTime(histTask.getEndTime());
+            flowTask.setDuration(DateUtil.formatBetween(histTask.getDurationInMillis(), BetweenFormatter.Level.SECOND));
+            flowTask.setProcDefId(histTask.getProcessDefinitionId());
+            flowTask.setTaskDefKey(histTask.getTaskDefinitionKey());
+            flowTask.setTaskName(histTask.getName());
+
+            // 娴佺▼瀹氫箟淇℃伅
+            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
+                    .processDefinitionId(histTask.getProcessDefinitionId())
+                    .singleResult();
+            flowTask.setDeployId(pd.getDeploymentId());
+            flowTask.setProcDefName(pd.getName());
+            flowTask.setProcDefVersion(pd.getVersion());
+            flowTask.setProcInsId(histTask.getProcessInstanceId());
+            flowTask.setHisProcInsId(histTask.getProcessInstanceId());
+
+            // 娴佺▼鍙戣捣浜轰俊鎭�
+            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
+                    .processInstanceId(histTask.getProcessInstanceId())
+                    .singleResult();
+            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
+            String nickName = null;
+            SysUserVo user = userService.selectUserById(userId);
+            if(!ObjectUtil.isNull(user)) {
+                nickName=user.getNickName();
+            }
+            flowTask.setStartUserId(userId);
+            flowTask.setStartUserName(nickName);
+
+            // 娴佺▼鍙橀噺
+            flowTask.setProcVars(histTask.getProcessVariables());
+
+            hisTaskList.add(flowTask);
+        }
+        return hisTaskList;
+    }
+
+    @Override
+    public FormConf selectFormContent(String definitionId, String deployId, String procInsId) {
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(definitionId);
+        if (ObjectUtil.isNull(bpmnModel)) {
+            throw new RuntimeException("鑾峰彇娴佺▼璁捐澶辫触锛�");
+        }
+        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
+        WfDeployForm deployForm = deployFormMapper.selectOne(new LambdaQueryWrapper<WfDeployForm>()
+            .eq(WfDeployForm::getDeployId, deployId)
+            .eq(WfDeployForm::getFormKey, startEvent.getFormKey())
+            .eq(WfDeployForm::getNodeKey, startEvent.getId()));
+        Map<String, Object> formModel = JsonUtils.parseObject(deployForm.getContent(), Map.class);
+        if (null == formModel || formModel.isEmpty()) {
+            throw new RuntimeException("鑾峰彇娴佺▼琛ㄥ崟澶辫触锛�");
+        }
+
+
+        FormConf formConf = new FormConf();
+        formConf.setFormBtns(false);
+        formConf.setFormModel(formModel);
+        if (ObjectUtil.isNotEmpty(procInsId)) {
+            // 鑾峰彇娴佺▼瀹炰緥
+            HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
+                    .processInstanceId(procInsId)
+                    .includeProcessVariables()
+                    .singleResult();
+            formConf.setFormData(historicProcIns.getProcessVariables());
+        }
+        return formConf;
+    }
+
+    /**
+     * 鏍规嵁娴佺▼瀹氫箟ID鍚姩娴佺▼瀹炰緥
+     *
+     * @param procDefId 娴佺▼瀹氫箟Id
+     * @param variables 娴佺▼鍙橀噺
+     * @return
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void startProcessByDefId(String procDefId, Map<String, Object> variables) {
+        try {
+            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionId(procDefId).singleResult();
+            startProcess(processDefinition, variables);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException("娴佺▼鍚姩閿欒");
+        }
+    }
+
+    /**
+     * 閫氳繃DefinitionKey鍚姩娴佺▼
+     * @param procDefKey 娴佺▼瀹氫箟Key
+     * @param variables 鎵╁睍鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void startProcessByDefKey(String procDefKey, Map<String, Object> variables) {
+        try {
+            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
+                .processDefinitionKey(procDefKey).latestVersion().singleResult();
+            startProcess(processDefinition, variables);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException("娴佺▼鍚姩閿欒");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteProcessByIds(String[] instanceIds) {
+        List<String> ids = Arrays.asList(instanceIds);
+        // 鏍¢獙娴佺▼鏄惁缁撴潫
+        long activeInsCount = runtimeService.createProcessInstanceQuery()
+            .processInstanceIds(new HashSet<>(ids)).active().count();
+        if (activeInsCount > 0) {
+            throw new ServiceException("涓嶅厑璁稿垹闄よ繘琛屼腑鐨勬祦绋嬪疄渚�");
+        }
+        // 鍒犻櫎鍘嗗彶娴佺▼瀹炰緥
+        historyService.bulkDeleteHistoricProcessInstances(ids);
+    }
+
+    /**
+     * 璇诲彇xml鏂囦欢
+     * @param processDefId 娴佺▼瀹氫箟ID
+     */
+    @Override
+    public String queryBpmnXmlById(String processDefId) {
+        InputStream inputStream = repositoryService.getProcessModel(processDefId);
+        try {
+            return IoUtil.readUtf8(inputStream);
+        } catch (IORuntimeException exception) {
+            throw new RuntimeException("鍔犺浇xml鏂囦欢寮傚父");
+        }
+    }
+
+    /**
+     * 娴佺▼璇︽儏淇℃伅
+     *
+     * @param procInsId 娴佺▼瀹炰緥ID
+     * @param taskId 浠诲姟ID
+     * @return
+     */
+    @Override
+    public WfDetailVo queryProcessDetail(String procInsId, String taskId) {
+        WfDetailVo detailVo = new WfDetailVo();
+        // 鑾峰彇娴佺▼瀹炰緥
+        HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
+            .processInstanceId(procInsId)
+            .includeProcessVariables()
+            .singleResult();
+        if (StringUtils.isNotBlank(taskId)) {
+            HistoricTaskInstance taskIns = historyService.createHistoricTaskInstanceQuery()
+                .taskId(taskId)
+                .includeIdentityLinks()
+                .includeProcessVariables()
+                .includeTaskLocalVariables()
+                .singleResult();
+            if (taskIns == null) {
+                throw new ServiceException("娌℃湁鍙姙鐞嗙殑浠诲姟锛�");
+            }
+            detailVo.setTaskFormData(currTaskFormData(historicProcIns.getDeploymentId(), taskIns));
+        }
+        // 鑾峰彇Bpmn妯″瀷淇℃伅
+        InputStream inputStream = repositoryService.getProcessModel(historicProcIns.getProcessDefinitionId());
+        String bpmnXmlStr = StrUtil.utf8Str(IoUtil.readBytes(inputStream, false));
+        BpmnModel bpmnModel = ModelUtils.getBpmnModel(bpmnXmlStr);
+        detailVo.setBpmnXml(bpmnXmlStr);
+        detailVo.setHistoryProcNodeList(historyProcNodeList(historicProcIns));
+        detailVo.setProcessFormList(processFormList(bpmnModel, historicProcIns));
+        detailVo.setFlowViewer(getFlowViewer(bpmnModel, procInsId));
+        return detailVo;
+    }
+
+    /**
+     * 鍚姩娴佺▼瀹炰緥
+     */
+    private void startProcess(ProcessDefinition procDef, Map<String, Object> variables) {
+        if (ObjectUtil.isNotNull(procDef) && procDef.isSuspended()) {
+            throw new ServiceException("娴佺▼宸茶鎸傝捣锛岃鍏堟縺娲绘祦绋�");
+        }
+        // 璁剧疆娴佺▼鍙戣捣浜篒d鍒版祦绋嬩腑
+        String userIdStr = TaskUtils.getUserId();
+        identityService.setAuthenticatedUserId(userIdStr);
+        variables.put(BpmnXMLConstants.ATTRIBUTE_EVENT_START_INITIATOR, userIdStr);
+        // 璁剧疆娴佺▼鐘舵�佷负杩涜涓�
+        variables.put(ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.RUNNING.getStatus());
+        // 鍙戣捣娴佺▼瀹炰緥
+        ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDef.getId(), variables);
+        // 绗竴涓敤鎴蜂换鍔′负鍙戣捣浜猴紝鍒欒嚜鍔ㄥ畬鎴愪换鍔�
+        wfTaskService.startFirstTask(processInstance, variables);
+    }
+
+
+    /**
+     * 鑾峰彇娴佺▼鍙橀噺
+     *
+     * @param taskId 浠诲姟ID
+     * @return 娴佺▼鍙橀噺
+     */
+    private Map<String, Object> getProcessVariables(String taskId) {
+        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
+            .includeProcessVariables()
+            .finished()
+            .taskId(taskId)
+            .singleResult();
+        if (Objects.nonNull(historicTaskInstance)) {
+            return historicTaskInstance.getProcessVariables();
+        }
+        return taskService.getVariables(taskId);
+    }
+
+    /**
+     * 鑾峰彇褰撳墠浠诲姟娴佺▼琛ㄥ崟淇℃伅
+     */
+    private FormConf currTaskFormData(String deployId, HistoricTaskInstance taskIns) {
+        WfDeployFormVo deployFormVo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
+            .eq(WfDeployForm::getDeployId, deployId)
+            .eq(WfDeployForm::getFormKey, taskIns.getFormKey())
+            .eq(WfDeployForm::getNodeKey, taskIns.getTaskDefinitionKey()));
+        if (ObjectUtil.isNotEmpty(deployFormVo)) {
+            FormConf currTaskFormData = JsonUtils.parseObject(deployFormVo.getContent(), FormConf.class);
+            if (null != currTaskFormData) {
+                currTaskFormData.setFormBtns(false);
+                ProcessFormUtils.fillFormData(currTaskFormData, taskIns.getTaskLocalVariables());
+                return currTaskFormData;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鑾峰彇鍘嗗彶娴佺▼琛ㄥ崟淇℃伅
+     */
+    private List<FormConf> processFormList(BpmnModel bpmnModel, HistoricProcessInstance historicProcIns) {
+        List<FormConf> procFormList = new ArrayList<>();
+
+        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(historicProcIns.getId()).finished()
+            .activityTypes(CollUtil.newHashSet(BpmnXMLConstants.ELEMENT_EVENT_START, BpmnXMLConstants.ELEMENT_TASK_USER))
+            .orderByHistoricActivityInstanceStartTime().asc()
+            .list();
+        List<String> processFormKeys = new ArrayList<>();
+        for (HistoricActivityInstance activityInstance : activityInstanceList) {
+            // 鑾峰彇褰撳墠鑺傜偣娴佺▼鍏冪礌淇℃伅
+            FlowElement flowElement = ModelUtils.getFlowElementById(bpmnModel, activityInstance.getActivityId());
+            // 鑾峰彇褰撳墠鑺傜偣琛ㄥ崟Key
+            String formKey = ModelUtils.getFormKey(flowElement);
+            if (formKey == null) {
+                continue;
+            }
+            boolean localScope = Convert.toBool(ModelUtils.getElementAttributeValue(flowElement, ProcessConstants.PROCESS_FORM_LOCAL_SCOPE), false);
+            Map<String, Object> variables;
+            if (localScope) {
+                // 鏌ヨ浠诲姟鑺傜偣鍙傛暟锛屽苟杞崲鎴怣ap
+                variables = historyService.createHistoricVariableInstanceQuery()
+                    .processInstanceId(historicProcIns.getId())
+                    .taskId(activityInstance.getTaskId())
+                    .list()
+                    .stream()
+                    .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
+            } else {
+                if (processFormKeys.contains(formKey)) {
+                    continue;
+                }
+                variables = historicProcIns.getProcessVariables();
+                processFormKeys.add(formKey);
+            }
+            // 闈炶妭鐐硅〃鍗曟澶勬煡璇㈢粨鏋滃彲鑳芥湁澶氭潯锛屽彧鑾峰彇绗竴鏉′俊鎭�
+            List<WfDeployFormVo> formInfoList = deployFormMapper.selectVoList(new LambdaQueryWrapper<WfDeployForm>()
+                .eq(WfDeployForm::getDeployId, historicProcIns.getDeploymentId())
+                .eq(WfDeployForm::getFormKey, formKey)
+                .eq(localScope, WfDeployForm::getNodeKey, flowElement.getId()));
+
+            //@update by Brath锛氶伩鍏嶇┖闆嗗悎瀵艰嚧鐨凬ULL绌烘寚閽�
+            WfDeployFormVo formInfo = formInfoList.stream().findFirst().orElse(null);
+
+            if (ObjectUtil.isNotNull(formInfo)) {
+                // 鏃ф暟鎹� formInfo.getFormName() 涓� null
+                String formName = Optional.ofNullable(formInfo.getFormName()).orElse(StringUtils.EMPTY);
+                String title = localScope ? formName.concat("(" + flowElement.getName() + ")") : formName;
+                FormConf formConf = new FormConf();
+                Map<String, Object> formModel = JsonUtils.parseObject(formInfo.getContent(), Map.class);
+                if (null != formModel && !formModel.isEmpty()) {
+                    formConf.setTitle(title);
+                    formConf.setDisabled(true);
+                    formConf.setFormBtns(false);
+                    formConf.setFormModel(formModel);
+                    formConf.setFormData(variables);
+                    procFormList.add(formConf);
+                }
+            }
+        }
+        return procFormList;
+    }
+
+    @Deprecated
+    private void buildStartFormData(HistoricProcessInstance historicProcIns, Process process, String deployId, List<FormConf> procFormList) {
+        procFormList = procFormList == null ? new ArrayList<>() : procFormList;
+        HistoricActivityInstance startInstance = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(historicProcIns.getId())
+            .activityId(historicProcIns.getStartActivityId())
+            .singleResult();
+        StartEvent startEvent = (StartEvent) process.getFlowElement(startInstance.getActivityId());
+        WfDeployFormVo startFormInfo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
+            .eq(WfDeployForm::getDeployId, deployId)
+            .eq(WfDeployForm::getFormKey, startEvent.getFormKey())
+            .eq(WfDeployForm::getNodeKey, startEvent.getId()));
+        if (ObjectUtil.isNotNull(startFormInfo)) {
+            FormConf formConf = JsonUtils.parseObject(startFormInfo.getContent(), FormConf.class);
+            if (null != formConf) {
+                formConf.setTitle(startEvent.getName());
+                formConf.setDisabled(true);
+                formConf.setFormBtns(false);
+                ProcessFormUtils.fillFormData(formConf, historicProcIns.getProcessVariables());
+                procFormList.add(formConf);
+            }
+        }
+    }
+
+    @Deprecated
+    private void buildUserTaskFormData(String procInsId, String deployId, Process process, List<FormConf> procFormList) {
+        procFormList = procFormList == null ? new ArrayList<>() : procFormList;
+        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(procInsId).finished()
+            .activityType(BpmnXMLConstants.ELEMENT_TASK_USER)
+            .orderByHistoricActivityInstanceStartTime().asc()
+            .list();
+        for (HistoricActivityInstance instanceItem : activityInstanceList) {
+            UserTask userTask = (UserTask) process.getFlowElement(instanceItem.getActivityId(), true);
+            String formKey = userTask.getFormKey();
+            if (formKey == null) {
+                continue;
+            }
+            // 鏌ヨ浠诲姟鑺傜偣鍙傛暟锛屽苟杞崲鎴怣ap
+            Map<String, Object> variables = historyService.createHistoricVariableInstanceQuery()
+                .processInstanceId(procInsId)
+                .taskId(instanceItem.getTaskId())
+                .list()
+                .stream()
+                .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
+            WfDeployFormVo deployFormVo = deployFormMapper.selectVoOne(new LambdaQueryWrapper<WfDeployForm>()
+                .eq(WfDeployForm::getDeployId, deployId)
+                .eq(WfDeployForm::getFormKey, formKey)
+                .eq(WfDeployForm::getNodeKey, userTask.getId()));
+            if (ObjectUtil.isNotNull(deployFormVo)) {
+                FormConf formConf = JsonUtils.parseObject(deployFormVo.getContent(), FormConf.class);
+                if (null != formConf) {
+                    formConf.setTitle(userTask.getName());
+                    formConf.setDisabled(true);
+                    formConf.setFormBtns(false);
+                    ProcessFormUtils.fillFormData(formConf, variables);
+                    procFormList.add(formConf);
+                }
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇鍘嗗彶浠诲姟淇℃伅鍒楄〃
+     */
+    private List<WfProcNodeVo> historyProcNodeList(HistoricProcessInstance historicProcIns) {
+        String procInsId = historicProcIns.getId();
+        List<HistoricActivityInstance> historicActivityInstanceList =  historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(procInsId)
+            .activityTypes(CollUtil.newHashSet(BpmnXMLConstants.ELEMENT_EVENT_START, BpmnXMLConstants.ELEMENT_EVENT_END, BpmnXMLConstants.ELEMENT_TASK_USER))
+            .orderByHistoricActivityInstanceStartTime().desc()
+            .orderByHistoricActivityInstanceEndTime().desc()
+            .list();
+
+        List<Comment> commentList = taskService.getProcessInstanceComments(procInsId);
+
+        List<WfProcNodeVo> elementVoList = new ArrayList<>();
+        for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
+            WfProcNodeVo elementVo = new WfProcNodeVo();
+            elementVo.setProcDefId(activityInstance.getProcessDefinitionId());
+            elementVo.setActivityId(activityInstance.getActivityId());
+            elementVo.setActivityName(activityInstance.getActivityName());
+            elementVo.setActivityType(activityInstance.getActivityType());
+            elementVo.setCreateTime(activityInstance.getStartTime());
+            elementVo.setEndTime(activityInstance.getEndTime());
+            if (ObjectUtil.isNotNull(activityInstance.getDurationInMillis())) {
+                elementVo.setDuration(DateUtil.formatBetween(activityInstance.getDurationInMillis(), BetweenFormatter.Level.SECOND));
+            }
+
+            if (BpmnXMLConstants.ELEMENT_EVENT_START.equals(activityInstance.getActivityType())) {
+                if (ObjectUtil.isNotNull(historicProcIns)) {
+                    Long userId = Long.parseLong(historicProcIns.getStartUserId());
+                    String nickName = null;
+                    SysUserVo user = userService.selectUserById(userId);
+                    if(!ObjectUtil.isNull(user)) {
+                        nickName=user.getNickName();
+                    }
+                    if (nickName != null) {
+                        elementVo.setAssigneeId(userId);
+                        elementVo.setAssigneeName(nickName);
+                    }
+                }
+            } else if (BpmnXMLConstants.ELEMENT_TASK_USER.equals(activityInstance.getActivityType())) {
+                if (StringUtils.isNotBlank(activityInstance.getAssignee())) {
+                    Long userId = Long.parseLong(activityInstance.getAssignee());
+                    String nickName = null;
+                    SysUserVo user = userService.selectUserById(userId);
+                    if(!ObjectUtil.isNull(user)) {
+                        nickName=user.getNickName();
+                    }
+                    elementVo.setAssigneeId(userId);
+                    elementVo.setAssigneeName(nickName);
+                }
+                // 灞曠ず瀹℃壒浜哄憳
+                List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(activityInstance.getTaskId());
+                StringBuilder stringBuilder = new StringBuilder();
+                for (HistoricIdentityLink identityLink : linksForTask) {
+                    if ("candidate".equals(identityLink.getType())) {
+                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
+                            Long userId = Long.parseLong(identityLink.getUserId());
+                            String nickName = null;
+                            SysUserVo user = userService.selectUserById(userId);
+                            if(!ObjectUtil.isNull(user)) {
+                                nickName=user.getNickName();
+                            }
+                            stringBuilder.append(nickName).append(",");
+                        }
+                        if (StringUtils.isNotBlank(identityLink.getGroupId())) {
+                            if (identityLink.getGroupId().startsWith(TaskConstants.ROLE_GROUP_PREFIX)) {
+                                Long roleId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.ROLE_GROUP_PREFIX));
+                                //finish do 鏌ヨ瑙掕壊鍚嶇О
+                                SysRoleVo role=roleService.selectRoleById(roleId);
+                                if (!ObjectUtil.isNull(role)) {
+                                    stringBuilder.append(role.getRoleName()).append(",");
+                                }
+                            } else if (identityLink.getGroupId().startsWith(TaskConstants.DEPT_GROUP_PREFIX)) {
+                                Long deptId = Long.parseLong(StringUtils.stripStart(identityLink.getGroupId(), TaskConstants.DEPT_GROUP_PREFIX));
+                                //finish do 鏌ヨ閮ㄩ棬鍚嶇О
+                                SysDeptVo deptR=deptService.selectDeptById(deptId);
+                                if (!ObjectUtil.isNull(deptR) ) {
+                                    stringBuilder.append(deptR.getDeptName()).append(",");
+                                }
+                            }
+                        }
+                    }
+                }
+                if (StringUtils.isNotBlank(stringBuilder)) {
+                    elementVo.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1));
+                }
+                // 鑾峰彇鎰忚璇勮鍐呭
+                if (CollUtil.isNotEmpty(commentList)) {
+                    List<Comment> comments = new ArrayList<>();
+                    for (Comment comment : commentList) {
+
+                        if (comment.getTaskId().equals(activityInstance.getTaskId())) {
+                            comments.add(comment);
+                        }
+                    }
+                    elementVo.setCommentList(comments);
+                }
+            }
+            elementVoList.add(elementVo);
+        }
+        return elementVoList;
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鎵ц杩囩▼
+     *
+     * @param procInsId
+     * @return
+     */
+    private WfViewerVo getFlowViewer(BpmnModel bpmnModel, String procInsId) {
+        // 鏋勫缓鏌ヨ鏉′欢
+        HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(procInsId);
+        List<HistoricActivityInstance> allActivityInstanceList = query.list();
+        if (CollUtil.isEmpty(allActivityInstanceList)) {
+            return new WfViewerVo();
+        }
+        // 鏌ヨ鎵�鏈夊凡瀹屾垚鐨勫厓绱�
+        List<HistoricActivityInstance> finishedElementList = allActivityInstanceList.stream()
+            .filter(item -> ObjectUtil.isNotNull(item.getEndTime())).collect(Collectors.toList());
+        // 鎵�鏈夊凡瀹屾垚鐨勮繛绾�
+        Set<String> finishedSequenceFlowSet = new HashSet<>();
+        // 鎵�鏈夊凡瀹屾垚鐨勪换鍔¤妭鐐�
+        Set<String> finishedTaskSet = new HashSet<>();
+        finishedElementList.forEach(item -> {
+            if (BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW.equals(item.getActivityType())) {
+                finishedSequenceFlowSet.add(item.getActivityId());
+            } else {
+                finishedTaskSet.add(item.getActivityId());
+            }
+        });
+        // 鏌ヨ鎵�鏈夋湭缁撴潫鐨勮妭鐐�
+        Set<String> unfinishedTaskSet = allActivityInstanceList.stream()
+            .filter(item -> ObjectUtil.isNull(item.getEndTime()))
+            .map(HistoricActivityInstance::getActivityId)
+            .collect(Collectors.toSet());
+        // DFS 鏌ヨ鏈�氳繃鐨勫厓绱犻泦鍚�
+        Set<String> rejectedSet = FlowableUtils.dfsFindRejects(bpmnModel, unfinishedTaskSet, finishedSequenceFlowSet, finishedTaskSet);
+        return new WfViewerVo(finishedTaskSet, finishedSequenceFlowSet, unfinishedTaskSet, rejectedSet);
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java
new file mode 100644
index 0000000..bde22fd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/java/org/ruoyi/flowable/workflow/service/impl/WfTaskServiceImpl.java
@@ -0,0 +1,664 @@
+package org.ruoyi.flowable.workflow.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.flowable.common.constant.ProcessConstants;
+import org.ruoyi.flowable.common.constant.TaskConstants;
+import org.ruoyi.flowable.common.enums.FlowComment;
+import org.ruoyi.flowable.common.enums.ProcessStatus;
+import org.ruoyi.flowable.factory.FlowServiceFactory;
+import org.ruoyi.flowable.flow.CustomProcessDiagramGenerator;
+import org.ruoyi.flowable.flow.FlowableUtils;
+import org.ruoyi.flowable.utils.ModelUtils;
+import org.ruoyi.flowable.utils.TaskUtils;
+import org.ruoyi.flowable.workflow.domain.bo.WfTaskBo;
+import org.ruoyi.flowable.workflow.service.IWfCopyService;
+import org.ruoyi.flowable.workflow.service.IWfTaskService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.flowable.bpmn.constants.BpmnXMLConstants;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.api.FlowableObjectNotFoundException;
+import org.flowable.common.engine.impl.identity.Authentication;
+import org.flowable.engine.ProcessEngineConfiguration;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.runtime.Execution;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.image.ProcessDiagramGenerator;
+import org.flowable.task.api.DelegationState;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.ruoyi.system.domain.SysUser;
+import org.ruoyi.system.domain.vo.SysUserVo;
+import org.ruoyi.system.service.ISysUserService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.InputStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author KonBAI
+ * @createTime 2022/3/10 00:12
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class WfTaskServiceImpl extends FlowServiceFactory implements IWfTaskService {
+
+    private final ISysUserService sysUserService;
+
+    private final IWfCopyService copyService;
+
+    /**
+     * 瀹屾垚浠诲姟
+     *
+     * @param taskBo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void complete(WfTaskBo taskBo) {
+        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
+        if (Objects.isNull(task)) {
+            throw new ServiceException("浠诲姟涓嶅瓨鍦�");
+        }
+        // 鑾峰彇 bpmn 妯″瀷
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        if (DelegationState.PENDING.equals(task.getDelegationState())) {
+            taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.DELEGATE.getType(), taskBo.getComment());
+            taskService.resolveTask(taskBo.getTaskId());
+        } else {
+            taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.NORMAL.getType(), taskBo.getComment());
+            taskService.setAssignee(taskBo.getTaskId(), TaskUtils.getUserId());
+            if (ObjectUtil.isNotEmpty(taskBo.getVariables())) {
+                // 鑾峰彇妯″瀷淇℃伅
+                String localScopeValue = ModelUtils.getUserTaskAttributeValue(bpmnModel, task.getTaskDefinitionKey(), ProcessConstants.PROCESS_FORM_LOCAL_SCOPE);
+                boolean localScope = Convert.toBool(localScopeValue, false);
+                taskService.complete(taskBo.getTaskId(), taskBo.getVariables(), localScope);
+            } else {
+                taskService.complete(taskBo.getTaskId());
+            }
+        }
+        // 璁剧疆浠诲姟鑺傜偣鍚嶇О
+        taskBo.setTaskName(task.getName());
+        // 澶勭悊涓嬩竴绾у鎵逛汉
+        if (StringUtils.isNotBlank(taskBo.getNextUserIds())) {
+            this.assignNextUsers(bpmnModel, taskBo.getProcInsId(), taskBo.getNextUserIds());
+        }
+        // 澶勭悊鎶勯�佺敤鎴�
+        if (!copyService.makeCopy(taskBo)) {
+            throw new RuntimeException("鎶勯�佷换鍔″け璐�");
+        }
+    }
+
+    /**
+     * 鎷掔粷浠诲姟
+     *
+     * @param taskBo
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void taskReject(WfTaskBo taskBo) {
+        // 褰撳墠浠诲姟 task
+        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
+        if (ObjectUtil.isNull(task)) {
+            throw new RuntimeException("鑾峰彇浠诲姟淇℃伅寮傚父锛�");
+        }
+        if (task.isSuspended()) {
+            throw new RuntimeException("浠诲姟澶勪簬鎸傝捣鐘舵��");
+        }
+        // 鑾峰彇娴佺▼瀹炰緥
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+            .processInstanceId(taskBo.getProcInsId())
+            .singleResult();
+        if (processInstance == null) {
+            throw new RuntimeException("娴佺▼瀹炰緥涓嶅瓨鍦紝璇风‘璁わ紒");
+        }
+        // 鑾峰彇娴佺▼瀹氫箟淇℃伅
+        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
+            .processDefinitionId(task.getProcessDefinitionId())
+            .singleResult();
+
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        // 娣诲姞瀹℃壒鎰忚
+        taskService.addComment(taskBo.getTaskId(), taskBo.getProcInsId(), FlowComment.REJECT.getType(), taskBo.getComment());
+        // 璁剧疆娴佺▼鐘舵�佷负宸茬粓缁�
+        runtimeService.setVariable(processInstance.getId(), ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.TERMINATED.getStatus());
+        // 鑾峰彇鎵�鏈夎妭鐐逛俊鎭�
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
+        EndEvent endEvent = ModelUtils.getEndEvent(bpmnModel);
+        // 缁堟娴佺▼
+        List<Execution> executions = runtimeService.createExecutionQuery().parentId(task.getProcessInstanceId()).list();
+        List<String> executionIds = executions.stream().map(Execution::getId).collect(Collectors.toList());
+        runtimeService.createChangeActivityStateBuilder()
+            .processInstanceId(task.getProcessInstanceId())
+            .moveExecutionsToSingleActivityId(executionIds, endEvent.getId())
+            .changeState();
+        // 澶勭悊鎶勯�佺敤鎴�
+        if (!copyService.makeCopy(taskBo)) {
+            throw new RuntimeException("鎶勯�佷换鍔″け璐�");
+        }
+    }
+
+    /**
+     * 閫�鍥炰换鍔�
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void taskReturn(WfTaskBo bo) {
+        // 褰撳墠浠诲姟 task
+        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
+        if (ObjectUtil.isNull(task)) {
+            throw new RuntimeException("鑾峰彇浠诲姟淇℃伅寮傚父锛�");
+        }
+        if (task.isSuspended()) {
+            throw new RuntimeException("浠诲姟澶勪簬鎸傝捣鐘舵��");
+        }
+        // 鑾峰彇娴佺▼瀹氫箟淇℃伅
+        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
+        // 鑾峰彇娴佺▼妯″瀷淇℃伅
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
+        // 鑾峰彇褰撳墠浠诲姟鑺傜偣鍏冪礌
+        FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        // 鑾峰彇璺宠浆鐨勮妭鐐瑰厓绱�
+        FlowElement target = ModelUtils.getFlowElementById(bpmnModel, bo.getTargetKey());
+        // 浠庡綋鍓嶈妭鐐瑰悜鍓嶆壂鎻忥紝鍒ゆ柇褰撳墠鑺傜偣涓庣洰鏍囪妭鐐规槸鍚﹀睘浜庝覆琛岋紝鑻ョ洰鏍囪妭鐐规槸鍦ㄥ苟琛岀綉鍏充笂鎴栭潪鍚屼竴璺嚎涓婏紝涓嶅彲璺宠浆
+        boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
+        if (!isSequential) {
+            throw new RuntimeException("褰撳墠鑺傜偣鐩稿浜庣洰鏍囪妭鐐癸紝涓嶅睘浜庝覆琛屽叧绯伙紝鏃犳硶鍥為��");
+        }
+
+        // 鑾峰彇鎵�鏈夋甯歌繘琛岀殑浠诲姟鑺傜偣 Key锛岃繖浜涗换鍔′笉鑳界洿鎺ヤ娇鐢紝闇�瑕佹壘鍑哄叾涓渶瑕佹挙鍥炵殑浠诲姟
+        List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
+        List<String> runTaskKeyList = new ArrayList<>();
+        runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
+        // 闇�閫�鍥炰换鍔″垪琛�
+        List<String> currentIds = new ArrayList<>();
+        // 閫氳繃鐖剁骇缃戝叧鐨勫嚭鍙h繛绾匡紝缁撳悎 runTaskList 姣斿锛岃幏鍙栭渶瑕佹挙鍥炵殑浠诲姟
+        List<UserTask> currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, null);
+        currentUserTaskList.forEach(item -> currentIds.add(item.getId()));
+
+        // 寰幆鑾峰彇閭d簺闇�瑕佽鎾ゅ洖鐨勮妭鐐圭殑ID锛岀敤鏉ヨ缃┏鍥炲師鍥�
+        List<String> currentTaskIds = new ArrayList<>();
+        currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
+            if (currentId.equals(runTask.getTaskDefinitionKey())) {
+                currentTaskIds.add(runTask.getId());
+            }
+        }));
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        // 璁剧疆鍥為��鎰忚
+        for (String currentTaskId : currentTaskIds) {
+            taskService.addComment(currentTaskId, task.getProcessInstanceId(), FlowComment.REBACK.getType(), bo.getComment());
+        }
+
+        try {
+            // 1 瀵� 1 鎴� 澶� 瀵� 1 鎯呭喌锛宑urrentIds 褰撳墠瑕佽烦杞殑鑺傜偣鍒楄〃(1鎴栧)锛宼argetKey 璺宠浆鍒扮殑鑺傜偣(1)
+            runtimeService.createChangeActivityStateBuilder()
+                .processInstanceId(task.getProcessInstanceId())
+                .moveActivityIdsToSingleActivityId(currentIds, bo.getTargetKey()).changeState();
+        } catch (FlowableObjectNotFoundException e) {
+            throw new RuntimeException("鏈壘鍒版祦绋嬪疄渚嬶紝娴佺▼鍙兘宸插彂鐢熷彉鍖�");
+        } catch (FlowableException e) {
+            throw new RuntimeException("鏃犳硶鍙栨秷鎴栧紑濮嬫椿鍔�");
+        }
+        // 璁剧疆浠诲姟鑺傜偣鍚嶇О
+        bo.setTaskName(task.getName());
+        // 澶勭悊鎶勯�佺敤鎴�
+        if (!copyService.makeCopy(bo)) {
+            throw new RuntimeException("鎶勯�佷换鍔″け璐�");
+        }
+    }
+
+
+    /**
+     * 鑾峰彇鎵�鏈夊彲鍥為��鐨勮妭鐐�
+     *
+     * @param bo
+     * @return
+     */
+    @Override
+    public List<FlowElement> findReturnTaskList(WfTaskBo bo) {
+        // 褰撳墠浠诲姟 task
+        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
+        // 鑾峰彇娴佺▼瀹氫箟淇℃伅
+        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
+        // 鑾峰彇娴佺▼妯″瀷淇℃伅
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
+        // 鏌ヨ鍘嗗彶鑺傜偣瀹炰緥
+        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(task.getProcessInstanceId())
+            .activityType(BpmnXMLConstants.ELEMENT_TASK_USER)
+            .finished()
+            .orderByHistoricActivityInstanceEndTime().asc()
+            .list();
+        List<String> activityIdList = activityInstanceList.stream()
+            .map(HistoricActivityInstance::getActivityId)
+            .filter(activityId -> !StringUtils.equals(activityId, task.getTaskDefinitionKey()))
+            .distinct()
+            .collect(Collectors.toList());
+        // 鑾峰彇褰撳墠浠诲姟鑺傜偣鍏冪礌
+        FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        List<FlowElement> elementList = new ArrayList<>();
+        for (String activityId : activityIdList) {
+            FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId);
+            boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
+            if (isSequential) {
+                elementList.add(target);
+            }
+        }
+        return elementList;
+    }
+
+    /**
+     * 鍒犻櫎浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    public void deleteTask(WfTaskBo bo) {
+        // todo 寰呯‘璁ゅ垹闄や换鍔℃槸鐗╃悊鍒犻櫎浠诲姟 杩樻槸閫昏緫鍒犻櫎锛岃杩欎釜浠诲姟鐩存帴閫氳繃锛�
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        taskService.deleteTask(bo.getTaskId(), bo.getComment());
+    }
+
+    /**
+     * 璁ら/绛炬敹浠诲姟
+     *
+     * @param taskBo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void claim(WfTaskBo taskBo) {
+        Task task = taskService.createTaskQuery().taskId(taskBo.getTaskId()).singleResult();
+        if (Objects.isNull(task)) {
+            throw new ServiceException("浠诲姟涓嶅瓨鍦�");
+        }
+        taskService.claim(taskBo.getTaskId(), TaskUtils.getUserId());
+    }
+
+    /**
+     * 鍙栨秷璁ら/绛炬敹浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void unClaim(WfTaskBo bo) {
+        taskService.unclaim(bo.getTaskId());
+    }
+
+    /**
+     * 濮旀淳浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delegateTask(WfTaskBo bo) {
+        // 褰撳墠浠诲姟 task
+        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException("鑾峰彇浠诲姟澶辫触锛�");
+        }
+        // 鑾峰彇褰撳墠鐨勭敤鎴�
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        String currentNickname = null;
+        if (ObjectUtil.isNotNull(loginUser)){
+            currentNickname=loginUser.getNickName();
+        }
+        StringBuilder commentBuilder = new StringBuilder(currentNickname)
+            .append("->");
+
+        String nickName = null;
+        SysUserVo user = sysUserService.selectUserById(Long.parseLong(bo.getUserId()));
+        if(!ObjectUtil.isNull(user)) {
+            nickName=user.getNickName();
+        }
+        if (StringUtils.isNotBlank(nickName)) {
+            commentBuilder.append(nickName);
+        } else {
+            commentBuilder.append(bo.getUserId());
+        }
+        if (StringUtils.isNotBlank(bo.getComment())) {
+            commentBuilder.append(": ").append(bo.getComment());
+        }
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        // 娣诲姞瀹℃壒鎰忚
+        taskService.addComment(bo.getTaskId(), task.getProcessInstanceId(), FlowComment.DELEGATE.getType(), commentBuilder.toString());
+        // 璁剧疆鍔炵悊浜轰负褰撳墠鐧诲綍浜�
+        taskService.setOwner(bo.getTaskId(), TaskUtils.getUserId());
+        // 鎵ц濮旀淳
+        taskService.delegateTask(bo.getTaskId(), bo.getUserId());
+        // 璁剧疆浠诲姟鑺傜偣鍚嶇О
+        bo.setTaskName(task.getName());
+        // 澶勭悊鎶勯�佺敤鎴�
+        if (!copyService.makeCopy(bo)) {
+            throw new RuntimeException("鎶勯�佷换鍔″け璐�");
+        }
+    }
+
+
+    /**
+     * 杞姙浠诲姟
+     *
+     * @param bo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void transferTask(WfTaskBo bo) {
+        // 褰撳墠浠诲姟 task
+        Task task = taskService.createTaskQuery().taskId(bo.getTaskId()).singleResult();
+        if (ObjectUtil.isEmpty(task)) {
+            throw new ServiceException("鑾峰彇浠诲姟澶辫触锛�");
+        }
+        // 鑾峰彇褰撳墠鐨勭敤鎴�
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        String currentNickname = null;
+        if (ObjectUtil.isNotNull(loginUser)){
+            currentNickname=loginUser.getNickName();
+        }
+        StringBuilder commentBuilder = new StringBuilder(currentNickname)
+                .append("->");
+
+        String nickName = null;
+        SysUserVo user = sysUserService.selectUserById(Long.parseLong(bo.getUserId()));
+        if(!ObjectUtil.isNull(user)) {
+            nickName=user.getNickName();
+        }
+        if (StringUtils.isNotBlank(nickName)) {
+            commentBuilder.append(nickName);
+        } else {
+            commentBuilder.append(bo.getUserId());
+        }
+        if (StringUtils.isNotBlank(bo.getComment())) {
+            commentBuilder.append(": ").append(bo.getComment());
+        }
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        // 娣诲姞瀹℃壒鎰忚
+        taskService.addComment(bo.getTaskId(), task.getProcessInstanceId(), FlowComment.TRANSFER.getType(), commentBuilder.toString());
+        // 璁剧疆鎷ユ湁鑰呬负褰撳墠鐧诲綍浜�
+        taskService.setOwner(bo.getTaskId(), TaskUtils.getUserId());
+        // 杞姙浠诲姟
+        taskService.setAssignee(bo.getTaskId(), bo.getUserId());
+        // 璁剧疆浠诲姟鑺傜偣鍚嶇О
+        bo.setTaskName(task.getName());
+        // 澶勭悊鎶勯�佺敤鎴�
+        if (!copyService.makeCopy(bo)) {
+            throw new RuntimeException("鎶勯�佷换鍔″け璐�");
+        }
+    }
+
+    /**
+     * 鍙栨秷鐢宠
+     *
+     * @param bo
+     * @return
+     */
+    @Override
+    public void stopProcess(WfTaskBo bo) {
+        List<Task> taskList = taskService.createTaskQuery().processInstanceId(bo.getProcInsId()).list();
+        if (CollectionUtils.isEmpty(taskList)) {
+            throw new RuntimeException("娴佺▼鏈惎鍔ㄦ垨宸叉墽琛屽畬鎴愶紝鍙栨秷鐢宠澶辫触");
+        }
+
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+            .processInstanceId(bo.getProcInsId()).singleResult();
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
+        if (Objects.nonNull(bpmnModel)) {
+            Process process = bpmnModel.getMainProcess();
+            List<EndEvent> endNodes = process.findFlowElementsOfType(EndEvent.class, false);
+            if (CollectionUtils.isNotEmpty(endNodes)) {
+                Authentication.setAuthenticatedUserId(TaskUtils.getUserId());
+                runtimeService.setVariable(processInstance.getId(), ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.CANCELED.getStatus());
+                for (Task task : taskList) {
+                    taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.STOP.getType(), "鍙栨秷娴佺▼");
+                }
+                // 鑾峰彇褰撳墠娴佺▼鏈�鍚庝竴涓妭鐐�
+                String endId = endNodes.get(0).getId();
+                List<Execution> executions = runtimeService.createExecutionQuery()
+                    .parentId(processInstance.getProcessInstanceId()).list();
+                List<String> executionIds = new ArrayList<>();
+                executions.forEach(execution -> executionIds.add(execution.getId()));
+                // 鍙樻洿娴佺▼涓哄凡缁撴潫鐘舵��
+                runtimeService.createChangeActivityStateBuilder()
+                    .moveExecutionsToSingleActivityId(executionIds, endId).changeState();
+            }
+        }
+    }
+
+    /**
+     * 鎾ゅ洖娴佺▼
+     *
+     * @param taskBo 璇锋眰瀹炰綋鍙傛暟
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void revokeProcess(WfTaskBo taskBo) {
+        String procInsId = taskBo.getProcInsId();
+        String taskId = taskBo.getTaskId();
+        // 鏍¢獙娴佺▼鏄惁缁撴潫
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+            .processInstanceId(procInsId)
+            .active()
+            .singleResult();
+        if(ObjectUtil.isNull(processInstance)) {
+            throw new RuntimeException("娴佺▼宸茬粨鏉熸垨宸叉寕璧凤紝鏃犳硶鎵ц鎾ゅ洖鎿嶄綔");
+        }
+        // 鑾峰彇寰呮挙鍥炰换鍔″疄渚�
+        HistoricTaskInstance currTaskIns = historyService.createHistoricTaskInstanceQuery()
+            .taskId(taskId)
+            .taskAssignee(TaskUtils.getUserId())
+            .singleResult();
+        if (ObjectUtil.isNull(currTaskIns)) {
+            throw new RuntimeException("褰撳墠浠诲姟涓嶅瓨鍦紝鏃犳硶鎵ц鎾ゅ洖鎿嶄綔");
+        }
+        // 鑾峰彇 bpmn 妯″瀷
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(currTaskIns.getProcessDefinitionId());
+        UserTask currUserTask = ModelUtils.getUserTaskByKey(bpmnModel, currTaskIns.getTaskDefinitionKey());
+        // 鏌ユ壘涓嬩竴绾х敤鎴蜂换鍔″垪琛�
+        List<UserTask> nextUserTaskList = ModelUtils.findNextUserTasks(currUserTask);
+        List<String> nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).collect(Collectors.toList());
+
+        // 鑾峰彇褰撳墠鑺傜偣涔嬪悗宸插畬鎴愮殑娴佺▼鍘嗗彶鑺傜偣
+        List<HistoricTaskInstance> finishedTaskInsList = historyService.createHistoricTaskInstanceQuery()
+            .processInstanceId(procInsId)
+            .taskCreatedAfter(currTaskIns.getEndTime())
+            .finished()
+            .list();
+        for (HistoricTaskInstance finishedTaskInstance : finishedTaskInsList) {
+            // 妫�鏌ュ凡瀹屾垚娴佺▼鍘嗗彶鑺傜偣鏄惁瀛樺湪涓嬩竴绾т腑
+            if (CollUtil.contains(nextUserTaskKeys, finishedTaskInstance.getTaskDefinitionKey())) {
+                throw new RuntimeException("涓嬩竴娴佺▼宸插鐞嗭紝鏃犳硶鎵ц鎾ゅ洖鎿嶄綔");
+            }
+        }
+        // 鑾峰彇鎵�鏈夋縺娲荤殑浠诲姟鑺傜偣锛屾壘鍒伴渶瑕佹挙鍥炵殑浠诲姟
+        List<Task> activateTaskList = taskService.createTaskQuery().processInstanceId(procInsId).list();
+        List<String> revokeExecutionIds = new ArrayList<>();
+        identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+        for (Task task : activateTaskList) {
+            // 妫�鏌ユ縺娲荤殑浠诲姟鑺傜偣鏄惁瀛樺湪涓嬩竴绾т腑锛屽鏋滃瓨鍦紝鍒欏姞鍏ュ埌闇�瑕佹挙鍥炵殑鑺傜偣
+            if (CollUtil.contains(nextUserTaskKeys, task.getTaskDefinitionKey())) {
+                // 娣诲姞鎾ゅ洖瀹℃壒淇℃伅
+                taskService.setAssignee(task.getId(), TaskUtils.getUserId());
+
+                // 鑾峰彇褰撳墠鐨勭敤鎴�
+                LoginUser loginUser = LoginHelper.getLoginUser();
+                String currentNickname = null;
+                if (ObjectUtil.isNotNull(loginUser)){
+                    currentNickname=loginUser.getNickName();
+                }
+                taskService.addComment(task.getId(), task.getProcessInstanceId(), FlowComment.REVOKE.getType(), currentNickname + "鎾ゅ洖娴佺▼瀹℃壒");
+                revokeExecutionIds.add(task.getExecutionId());
+            }
+        }
+        try {
+            runtimeService.createChangeActivityStateBuilder()
+                .processInstanceId(procInsId)
+                .moveExecutionsToSingleActivityId(revokeExecutionIds, currTaskIns.getTaskDefinitionKey()).changeState();
+        } catch (FlowableObjectNotFoundException e) {
+            throw new RuntimeException("鏈壘鍒版祦绋嬪疄渚嬶紝娴佺▼鍙兘宸插彂鐢熷彉鍖�");
+        } catch (FlowableException e) {
+            throw new RuntimeException("鎵ц鎾ゅ洖鎿嶄綔澶辫触");
+        }
+    }
+
+    /**
+     * 鑾峰彇娴佺▼杩囩▼鍥�
+     *
+     * @param processId
+     * @return
+     */
+    @Override
+    public InputStream diagram(String processId) {
+        String processDefinitionId;
+        // 鑾峰彇褰撳墠鐨勬祦绋嬪疄渚�
+        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
+        // 濡傛灉娴佺▼宸茬粡缁撴潫锛屽垯寰楀埌缁撴潫鑺傜偣
+        if (Objects.isNull(processInstance)) {
+            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processId).singleResult();
+
+            processDefinitionId = pi.getProcessDefinitionId();
+        } else {// 濡傛灉娴佺▼娌℃湁缁撴潫锛屽垯鍙栧綋鍓嶆椿鍔ㄨ妭鐐�
+            // 鏍规嵁娴佺▼瀹炰緥ID鑾峰緱褰撳墠澶勪簬娲诲姩鐘舵�佺殑ActivityId鍚堥泦
+            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
+            processDefinitionId = pi.getProcessDefinitionId();
+        }
+
+        // 鑾峰緱娲诲姩鐨勮妭鐐�
+        List<HistoricActivityInstance> highLightedFlowList = historyService.createHistoricActivityInstanceQuery()
+            .processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();
+
+        List<String> highLightedFlows = new ArrayList<>();
+        List<String> highLightedNodes = new ArrayList<>();
+        //楂樹寒绾�
+        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
+            if ("sequenceFlow".equals(tempActivity.getActivityType())) {
+                //楂樹寒绾�
+                highLightedFlows.add(tempActivity.getActivityId());
+            } else {
+                //楂樹寒鑺傜偣
+                highLightedNodes.add(tempActivity.getActivityId());
+            }
+        }
+
+        //鑾峰彇娴佺▼鍥�
+        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
+        ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
+        //鑾峰彇鑷畾涔夊浘鐗囩敓鎴愬櫒
+        ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator();
+        return diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodes, highLightedFlows, configuration.getActivityFontName(),
+            configuration.getLabelFontName(), configuration.getAnnotationFontName(), configuration.getClassLoader(), 1.0, true);
+
+    }
+
+    /**
+     * 鑾峰彇娴佺▼鍙橀噺
+     *
+     * @param taskId 浠诲姟ID
+     * @return 娴佺▼鍙橀噺
+     */
+    @Override
+    public Map<String, Object> getProcessVariables(String taskId) {
+        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
+            .includeProcessVariables()
+            .finished()
+            .taskId(taskId)
+            .singleResult();
+        if (Objects.nonNull(historicTaskInstance)) {
+            return historicTaskInstance.getProcessVariables();
+        }
+        return taskService.getVariables(taskId);
+    }
+
+    /**
+     * 鍚姩绗竴涓换鍔�
+     * @param processInstance 娴佺▼瀹炰緥
+     * @param variables 娴佺▼鍙傛暟
+     */
+    @Override
+    public void startFirstTask(ProcessInstance processInstance, Map<String, Object> variables) {
+        // 鑻ョ涓�涓敤鎴蜂换鍔′负鍙戣捣浜猴紝鍒欒嚜鍔ㄥ畬鎴愪换鍔�
+        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).list();
+        if (CollUtil.isNotEmpty(tasks)) {
+            String userIdStr = (String) variables.get(TaskConstants.PROCESS_INITIATOR);
+            identityService.setAuthenticatedUserId(TaskUtils.getUserId());
+            // 鑾峰彇褰撳墠鐨勭敤鎴�
+            LoginUser loginUser = LoginHelper.getLoginUser();
+            String currentNickname = null;
+            if (ObjectUtil.isNotNull(loginUser)){
+                currentNickname=loginUser.getNickName();
+            }
+            for (Task task : tasks) {
+                if (StrUtil.equals(task.getAssignee(), userIdStr)) {
+                    taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), currentNickname+ "鍙戣捣娴佺▼鐢宠");
+                    taskService.complete(task.getId(), variables);
+                }
+            }
+        }
+    }
+
+    /**
+     * 鎸囨淳涓嬩竴浠诲姟瀹℃壒浜�
+     * @param bpmnModel bpmn妯″瀷
+     * @param processInsId 娴佺▼瀹炰緥id
+     * @param userIds 鐢ㄦ埛ids
+     */
+    private void assignNextUsers(BpmnModel bpmnModel, String processInsId, String userIds) {
+        // 鑾峰彇鎵�鏈夎妭鐐逛俊鎭�
+        List<Task> list = taskService.createTaskQuery()
+            .processInstanceId(processInsId)
+            .list();
+        if (list.size() == 0) {
+            return;
+        }
+        Queue<String> assignIds = CollUtil.newLinkedList(userIds.split(","));
+        if (list.size() == assignIds.size()) {
+            for (Task task : list) {
+                taskService.setAssignee(task.getId(), assignIds.poll());
+            }
+            return;
+        }
+        // 浼樺厛澶勭悊闈炲瀹炰緥浠诲姟
+        Iterator<Task> iterator = list.iterator();
+        while (iterator.hasNext()) {
+            Task task = iterator.next();
+            if (!ModelUtils.isMultiInstance(bpmnModel, task.getTaskDefinitionKey())) {
+                if (!assignIds.isEmpty()) {
+                    taskService.setAssignee(task.getId(), assignIds.poll());
+                }
+                iterator.remove();
+            }
+        }
+        // 鑻ュ瓨鍦ㄥ瀹炰緥浠诲姟锛屽垯杩涜鍔ㄦ�佸姞鍑忕
+        if (CollUtil.isNotEmpty(list)) {
+            if (assignIds.isEmpty()) {
+                // 鍔ㄦ�佸噺绛�
+                for (Task task : list) {
+                    runtimeService.deleteMultiInstanceExecution(task.getExecutionId(), true);
+                }
+            } else {
+                // 鍔ㄦ�佸姞绛�
+                for (String assignId : assignIds) {
+                    Map<String, Object> assignVariables = Collections.singletonMap(BpmnXMLConstants.ATTRIBUTE_TASK_USER_ASSIGNEE, assignId);
+                    runtimeService.addMultiInstanceExecution(list.get(0).getTaskDefinitionKey(), list.get(0).getProcessInstanceId(), assignVariables);
+                }
+            }
+        }
+    }
+}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt b/ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt
new file mode 100644
index 0000000..e406032
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/resources/banner.txt
@@ -0,0 +1,12 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+                                 鈻堚枅         鈻堚枅鈻堚枅  鈻堚枅                               鈻堚枅       鈻堚枅
+                          鈻堚枅   鈻堚枅鈻戔枒         鈻戔枅鈻堚枒  鈻戔枅鈻�                              鈻戔枅鈻�      鈻戔枅鈻�
+ 鈻堚枅鈻堚枅鈻堚枅 鈻堚枅   鈻堚枅  鈻堚枅鈻堚枅鈻堚枅  鈻戔枒鈻堚枅 鈻堚枅  鈻堚枅       鈻堚枅鈻堚枅鈻堚枅 鈻戔枅鈻�  鈻堚枅鈻堚枅鈻堚枅  鈻堚枅鈻�     鈻堚枅  鈻堚枅鈻堚枅鈻堚枅  鈻戔枅鈻�      鈻戔枅鈻�  鈻堚枅鈻堚枅鈻�
+鈻戔枒鈻堚枅鈻戔枒鈻堚枒鈻堚枅  鈻戔枅鈻� 鈻堚枅鈻戔枒鈻戔枒鈻堚枅  鈻戔枒鈻堚枅鈻�  鈻戔枅鈻� 鈻堚枅鈻堚枅鈻堚枒鈻戔枒鈻堚枅鈻�  鈻戔枅鈻� 鈻堚枅鈻戔枒鈻戔枒鈻堚枅鈻戔枒鈻堚枅  鈻� 鈻戔枅鈻� 鈻戔枒鈻戔枒鈻戔枒鈻堚枅 鈻戔枅鈻堚枅鈻堚枅鈻�  鈻戔枅鈻� 鈻堚枅鈻戔枒鈻戔枅鈻�
+ 鈻戔枅鈻� 鈻� 鈻戔枅鈻�  鈻戔枅鈻堚枒鈻堚枅   鈻戔枅鈻�   鈻戔枅鈻�   鈻戔枅鈻堚枒鈻戔枒鈻戔枒   鈻戔枅鈻�   鈻戔枅鈻堚枒鈻堚枅   鈻戔枅鈻� 鈻戔枅鈻� 鈻堚枅鈻堚枒鈻堚枅  鈻堚枅鈻堚枅鈻堚枅鈻� 鈻戔枅鈻堚枒鈻戔枒鈻堚枅 鈻戔枅鈻堚枒鈻堚枅鈻堚枅鈻堚枅鈻�
+ 鈻戔枅鈻�   鈻戔枅鈻�  鈻戔枅鈻堚枒鈻堚枅   鈻戔枅鈻�   鈻堚枅    鈻戔枅鈻�        鈻戔枅鈻�   鈻戔枅鈻堚枒鈻堚枅   鈻戔枅鈻� 鈻戔枅鈻堚枅鈻堚枒鈻堚枅鈻堚枅 鈻堚枅鈻戔枒鈻戔枒鈻堚枅 鈻戔枅鈻�  鈻戔枅鈻� 鈻戔枅鈻堚枒鈻堚枅鈻戔枒鈻戔枒
+鈻戔枅鈻堚枅   鈻戔枒鈻堚枅鈻堚枅鈻堚枅鈻戔枒鈻堚枅鈻堚枅鈻堚枅   鈻堚枅     鈻戔枅鈻�        鈻戔枅鈻�   鈻堚枅鈻堚枒鈻戔枅鈻堚枅鈻堚枅鈻�  鈻堚枅鈻堚枒 鈻戔枒鈻戔枅鈻堚枒鈻戔枅鈻堚枅鈻堚枅鈻堚枅鈻堚枒鈻堚枅鈻堚枅鈻堚枅  鈻堚枅鈻堚枒鈻戔枅鈻堚枅鈻堚枅鈻�
+鈻戔枒鈻�     鈻戔枒鈻戔枒鈻戔枒  鈻戔枒鈻戔枒鈻戔枒   鈻戔枒      鈻戔枒         鈻戔枒   鈻戔枒鈻�  鈻戔枒鈻戔枒鈻戔枒  鈻戔枒鈻�    鈻戔枒鈻�  鈻戔枒鈻戔枒鈻戔枒鈻戔枒 鈻戔枒鈻戔枒鈻�   鈻戔枒鈻�  鈻戔枒鈻戔枒鈻戔枒
+
+
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml b/ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..0c36a5e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/resources/bootstrap.yml
@@ -0,0 +1,27 @@
+# Tomcat
+server:
+  port: 9308
+
+# Spring
+spring:
+  main:
+    lazy-initialization: true #榛樿false 鍏抽棴
+  application:
+    # 搴旂敤鍚嶇О
+    name: ruoyi-flowable
+  profiles:
+    # 鐜閰嶇疆
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+        # 鏈嶅姟娉ㄥ唽鍦板潃
+        server-addr: 127.0.0.1:8848
+      config:
+        # 閰嶇疆涓績鍦板潃
+        server-addr: 127.0.0.1:8848
+        # 閰嶇疆鏂囦欢鏍煎紡
+        file-extension: yml
+        # 鍏变韩閰嶇疆
+        shared-configs:
+          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml b/ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml
new file mode 100644
index 0000000..7ab916d
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/resources/logback.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 鏃ュ織瀛樻斁璺緞 -->
+	<property name="log.path" value="logs/ruoyi-flowable" />
+   <!-- 鏃ュ織杈撳嚭鏍煎紡 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+    <!-- 鎺у埗鍙拌緭鍑� -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+    <!-- 绯荤粺鏃ュ織杈撳嚭 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/info.log</file>
+        <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+			<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 杩囨护鐨勭骇鍒� -->
+            <level>INFO</level>
+            <!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/error.log</file>
+        <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 杩囨护鐨勭骇鍒� -->
+            <level>ERROR</level>
+			<!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- Console Appender -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- MyBatis-Plus Logger Configuration -->
+    <logger name="com.baomidou.mybatisplus" level="DEBUG"/>
+
+    <!-- 绯荤粺妯″潡鏃ュ織绾у埆鎺у埗  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring鏃ュ織绾у埆鎺у埗  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--绯荤粺鎿嶄綔鏃ュ織-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml b/ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml
new file mode 100644
index 0000000..6da982e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-flowable/src/main/resources/mapper/flowable/WfCategoryMapper.xml
@@ -0,0 +1,19 @@
+<?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.ruoyi.flowable.workflow.mapper.WfCategoryMapper">
+
+    <resultMap type="org.ruoyi.flowable.workflow.domain.WfCategory" id="FlowCategoryResult">
+        <result property="categoryId" column="category_id"/>
+        <result property="categoryName" column="category_name"/>
+        <result property="code" column="code"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+        <result property="delFlag" column="del_flag"/>
+    </resultMap>
+
+</mapper>

--
Gitblit v1.9.3