Преглед на файлове

!652 发布 5.3.1-BETA 公测版本
Merge pull request !652 from 疯狂的狮子Li/dev

疯狂的狮子Li преди 1 месец
родител
ревизия
7334d91d6b
променени са 81 файла, в които са добавени 1420 реда и са изтрити 257 реда
  1. 1 1
      .run/ruoyi-monitor-admin.run.xml
  2. 1 1
      .run/ruoyi-server.run.xml
  3. 1 1
      .run/ruoyi-snailjob-server.run.xml
  4. 1 1
      README.md
  5. 12 13
      pom.xml
  6. 24 6
      ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
  7. 4 8
      ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java
  8. 2 2
      ruoyi-admin/src/main/resources/application-dev.yml
  9. 2 2
      ruoyi-admin/src/main/resources/application-prod.yml
  10. 22 24
      ruoyi-admin/src/main/resources/application.yml
  11. 5 5
      ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
  12. 1 1
      ruoyi-common/ruoyi-common-bom/pom.xml
  13. 0 33
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/RuoYiConfig.java
  14. 4 3
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
  15. 5 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  16. 8 3
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
  17. 5 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java
  18. 0 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java
  19. 41 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java
  20. 41 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java
  21. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java
  22. 2 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessCreateTaskEvent.java
  23. 20 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
  24. 0 1
      ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  25. 0 3
      ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
  26. 3 8
      ruoyi-common/ruoyi-common-oss/pom.xml
  27. 5 9
      ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
  28. 1 0
      ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java
  29. 11 5
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
  30. 9 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java
  31. 5 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java
  32. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java
  33. 5 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java
  34. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java
  35. 5 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
  36. 10 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java
  37. 5 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java
  38. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java
  39. 12 3
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
  40. 4 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
  41. 38 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
  42. 1 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
  43. 4 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java
  44. 21 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
  45. 1 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
  46. 0 3
      ruoyi-modules/ruoyi-workflow/README.md
  47. 60 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
  48. 32 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java
  49. 11 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
  50. 9 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java
  51. 5 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
  52. 38 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java
  53. 34 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java
  54. 7 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java
  55. 41 2
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
  56. 11 11
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java
  57. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
  58. 76 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
  59. 18 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
  60. 71 30
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
  61. 8 2
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
  62. 6 4
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
  63. 190 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
  64. 143 20
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
  65. 23 10
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
  66. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
  67. 4 4
      script/docker/docker-compose.yml
  68. 2 0
      script/docker/nginx/conf/nginx.conf
  69. 212 0
      script/leave/leave6.json
  70. 4 2
      script/sql/oracle/oracle_ry_vue_5.X.sql
  71. 2 2
      script/sql/oracle/oracle_ry_workflow.sql
  72. 4 2
      script/sql/postgres/postgres_ry_vue_5.X.sql
  73. 2 2
      script/sql/postgres/postgres_ry_workflow.sql
  74. 3 2
      script/sql/ry_vue_5.X.sql
  75. 2 2
      script/sql/ry_workflow.sql
  76. 9 2
      script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
  77. 8 8
      script/sql/sqlserver/sqlserver_ry_workflow.sql
  78. 6 0
      script/sql/update/oracle/update_5.3.0-5.3.1.sql
  79. 6 0
      script/sql/update/postgres/update_5.3.0-5.3.1.sql
  80. 18 0
      script/sql/update/sqlserver/update_5.3.0-5.3.1.sql
  81. 6 0
      script/sql/update/update_5.3.0-5.3.1.sql

+ 1 - 1
.run/ruoyi-monitor-admin.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.1-BETA" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-server.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-server:5.3.1-BETA" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
       </settings>

+ 1 - 1
.run/ruoyi-snailjob-server.run.xml

@@ -2,7 +2,7 @@
   <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
+        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.1-BETA" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
       </settings>

+ 1 - 1
README.md

@@ -10,7 +10,7 @@
 [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
 [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 <br>
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0--BETA-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
 [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
 [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()

+ 12 - 13
pom.xml

@@ -13,21 +13,21 @@
     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 
     <properties>
-        <revision>5.3.0</revision>
-        <spring-boot.version>3.4.2</spring-boot.version>
+        <revision>5.3.1-BETA</revision>
+        <spring-boot.version>3.4.3</spring-boot.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>17</java.version>
         <mybatis.version>3.5.16</mybatis.version>
-        <springdoc.version>2.8.4</springdoc.version>
+        <springdoc.version>2.8.5</springdoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
         <easyexcel.version>4.0.3</easyexcel.version>
         <velocity.version>2.3</velocity.version>
         <satoken.version>1.40.0</satoken.version>
-        <mybatis-plus.version>3.5.10</mybatis-plus.version>
+        <mybatis-plus.version>3.5.10.1</mybatis-plus.version>
         <p6spy.version>3.9.1</p6spy.version>
         <hutool.version>5.8.35</hutool.version>
-        <spring-boot-admin.version>3.4.1</spring-boot-admin.version>
+        <spring-boot-admin.version>3.4.2</spring-boot-admin.version>
         <redisson.version>3.44.0</redisson.version>
         <lock4j.version>2.2.7</lock4j.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
@@ -42,7 +42,6 @@
 
         <!-- OSS 配置 -->
         <aws.sdk.version>2.28.22</aws.sdk.version>
-        <aws.crt.version>0.31.3</aws.crt.version>
         <!-- SMS 配置 -->
         <sms4j.version>3.3.3</sms4j.version>
         <!-- 限制框架中的fastjson版本 -->
@@ -50,7 +49,7 @@
         <!-- 面向运行时的D-ORM依赖 -->
         <anyline.version>8.7.2-20250101</anyline.version>
         <!--工作流配置-->
-        <warm-flow.version>1.6.6</warm-flow.version>
+        <warm-flow.version>1.6.7</warm-flow.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@@ -245,18 +244,18 @@
                 <artifactId>s3</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
-            <!-- 使用AWS基于 CRT 的 S3 客户端 -->
-            <dependency>
-                <groupId>software.amazon.awssdk.crt</groupId>
-                <artifactId>aws-crt</artifactId>
-                <version>${aws.crt.version}</version>
-            </dependency>
             <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>s3-transfer-manager</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
+            <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>netty-nio-client</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
             <!--短信sms4j-->
             <dependency>
                 <groupId>org.dromara.sms4j</groupId>

+ 24 - 6
ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java

@@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.GlobalConstants;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.reflect.ReflectUtils;
@@ -79,12 +80,21 @@ public class CaptchaController {
      *
      * @param email 邮箱
      */
-    @RateLimiter(key = "#email", time = 60, count = 1)
     @GetMapping("/resource/email/code")
     public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
         if (!mailProperties.getEnabled()) {
             return R.fail("当前系统没有开启邮箱功能!");
         }
+        SpringUtils.getAopProxy(this).emailCodeImpl(email);
+        return R.ok();
+    }
+
+    /**
+     * 邮箱验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(key = "#email", time = 60, count = 1)
+    public void emailCodeImpl(String email) {
         String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
         String code = RandomUtil.randomNumbers(4);
         RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
@@ -92,23 +102,30 @@ public class CaptchaController {
             MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
         } catch (Exception e) {
             log.error("验证码短信发送异常 => {}", e.getMessage());
-            return R.fail(e.getMessage());
+            throw new ServiceException(e.getMessage());
         }
-        return R.ok();
     }
 
     /**
      * 生成验证码
      */
-    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
     @GetMapping("/auth/code")
     public R<CaptchaVo> getCode() {
-        CaptchaVo captchaVo = new CaptchaVo();
         boolean captchaEnabled = captchaProperties.getEnable();
         if (!captchaEnabled) {
+            CaptchaVo captchaVo = new CaptchaVo();
             captchaVo.setCaptchaEnabled(false);
             return R.ok(captchaVo);
         }
+        return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
+    }
+
+    /**
+     * 生成验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public CaptchaVo getCodeImpl() {
         // 保存验证码信息
         String uuid = IdUtil.simpleUUID();
         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
@@ -128,9 +145,10 @@ public class CaptchaController {
             code = exp.getValue(String.class);
         }
         RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        CaptchaVo captchaVo = new CaptchaVo();
         captchaVo.setUuid(uuid);
         captchaVo.setImg(captcha.getImageBase64());
-        return R.ok(captchaVo);
+        return captchaVo;
     }
 
 }

+ 4 - 8
ruoyi-admin/src/main/java/org/dromara/web/controller/IndexController.java

@@ -1,9 +1,9 @@
 package org.dromara.web.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
-import org.dromara.common.core.config.RuoYiConfig;
-import org.dromara.common.core.utils.StringUtils;
 import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -17,16 +17,12 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 public class IndexController {
 
-    /**
-     * 系统基础配置
-     */
-    private final RuoYiConfig ruoyiConfig;
-
     /**
      * 访问首页,提示语
      */
     @GetMapping("/")
     public String index() {
-        return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
+        return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
     }
+
 }

+ 2 - 2
ruoyi-admin/src/main/resources/application-dev.yml

@@ -120,8 +120,8 @@ redisson:
   nettyThreads: 8
   # 单节点配置
   singleServerConfig:
-    # 客户端名称
-    clientName: ${ruoyi.name}
+    # 客户端名称 不能用中文
+    clientName: RuoYi-Vue-Plus
     # 最小空闲连接数
     connectionMinimumIdleSize: 8
     # 连接池大小

+ 2 - 2
ruoyi-admin/src/main/resources/application-prod.yml

@@ -123,8 +123,8 @@ redisson:
   nettyThreads: 32
   # 单节点配置
   singleServerConfig:
-    # 客户端名称
-    clientName: ${ruoyi.name}
+    # 客户端名称 不能用中文
+    clientName: RuoYi-Vue-Plus
     # 最小空闲连接数
     connectionMinimumIdleSize: 32
     # 连接池大小

+ 22 - 24
ruoyi-admin/src/main/resources/application.yml

@@ -1,24 +1,3 @@
-# 项目相关配置
-ruoyi:
-  # 名称
-  name: RuoYi-Vue-Plus
-  # 版本
-  version: ${revision}
-  # 版权年份
-  copyrightYear: 2024
-
-captcha:
-  enable: true
-  # 页面 <参数设置> 可开启关闭 验证码校验
-  # 验证码类型 math 数组计算 char 字符验证
-  type: MATH
-  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
-  category: CIRCLE
-  # 数字验证码位数
-  numberLength: 1
-  # 字符验证码长度
-  charLength: 4
-
 # 开发环境配置
 server:
   # 服务器的HTTP端口,默认为8080
@@ -41,6 +20,18 @@ server:
       # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
       worker: 256
 
+captcha:
+  enable: true
+  # 页面 <参数设置> 可开启关闭 验证码校验
+  # 验证码类型 math 数组计算 char 字符验证
+  type: MATH
+  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
+  category: CIRCLE
+  # 数字验证码位数
+  numberLength: 1
+  # 字符验证码长度
+  charLength: 4
+
 # 日志配置
 logging:
   level:
@@ -61,7 +52,7 @@ user:
 # Spring配置
 spring:
   application:
-    name: ${ruoyi.name}
+    name: RuoYi-Vue-Plus
   threads:
     # 开启虚拟线程 仅jdk21可用
     virtual:
@@ -191,7 +182,7 @@ springdoc:
 #    persistAuthorization: true
   info:
     # 标题
-    title: '标题:${ruoyi.name}多租户管理系统_接口文档'
+    title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
     # 描述
     description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
     # 版本
@@ -228,7 +219,6 @@ xss:
   # 排除链接(多个用逗号分隔)
   excludeUrls:
     - /system/notice
-    - /warm-flow/save-xml
 
 # 全局线程池相关配置
 # 如使用JDK21请直接使用虚拟线程 不要开启此配置
@@ -281,3 +271,11 @@ warm-flow:
   ui: true
   # 默认Authorization,如果有多个token,用逗号分隔
   token-name: ${sa-token.token-name},clientid
+  # 流程状态对应的三元色
+  chart-status-color:
+    ## 未办理
+    - 62,62,62
+    ## 待办理
+    - 255,205,23
+    ## 已办理
+    - 157,255,0

+ 5 - 5
ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java

@@ -1,6 +1,6 @@
 package org.dromara.test;
 
-import org.dromara.common.core.config.RuoYiConfig;
+import org.dromara.common.web.config.properties.CaptchaProperties;
 import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -17,19 +17,19 @@ import java.util.concurrent.TimeUnit;
 public class DemoUnitTest {
 
     @Autowired
-    private RuoYiConfig ruoYiConfig;
+    private CaptchaProperties captchaProperties;
 
     @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
     @Test
     public void testTest() {
-        System.out.println(ruoYiConfig);
+        System.out.println(captchaProperties);
     }
 
     @Disabled
     @DisplayName("测试 @Disabled 注解")
     @Test
     public void testDisabled() {
-        System.out.println(ruoYiConfig);
+        System.out.println(captchaProperties);
     }
 
     @Timeout(value = 2L, unit = TimeUnit.SECONDS)
@@ -37,7 +37,7 @@ public class DemoUnitTest {
     @Test
     public void testTimeout() throws InterruptedException {
         Thread.sleep(3000);
-        System.out.println(ruoYiConfig);
+        System.out.println(captchaProperties);
     }
 
 

+ 1 - 1
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -14,7 +14,7 @@
     </description>
 
     <properties>
-        <revision>5.3.0</revision>
+        <revision>5.3.1-BETA</revision>
     </properties>
 
     <dependencyManagement>

+ 0 - 33
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/RuoYiConfig.java

@@ -1,33 +0,0 @@
-package org.dromara.common.core.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * 读取项目相关配置
- *
- * @author Lion Li
- */
-
-@Data
-@Component
-@ConfigurationProperties(prefix = "ruoyi")
-public class RuoYiConfig {
-
-    /**
-     * 项目名称
-     */
-    private String name;
-
-    /**
-     * 版本
-     */
-    private String version;
-
-    /**
-     * 版权年份
-     */
-    private String copyrightYear;
-
-}

+ 4 - 3
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java

@@ -3,6 +3,7 @@ package org.dromara.common.core.config;
 import jakarta.validation.Validator;
 import org.hibernate.validator.HibernateValidator;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
 import org.springframework.context.MessageSource;
 import org.springframework.context.annotation.Bean;
 import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@@ -14,11 +15,11 @@ import java.util.Properties;
  *
  * @author Lion Li
  */
-@AutoConfiguration
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
 public class ValidatorConfig {
 
     /**
-     * 配置校验框架 快速返回模式
+     * 配置校验框架 快速失败模式
      */
     @Bean
     public Validator validator(MessageSource messageSource) {
@@ -28,7 +29,7 @@ public class ValidatorConfig {
             // 设置使用 HibernateValidator 校验器
             factoryBean.setProviderClass(HibernateValidator.class);
             Properties properties = new Properties();
-            // 设置 快速异常返回
+            // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
             properties.setProperty("hibernate.validator.fail_fast", "true");
             factoryBean.setValidationProperties(properties);
             // 加载配置

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -30,6 +30,11 @@ public interface CacheNames {
      */
     String SYS_DICT = "sys_dict";
 
+    /**
+     * 数据字典类型
+     */
+    String SYS_DICT_TYPE = "sys_dict_type";
+
     /**
      * 租户
      */

+ 8 - 3
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java

@@ -17,9 +17,14 @@ public interface RegexConstants extends RegexPool {
     String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
 
     /**
-     * 权限标识必须符合 tool:build:list 格式,或者空字符串
-     */
-    String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
+     * 权限标识必须符合以下格式:
+     * 1. 标准格式:xxx:yyy:zzz
+     * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
+     * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
+     * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
+     * 2. 允许空字符串(""),表示没有权限标识
+     */
+    String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
 
     /**
      * 身份证号码(后6位)

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java

@@ -72,4 +72,9 @@ public interface SystemConstants {
      */
     Long SUPER_ADMIN_ID = 1L;
 
+    /**
+     * 根部门祖级列表
+     */
+    String ROOT_DEPT_ANCESTORS = "0";
+
 }

+ 0 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DeptDTO.java

@@ -11,7 +11,6 @@ import java.io.Serializable;
  *
  * @author AprilWind
  */
-
 @Data
 @NoArgsConstructor
 public class DeptDTO implements Serializable {

+ 41 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java

@@ -0,0 +1,41 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典数据DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictDataDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典标签
+     */
+    private String dictLabel;
+
+    /**
+     * 字典键值
+     */
+    private String dictValue;
+
+    /**
+     * 是否默认(Y是 N否)
+     */
+    private String isDefault;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 41 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java

@@ -0,0 +1,41 @@
+package org.dromara.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典类型DTO
+ *
+ * @author AprilWind
+ */
+@Data
+@NoArgsConstructor
+public class DictTypeDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典主键
+     */
+    private Long dictId;
+
+    /**
+     * 字典名称
+     */
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/RoleDTO.java

@@ -35,7 +35,7 @@ public class RoleDTO implements Serializable {
     private String roleKey;
 
     /**
-     * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
      */
     private String dataScope;
 

+ 2 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java → ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessCreateTaskEvent.java

@@ -6,12 +6,12 @@ import java.io.Serial;
 import java.io.Serializable;
 
 /**
- * 流程办理监听
+ * 流程创建任务监听
  *
  * @author may
  */
 @Data
-public class ProcessTaskEvent implements Serializable {
+public class ProcessCreateTaskEvent implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;

+ 20 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java

@@ -1,5 +1,9 @@
 package org.dromara.common.core.service;
 
+import org.dromara.common.core.domain.dto.DictDataDTO;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
+
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -64,4 +68,20 @@ public interface DictService {
      */
     Map<String, String> getAllDictByDictType(String dictType);
 
+    /**
+     * 根据字典类型查询详细信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型详细信息
+     */
+    DictTypeDTO getDictType(String dictType);
+
+    /**
+     * 根据字典类型查询字典数据列表
+     *
+     * @param dictType 字典类型
+     * @return 字典数据列表
+     */
+    List<DictDataDTO> getDictData(String dictType);
+
 }

+ 0 - 1
ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1,6 +1,5 @@
 org.dromara.common.core.config.ApplicationConfig
 org.dromara.common.core.config.AsyncConfig
-org.dromara.common.core.config.RuoYiConfig
 org.dromara.common.core.config.ThreadPoolConfig
 org.dromara.common.core.config.ValidatorConfig
 org.dromara.common.core.utils.SpringUtils

+ 0 - 3
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java

@@ -76,10 +76,7 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
         String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
 
         // 设置响应头
-        servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
         servletResponse.setHeader(headerFlag, encryptPassword);
-        servletResponse.setHeader("Access-Control-Allow-Origin", "*");
-        servletResponse.setHeader("Access-Control-Allow-Methods", "*");
         servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
 
         // 获取原始内容

+ 3 - 8
ruoyi-common/ruoyi-common-oss/pom.xml

@@ -31,11 +31,6 @@
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>s3</artifactId>
             <exclusions>
-                <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
-                <exclusion>
-                    <groupId>software.amazon.awssdk</groupId>
-                    <artifactId>netty-nio-client</artifactId>
-                </exclusion>
                 <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
                 <exclusion>
                     <groupId>software.amazon.awssdk</groupId>
@@ -54,10 +49,10 @@
             </exclusions>
         </dependency>
 
-        <!-- 使用AWS基于 CRT 的 S3 客户端 -->
+        <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
         <dependency>
-            <groupId>software.amazon.awssdk.crt</groupId>
-            <artifactId>aws-crt</artifactId>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>netty-nio-client</artifactId>
         </dependency>
 
         <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->

+ 5 - 9
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java

@@ -16,10 +16,10 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 import software.amazon.awssdk.core.ResponseInputStream;
 import software.amazon.awssdk.core.async.AsyncResponseTransformer;
 import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3AsyncClient;
 import software.amazon.awssdk.services.s3.S3Configuration;
-import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
 import software.amazon.awssdk.services.s3.model.GetObjectResponse;
 import software.amazon.awssdk.services.s3.presigner.S3Presigner;
 import software.amazon.awssdk.transfer.s3.S3TransferManager;
@@ -84,18 +84,14 @@ public class OssClient {
             // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
             boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
 
-            // 创建AWS基于 CRT 的 S3 客户端
-            this.client = S3AsyncClient.crtBuilder()
+            // 创建AWS基于 Netty 的 S3 客户端
+            this.client = S3AsyncClient.builder()
                 .credentialsProvider(credentialsProvider)
                 .endpointOverride(URI.create(getEndpoint()))
                 .region(of())
-                .targetThroughputInGbps(20.0)
-                .minimumPartSizeInBytes(10 * 1025 * 1024L)
-                .checksumValidationEnabled(false)
                 .forcePathStyle(isStyle)
-                .httpConfiguration(S3CrtHttpConfiguration.builder()
-                    .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
-                    .build())
+                .httpClient(NettyNioAsyncHttpClient.builder()
+                    .connectionTimeout(Duration.ofSeconds(60)).build())
                 .build();
 
             //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端

+ 1 - 0
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/CaffeineCacheDecorator.java

@@ -78,6 +78,7 @@ public class CaffeineCacheDecorator implements Cache {
 
     @Override
     public void clear() {
+        CAFFEINE.invalidateAll();
         cache.clear();
     }
 

+ 11 - 5
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java

@@ -63,23 +63,25 @@ public class LoginHelper {
     /**
      * 获取用户(多级缓存)
      */
-    public static LoginUser getLoginUser() {
+    @SuppressWarnings("unchecked cast")
+    public static <T extends LoginUser> T getLoginUser() {
         SaSession session = StpUtil.getTokenSession();
         if (ObjectUtil.isNull(session)) {
             return null;
         }
-        return (LoginUser) session.get(LOGIN_USER_KEY);
+        return (T) session.get(LOGIN_USER_KEY);
     }
 
     /**
      * 获取用户基于token
      */
-    public static LoginUser getLoginUser(String token) {
+    @SuppressWarnings("unchecked cast")
+    public static <T extends LoginUser> T getLoginUser(String token) {
         SaSession session = StpUtil.getTokenSessionByToken(token);
         if (ObjectUtil.isNull(session)) {
             return null;
         }
-        return (LoginUser) session.get(LOGIN_USER_KEY);
+        return (T) session.get(LOGIN_USER_KEY);
     }
 
     /**
@@ -191,7 +193,11 @@ public class LoginHelper {
      * @return 结果
      */
     public static boolean isTenantAdmin() {
-        return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
+        LoginUser loginUser = getLoginUser();
+        if (loginUser == null) {
+            return false;
+        }
+        return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
     }
 
     /**

+ 9 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysDept.java

@@ -1,5 +1,6 @@
 package org.dromara.system.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -8,6 +9,8 @@ import lombok.EqualsAndHashCode;
 import org.dromara.common.tenant.core.TenantEntity;
 
 import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 部门表 sys_dept
@@ -80,4 +83,10 @@ public class SysDept extends TenantEntity {
      */
     private String ancestors;
 
+    /**
+     * 子部门
+     */
+    @TableField(exist = false)
+    private List<SysMenu> children = new ArrayList<>();
+
 }

+ 5 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOss.java

@@ -42,6 +42,11 @@ public class SysOss extends TenantEntity {
      */
     private String url;
 
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
     /**
      * 服务商
      */

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysRole.java

@@ -42,7 +42,7 @@ public class SysRole extends TenantEntity {
     private Integer roleSort;
 
     /**
-     * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
      */
     private String dataScope;
 

+ 5 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOssBo.java

@@ -41,6 +41,11 @@ public class SysOssBo extends BaseEntity {
      */
     private String url;
 
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
     /**
      * 服务商
      */

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysRoleBo.java

@@ -49,7 +49,7 @@ public class SysRoleBo extends BaseEntity {
     private Integer roleSort;
 
     /**
-     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限
      */
     private String dataScope;
 

+ 5 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java

@@ -103,6 +103,11 @@ public class SysUserBo extends BaseEntity {
      */
     private Long roleId;
 
+    /**
+     * 用户ID
+     */
+    private String userIds;
+
     /**
      * 排除不查询的用户(工作流用)
      */

+ 10 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysDeptVo.java

@@ -2,15 +2,18 @@ package org.dromara.system.domain.vo;
 
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.excel.convert.ExcelDictConvert;
 import org.dromara.system.domain.SysDept;
-import io.github.linpeilie.annotations.AutoMapper;
-import lombok.Data;
+import org.dromara.system.domain.SysMenu;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 /**
  * 部门视图对象 sys_dept
@@ -99,4 +102,9 @@ public class SysDeptVo implements Serializable {
     @ExcelProperty(value = "创建时间")
     private Date createTime;
 
+    /**
+     * 子部门
+     */
+    private List<SysMenu> children = new ArrayList<>();
+
 }

+ 5 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysOssVo.java

@@ -47,6 +47,11 @@ public class SysOssVo implements Serializable {
      */
     private String url;
 
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
     /**
      * 创建时间
      */

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysRoleVo.java

@@ -51,10 +51,10 @@ public class SysRoleVo implements Serializable {
     private Integer roleSort;
 
     /**
-     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限
      */
     @ExcelProperty(value = "数据范围", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
+    @ExcelDictFormat(readConverterExp = "1=全部数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限,6=部门及以下或本人数据权限")
     private String dataScope;
 
     /**

+ 12 - 3
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java

@@ -16,10 +16,17 @@ import java.util.List;
 public interface ISysUserService {
 
 
+    /**
+     * 根据条件分页查询用户列表
+     *
+     * @param user      用户信息
+     * @param pageQuery 发呢也
+     * @return 用户信息
+     */
     TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery);
 
     /**
-     * 根据条件分页查询用户列表
+     * 导出用户列表
      *
      * @param user 用户信息
      * @return 用户信息集合信息
@@ -29,7 +36,8 @@ public interface ISysUserService {
     /**
      * 根据条件分页查询已分配用户角色列表
      *
-     * @param user 用户信息
+     * @param user      用户信息
+     * @param pageQuery 分页
      * @return 用户信息集合信息
      */
     TableDataInfo<SysUserVo> selectAllocatedList(SysUserBo user, PageQuery pageQuery);
@@ -37,7 +45,8 @@ public interface ISysUserService {
     /**
      * 根据条件分页查询未分配用户角色列表
      *
-     * @param user 用户信息
+     * @param user      用户信息
+     * @param pageQuery 分页
      * @return 用户信息集合信息
      */
     TableDataInfo<SysUserVo> selectUnallocatedList(SysUserBo user, PageQuery pageQuery);

+ 4 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java

@@ -323,8 +323,10 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
             dept.setAncestors(oldDept.getAncestors());
         }
         int result = baseMapper.updateById(dept);
-        if (SystemConstants.NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
-            && !StringUtils.equals(SystemConstants.NORMAL, dept.getAncestors())) {
+        // 如果部门状态为启用,且部门祖级列表不为空,且部门祖级列表不等于根部门祖级列表(如果部门祖级列表不等于根部门祖级列表,则说明存在上级部门)
+        if (SystemConstants.NORMAL.equals(dept.getStatus())
+            && StringUtils.isNotEmpty(dept.getAncestors())
+            && !StringUtils.equals(SystemConstants.ROOT_DEPT_ANCESTORS, dept.getAncestors())) {
             // 如果该部门是启用状态,则启用该部门的所有上级部门
             updateParentDeptStatusNormal(dept);
         }

+ 38 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java

@@ -1,5 +1,6 @@
 package org.dromara.system.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -8,6 +9,8 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.dto.DictDataDTO;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.service.DictService;
 import org.dromara.common.core.utils.MapstructUtils;
@@ -121,6 +124,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
      * @param dictType 字典类型
      * @return 字典类型
      */
+    @Cacheable(cacheNames = CacheNames.SYS_DICT_TYPE, key = "#dictType")
     @Override
     public SysDictTypeVo selectDictTypeByType(String dictType) {
         return baseMapper.selectVoOne(new LambdaQueryWrapper<SysDictType>().eq(SysDictType::getDictType, dictType));
@@ -140,6 +144,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
                 throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName()));
             }
             CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType());
+            CacheUtils.evict(CacheNames.SYS_DICT_TYPE, dictType.getDictType());
         }
         baseMapper.deleteByIds(Arrays.asList(dictIds));
     }
@@ -150,6 +155,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public void resetDictCache() {
         CacheUtils.clear(CacheNames.SYS_DICT);
+        CacheUtils.clear(CacheNames.SYS_DICT_TYPE);
     }
 
     /**
@@ -188,6 +194,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
         int row = baseMapper.updateById(dict);
         if (row > 0) {
             CacheUtils.evict(CacheNames.SYS_DICT, oldDict.getDictType());
+            CacheUtils.evict(CacheNames.SYS_DICT_TYPE, oldDict.getDictType());
             return dictDataMapper.selectDictDataByType(dict.getDictType());
         }
         throw new ServiceException("操作失败");
@@ -249,10 +256,40 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
         }
     }
 
+    /**
+     * 获取字典下所有的字典值与标签
+     *
+     * @param dictType 字典类型
+     * @return dictValue为key,dictLabel为值组成的Map
+     */
     @Override
     public Map<String, String> getAllDictByDictType(String dictType) {
-        List<SysDictDataVo> list = selectDictDataByType(dictType);
+        List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
         return StreamUtils.toMap(list, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
     }
 
+    /**
+     * 根据字典类型查询详细信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型详细信息
+     */
+    @Override
+    public DictTypeDTO getDictType(String dictType) {
+        SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
+        return BeanUtil.toBean(vo, DictTypeDTO.class);
+    }
+
+    /**
+     * 根据字典类型查询字典数据列表
+     *
+     * @param dictType 字典类型
+     * @return 字典数据列表
+     */
+    @Override
+    public List<DictDataDTO> getDictData(String dictType) {
+        List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+        return BeanUtil.copyToList(list, DictDataDTO.class);
+    }
+
 }

+ 1 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java

@@ -154,6 +154,7 @@ public class SysPostServiceImpl implements ISysPostService, PostService {
     public boolean checkPostNameUnique(SysPostBo post) {
         boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysPost>()
             .eq(SysPost::getPostName, post.getPostName())
+            .eq(SysPost::getDeptId, post.getDeptId())
             .ne(ObjectUtil.isNotNull(post.getPostId()), SysPost::getPostId, post.getPostId()));
         return !exist;
     }

+ 4 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java

@@ -53,6 +53,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
         QueryWrapper<SysRole> wrapper = Wrappers.query();
         wrapper.eq("r.del_flag", SystemConstants.NORMAL)
+            .eq("r.status", SystemConstants.NORMAL)
             .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), "r.role_name", taskQuery.getHandlerCode())
             .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), "r.role_key", taskQuery.getHandlerName())
             .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
@@ -75,6 +76,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
     public TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery) {
         PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
         LambdaQueryWrapper<SysPost> wrapper = Wrappers.<SysPost>lambdaQuery()
+            .eq(SysPost::getStatus, SystemConstants.NORMAL)
             .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), SysPost::getPostCategory, taskQuery.getHandlerCode())
             .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), SysPost::getPostName, taskQuery.getHandlerName())
             .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
@@ -106,6 +108,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
         LambdaQueryWrapper<SysDept> wrapper = Wrappers.<SysDept>lambdaQuery()
             .eq(SysDept::getDelFlag, SystemConstants.NORMAL)
+            .eq(SysDept::getStatus, SystemConstants.NORMAL)
             .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), SysDept::getDeptCategory, taskQuery.getHandlerCode())
             .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), SysDept::getDeptName, taskQuery.getHandlerName())
             .between(StringUtils.isNotBlank(taskQuery.getBeginTime()) && StringUtils.isNotBlank(taskQuery.getEndTime()),
@@ -143,6 +146,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum());
         QueryWrapper<SysUser> wrapper = Wrappers.query();
         wrapper.eq("u.del_flag", SystemConstants.NORMAL)
+            .eq("u.status", SystemConstants.NORMAL)
             .like(StringUtils.isNotBlank(taskQuery.getHandlerCode()), "u.user_name", taskQuery.getHandlerCode())
             .like(StringUtils.isNotBlank(taskQuery.getHandlerName()), "u.nick_name", taskQuery.getHandlerName())
             .between(taskQuery.getBeginTime() != null && taskQuery.getEndTime() != null,

+ 21 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java

@@ -179,10 +179,20 @@ public class SysTenantServiceImpl implements ISysTenantService {
         for (SysDictType dictType : dictTypeList) {
             dictType.setDictId(null);
             dictType.setTenantId(tenantId);
+            dictType.setCreateDept(null);
+            dictType.setCreateBy(null);
+            dictType.setCreateTime(null);
+            dictType.setUpdateBy(null);
+            dictType.setUpdateTime(null);
         }
         for (SysDictData dictData : dictDataList) {
             dictData.setDictCode(null);
             dictData.setTenantId(tenantId);
+            dictData.setCreateDept(null);
+            dictData.setCreateBy(null);
+            dictData.setCreateTime(null);
+            dictData.setUpdateBy(null);
+            dictData.setUpdateTime(null);
         }
         dictTypeMapper.insertBatch(dictTypeList);
         dictDataMapper.insertBatch(dictDataList);
@@ -192,6 +202,11 @@ public class SysTenantServiceImpl implements ISysTenantService {
         for (SysConfig config : sysConfigList) {
             config.setConfigId(null);
             config.setTenantId(tenantId);
+            config.setCreateDept(null);
+            config.setCreateBy(null);
+            config.setCreateTime(null);
+            config.setUpdateBy(null);
+            config.setUpdateTime(null);
         }
         configMapper.insertBatch(sysConfigList);
 
@@ -433,6 +448,9 @@ public class SysTenantServiceImpl implements ISysTenantService {
                             data.setTenantId(tenantId);
                             data.setCreateTime(null);
                             data.setUpdateTime(null);
+                            data.setCreateDept(null);
+                            data.setCreateBy(null);
+                            data.setUpdateBy(null);
                             set.add(tenantId);
                             saveDataList.add(data);
                         }
@@ -454,6 +472,9 @@ public class SysTenantServiceImpl implements ISysTenantService {
                             data.setTenantId(tenantId);
                             data.setCreateTime(null);
                             data.setUpdateTime(null);
+                            data.setCreateDept(null);
+                            data.setCreateBy(null);
+                            data.setUpdateBy(null);
                             set.add(tenantId);
                             saveDataList.add(data);
                         }

+ 1 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java

@@ -79,6 +79,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
         QueryWrapper<SysUser> wrapper = Wrappers.query();
         wrapper.eq("u.del_flag", SystemConstants.NORMAL)
             .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
+            .in(StringUtils.isNotBlank(user.getUserIds()), "u.user_id", StringUtils.splitTo(user.getUserIds(), Convert::toLong))
             .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
             .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
             .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())

+ 0 - 3
ruoyi-modules/ruoyi-workflow/README.md

@@ -1,3 +0,0 @@
-# 工作流说明
-
-工作流目前在未成熟阶段 后续仍会经历重构 甚至重写(生产使用前请慎重考虑后续是否要更新维护)

+ 60 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java

@@ -0,0 +1,60 @@
+package org.dromara.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 按钮权限枚举
+ *
+ * @author AprilWind
+ */
+@Getter
+@AllArgsConstructor
+public enum ButtonPermissionEnum implements NodeExtEnum {
+
+    /**
+     * 是否弹窗选人
+     */
+    POP("是否弹窗选人", "pop", false),
+
+    /**
+     * 是否能委托
+     */
+    TRUST("是否能委托", "trust", false),
+
+    /**
+     * 是否能转办
+     */
+    TRANSFER("是否能转办", "transfer", false),
+
+    /**
+     * 是否能抄送
+     */
+    COPY("是否能抄送", "copy", false),
+
+    /**
+     * 是否显示退回
+     */
+    BACK("是否显示退回", "back", true),
+
+    /**
+     * 是否能加签
+     */
+    ADD_SIGN("是否能加签", "addSign", false),
+
+    /**
+     * 是否能减签
+     */
+    SUB_SIGN("是否能减签", "subSign", false),
+
+    /**
+     * 是否能终止
+     */
+    TERMINATION("是否能终止", "termination", true);
+
+    private final String label;
+    private final String value;
+    private final boolean selected;
+
+}
+

+ 32 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java

@@ -0,0 +1,32 @@
+package org.dromara.workflow.common.enums;
+
+/**
+ * 节点扩展属性枚举
+ *
+ * @author AprilWind
+ */
+public interface NodeExtEnum {
+
+    /**
+     * 选项label
+     *
+     * @return 选项label
+     */
+    String getLabel();
+
+    /**
+     * 选项值
+     *
+     * @return 选项值
+     */
+    String getValue();
+
+    /**
+     * 是否默认选中
+     *
+     * @return 是否默认选中
+     */
+    boolean isSelected();
+
+}
+

+ 11 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java

@@ -12,6 +12,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.warm.flow.core.entity.Node;
+import org.dromara.warm.flow.orm.entity.FlowNode;
 import org.dromara.workflow.common.ConditionalOnEnable;
 import org.dromara.workflow.domain.bo.*;
 import org.dromara.workflow.domain.vo.FlowHisTaskVo;
@@ -127,6 +128,16 @@ public class FlwTaskController extends BaseController {
         return R.ok(flwTaskService.selectById(taskId));
     }
 
+    /**
+     * 获取下一节点信息
+     *
+     * @param bo 参数
+     */
+    @PostMapping("/getNextNodeList")
+    public R<List<FlowNode>> getNextNodeList(@RequestBody FlowNextNodeBo bo) {
+        return R.ok(flwTaskService.getNextNodeList(bo));
+    }
+
     /**
      * 终止任务
      *

+ 9 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java

@@ -1,5 +1,6 @@
 package org.dromara.workflow.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -8,6 +9,8 @@ import lombok.EqualsAndHashCode;
 import org.dromara.common.tenant.core.TenantEntity;
 
 import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 流程分类对象 wf_category
@@ -55,4 +58,10 @@ public class FlowCategory extends TenantEntity {
     @TableLogic
     private String delFlag;
 
+    /**
+     * 子菜单
+     */
+    @TableField(exist = false)
+    private List<FlowCategory> children = new ArrayList<>();
+
 }

+ 5 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java

@@ -58,9 +58,13 @@ public class CompleteTaskBo implements Serializable {
      */
     private Map<String, Object> variables;
 
+    /**
+     * 弹窗选择的办理人
+     */
+    private Map<String, Object> assigneeMap;
+
     /**
      * 扩展变量(此处为逗号分隔的ossId)
-     * @return
      */
     private String ext;
 

+ 38 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java

@@ -0,0 +1,38 @@
+package org.dromara.workflow.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 下一节点信息
+ *
+ * @author may
+ */
+@Data
+public class FlowNextNodeBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * 任务id
+     */
+    private String taskId;
+
+    /**
+     * 流程变量
+     */
+    private Map<String, Object> variables;
+
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            return new HashMap<>(16);
+        }
+        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
+        return variables;
+    }
+}

+ 34 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java

@@ -0,0 +1,34 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 按钮权限
+ *
+ * @author may
+ * @date 2025-02-28
+ */
+@Data
+public class ButtonPermission implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 枚举路径
+     */
+    private String code;
+
+    /**
+     * 按钮编码
+     */
+    private String value;
+
+    /**
+     * 是否显示
+     */
+    private boolean show;
+}

+ 7 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java

@@ -8,7 +8,9 @@ import org.dromara.workflow.domain.FlowCategory;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 
 /**
@@ -64,4 +66,9 @@ public class FlowCategoryVo implements Serializable {
     @ExcelProperty(value = "创建时间")
     private Date createTime;
 
+    /**
+     * 子菜单
+     */
+    private List<FlowCategoryVo> children = new ArrayList<>();
+
 }

+ 41 - 2
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java

@@ -1,16 +1,20 @@
 package org.dromara.workflow.domain.vo;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONUtil;
 import lombok.Data;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.translation.annotation.Translation;
 import org.dromara.common.translation.constant.TransConstant;
 import org.dromara.warm.flow.core.entity.User;
 import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.ButtonPermissionEnum;
 
 import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 任务视图
@@ -173,4 +177,39 @@ public class FlowTaskVo implements Serializable {
      */
     @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
     private String createByName;
+
+    /**
+     * 是否为申请人节点
+     */
+    private boolean applyNode;
+
+    /**
+     * 按钮权限
+     */
+    private List<ButtonPermission> buttonList;
+
+    public List<ButtonPermission> getButtonList(String ext) {
+        List<ButtonPermission> buttonPermissions = Arrays.stream(ButtonPermissionEnum.values())
+            .map(value -> {
+                ButtonPermission buttonPermission = new ButtonPermission();
+                buttonPermission.setCode(value.getValue());
+                buttonPermission.setShow(false);
+                return buttonPermission;
+            })
+            .collect(Collectors.toList());
+        if (StringUtils.isNotBlank(ext)) {
+            List<ButtonPermission> buttonCodeList = JSONUtil.toList(JSONUtil.parseArray(ext), ButtonPermission.class);
+            if (CollUtil.isNotEmpty(buttonCodeList)) {
+                Optional<ButtonPermission> firstPermission = buttonCodeList.stream().findFirst();
+                firstPermission.ifPresent(permission -> {
+                    Set<String> codeSet = Arrays.stream(permission.getValue().split(","))
+                        .map(String::trim)
+                        .filter(code -> !code.isEmpty())
+                        .collect(Collectors.toSet());
+                    buttonPermissions.forEach(bp -> bp.setShow(codeSet.contains(bp.getCode())));
+                });
+            }
+        }
+        return buttonPermissions;
+    }
 }

+ 11 - 11
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java

@@ -1,9 +1,9 @@
 package org.dromara.workflow.handler;
 
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.event.ProcessCreateTaskEvent;
 import org.dromara.common.core.domain.event.ProcessDeleteEvent;
 import org.dromara.common.core.domain.event.ProcessEvent;
-import org.dromara.common.core.domain.event.ProcessTaskEvent;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.workflow.common.ConditionalOnEnable;
@@ -23,7 +23,7 @@ import java.util.Map;
 public class FlowProcessEventHandler {
 
     /**
-     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等)
+     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
      *
      * @param flowCode   流程定义编码
      * @param businessId 业务id
@@ -44,23 +44,23 @@ public class FlowProcessEventHandler {
     }
 
     /**
-     * 执行办理任务监听
+     * 执行创建任务监听
      *
      * @param flowCode   流程定义编码
      * @param nodeCode   审批节点编码
      * @param taskId     任务id
      * @param businessId 业务id
      */
-    public void processTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) {
+    public void processCreateTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) {
         String tenantId = TenantHelper.getTenantId();
         log.info("发布流程任务事件, 租户ID: {}, 流程编码: {}, 节点编码: {}, 任务ID: {}, 业务ID: {}", tenantId, flowCode, nodeCode, taskId, businessId);
-        ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
-        processTaskEvent.setTenantId(tenantId);
-        processTaskEvent.setFlowCode(flowCode);
-        processTaskEvent.setNodeCode(nodeCode);
-        processTaskEvent.setTaskId(taskId);
-        processTaskEvent.setBusinessId(businessId);
-        SpringUtils.context().publishEvent(processTaskEvent);
+        ProcessCreateTaskEvent processCreateTaskEvent = new ProcessCreateTaskEvent();
+        processCreateTaskEvent.setTenantId(tenantId);
+        processCreateTaskEvent.setFlowCode(flowCode);
+        processCreateTaskEvent.setNodeCode(nodeCode);
+        processCreateTaskEvent.setTaskId(taskId);
+        processCreateTaskEvent.setBusinessId(businessId);
+        SpringUtils.context().publishEvent(processCreateTaskEvent);
     }
 
     /**

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java

@@ -52,7 +52,7 @@ public class WorkflowGlobalListener implements GlobalListener {
         Task task = listenerVariable.getTask();
         if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) {
             // 判断流程状态(发布审批中事件)
-            flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId);
+            flowProcessEventHandler.processCreateTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId);
         }
     }
 

+ 76 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java

@@ -0,0 +1,76 @@
+package org.dromara.workflow.service;
+
+import org.dromara.warm.flow.core.entity.User;
+import org.dromara.warm.flow.core.service.UserService;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 通用 工作流服务
+ *
+ * @author LionLi
+ */
+public interface IFlwCommonService {
+
+    /**
+     * 获取工作流用户service
+     *
+     * @return 工作流用户service
+     */
+    UserService getFlowUserService();
+
+    /**
+     * 构建工作流用户
+     *
+     * @param userList 办理用户
+     * @param taskId   任务ID
+     * @return 用户
+     */
+    Set<User> buildUser(List<User> userList, Long taskId);
+
+    /**
+     * 构建工作流用户
+     *
+     * @param userIdList 办理用户
+     * @param taskId     任务ID
+     * @return 用户
+     */
+    Set<User> buildFlowUser(List<String> userIdList, Long taskId);
+
+    /**
+     * 发送消息
+     *
+     * @param flowName    流程定义名称
+     * @param instId      实例id
+     * @param messageType 消息类型
+     * @param message     消息内容,为空则发送默认配置的消息内容
+     */
+    void sendMessage(String flowName, Long instId, List<String> messageType, String message);
+
+    /**
+     * 驳回
+     *
+     * @param message        审批意见
+     * @param instanceId     流程实例id
+     * @param targetNodeCode 目标节点
+     * @param flowStatus     流程状态
+     * @param flowHisStatus  节点操作状态
+     */
+    void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus);
+
+    /**
+     * 申请人节点编码
+     *
+     * @param definitionId 流程定义id
+     * @return 申请人节点编码
+     */
+    String applyNodeCode(Long definitionId);
+
+    /**
+     * 删除运行中的任务
+     *
+     * @param taskIds 任务id
+     */
+    void deleteRunTask(List<Long> taskIds);
+}

+ 18 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java

@@ -6,6 +6,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.warm.flow.core.entity.Node;
 import org.dromara.warm.flow.orm.entity.FlowHisTask;
+import org.dromara.warm.flow.orm.entity.FlowNode;
 import org.dromara.warm.flow.orm.entity.FlowTask;
 import org.dromara.workflow.domain.bo.*;
 import org.dromara.workflow.domain.vo.FlowHisTaskVo;
@@ -132,6 +133,14 @@ public interface IFlwTaskService {
      */
     FlowTaskVo selectById(Long taskId);
 
+    /**
+     * 获取下一节点信息
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    List<FlowNode> getNextNodeList(FlowNextNodeBo bo);
+
     /**
      * 按照任务id查询任务
      *
@@ -188,4 +197,13 @@ public interface IFlwTaskService {
      * @return 结果
      */
     List<UserDTO> currentTaskAllUser(Long taskId);
+
+    /**
+     * 按照节点编码查询节点
+     *
+     * @param nodeCode     节点编码
+     * @param definitionId 流程定义id
+     * @return 节点
+     */
+    FlowNode getByNodeCode(String nodeCode, Long definitionId);
 }

+ 71 - 30
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java → ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java

@@ -1,10 +1,10 @@
-package org.dromara.workflow.utils;
+package org.dromara.workflow.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.dto.UserDTO;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StreamUtils;
@@ -28,9 +28,13 @@ import org.dromara.warm.flow.orm.entity.FlowTask;
 import org.dromara.warm.flow.orm.entity.FlowUser;
 import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
 import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
 import org.dromara.workflow.common.enums.MessageTypeEnum;
+import org.dromara.workflow.common.enums.TaskAssigneeType;
+import org.dromara.workflow.service.IFlwCommonService;
 import org.dromara.workflow.service.IFlwTaskAssigneeService;
 import org.dromara.workflow.service.IFlwTaskService;
+import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -42,24 +46,26 @@ import java.util.stream.Collectors;
 /**
  * 工作流工具
  *
- * @author may
+ * @author LionLi
  */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class WorkflowUtils {
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwCommonServiceImpl implements IFlwCommonService {
 
-    private static final IFlwTaskAssigneeService TASK_ASSIGNEE_SERVICE = SpringUtils.getBean(IFlwTaskAssigneeService.class);
-    private static final IFlwTaskService FLW_TASK_SERVICE = SpringUtils.getBean(IFlwTaskService.class);
-    private static final FlowNodeMapper FLOW_NODE_MAPPER = SpringUtils.getBean(FlowNodeMapper.class);
-    private static final FlowTaskMapper FLOW_TASK_MAPPER = SpringUtils.getBean(FlowTaskMapper.class);
-    private static final UserService USER_SERVICE = SpringUtils.getBean(UserService.class);
-    private static final TaskService TASK_SERVICE = SpringUtils.getBean(TaskService.class);
-    private static final NodeService NODE_SERVICE = SpringUtils.getBean(NodeService.class);
+    private final FlowNodeMapper flowNodeMapper;
+    private final FlowTaskMapper flowTaskMapper;
+    private final UserService userService;
+    private final TaskService taskService;
+    private final NodeService nodeService;
 
     /**
      * 获取工作流用户service
      */
-    public static UserService getFlowUserService() {
-        return USER_SERVICE;
+    @Override
+    public UserService getFlowUserService() {
+        return userService;
     }
 
     /**
@@ -69,15 +75,17 @@ public class WorkflowUtils {
      * @param taskId   任务ID
      * @return 用户
      */
-    public static Set<User> buildUser(List<User> userList, Long taskId) {
+    @Override
+    public Set<User> buildUser(List<User> userList, Long taskId) {
         if (CollUtil.isEmpty(userList)) {
             return Set.of();
         }
         Set<User> list = new HashSet<>();
         Set<String> processedBySet = new HashSet<>();
+        IFlwTaskAssigneeService taskAssigneeService = SpringUtils.getBean(IFlwTaskAssigneeService.class);
         for (User user : userList) {
             // 根据 processedBy 前缀判断处理人类型,分别获取用户列表
-            List<UserDTO> users = TASK_ASSIGNEE_SERVICE.fetchUsersByStorageId(user.getProcessedBy());
+            List<UserDTO> users = taskAssigneeService.fetchUsersByStorageId(user.getProcessedBy());
             // 转换为 FlowUser 并添加到结果集合
             if (CollUtil.isNotEmpty(users)) {
                 users.forEach(dto -> {
@@ -96,6 +104,33 @@ public class WorkflowUtils {
         return list;
     }
 
+    /**
+     * 构建工作流用户
+     *
+     * @param userIdList 办理用户
+     * @param taskId     任务ID
+     * @return 用户
+     */
+    @Override
+    public Set<User> buildFlowUser(List<String> userIdList, Long taskId) {
+        if (CollUtil.isEmpty(userIdList)) {
+            return Set.of();
+        }
+        Set<User> list = new HashSet<>();
+        Set<String> processedBySet = new HashSet<>();
+        for (String userId : userIdList) {
+            if (!processedBySet.contains(userId)) {
+                FlowUser flowUser = new FlowUser();
+                flowUser.setType(TaskAssigneeType.APPROVER.getCode());
+                flowUser.setProcessedBy(String.valueOf(userId));
+                flowUser.setAssociated(taskId);
+                list.add(flowUser);
+                processedBySet.add(String.valueOf(userId));
+            }
+        }
+        return list;
+    }
+
     /**
      * 发送消息
      *
@@ -103,14 +138,16 @@ public class WorkflowUtils {
      * @param messageType 消息类型
      * @param message     消息内容,为空则发送默认配置的消息内容
      */
-    public static void sendMessage(String flowName, Long instId, List<String> messageType, String message) {
+    @Override
+    public void sendMessage(String flowName, Long instId, List<String> messageType, String message) {
+        IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
         List<UserDTO> userList = new ArrayList<>();
-        List<FlowTask> list = FLW_TASK_SERVICE.selectByInstId(instId);
+        List<FlowTask> list = flwTaskService.selectByInstId(instId);
         if (StringUtils.isBlank(message)) {
             message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。";
         }
         for (Task task : list) {
-            List<UserDTO> users = FLW_TASK_SERVICE.currentTaskAllUser(task.getId());
+            List<UserDTO> users = flwTaskService.currentTaskAllUser(task.getId());
             if (CollUtil.isNotEmpty(users)) {
                 userList.addAll(users);
             }
@@ -149,8 +186,10 @@ public class WorkflowUtils {
      * @param flowStatus     流程状态
      * @param flowHisStatus  节点操作状态
      */
-    public static void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) {
-        List<FlowTask> list = FLW_TASK_SERVICE.selectByInstId(instanceId);
+    @Override
+    public void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) {
+        IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
+        List<FlowTask> list = flwTaskService.selectByInstId(instanceId);
         if (CollUtil.isNotEmpty(list)) {
             List<FlowTask> tasks = StreamUtils.filter(list, e -> e.getNodeCode().equals(targetNodeCode));
             if (list.size() == tasks.size()) {
@@ -158,7 +197,7 @@ public class WorkflowUtils {
             }
         }
         for (FlowTask task : list) {
-            List<UserDTO> userList = FLW_TASK_SERVICE.currentTaskAllUser(task.getId());
+            List<UserDTO> userList = flwTaskService.currentTaskAllUser(task.getId());
             FlowParams flowParams = FlowParams.build();
             flowParams.nodeCode(targetNodeCode);
             flowParams.message(message);
@@ -169,7 +208,7 @@ public class WorkflowUtils {
             if (CollUtil.isNotEmpty(userList)) {
                 flowParams.handler(userList.get(0).getUserId().toString());
             }
-            TASK_SERVICE.skip(task.getId(), flowParams);
+            taskService.skip(task.getId(), flowParams);
         }
         //解决会签多人审批问题
         backTask(message, instanceId, targetNodeCode, flowStatus, flowHisStatus);
@@ -181,13 +220,14 @@ public class WorkflowUtils {
      * @param definitionId 流程定义id
      * @return 申请人节点编码
      */
-    public static String applyNodeCode(Long definitionId) {
+    @Override
+    public String applyNodeCode(Long definitionId) {
         //获取已发布的流程节点
-        List<FlowNode> flowNodes = FLOW_NODE_MAPPER.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, definitionId));
+        List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, definitionId));
         AssertUtil.isTrue(CollUtil.isEmpty(flowNodes), ExceptionCons.NOT_PUBLISH_NODE);
         Node startNode = flowNodes.stream().filter(t -> NodeType.isStart(t.getNodeType())).findFirst().orElse(null);
         AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE);
-        Node nextNode = NODE_SERVICE.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
+        Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
         return nextNode.getNodeCode();
     }
 
@@ -196,11 +236,12 @@ public class WorkflowUtils {
      *
      * @param taskIds 任务id
      */
-    public static void deleteRunTask(List<Long> taskIds) {
+    @Override
+    public void deleteRunTask(List<Long> taskIds) {
         if (CollUtil.isEmpty(taskIds)) {
             return;
         }
-        USER_SERVICE.deleteByTaskIds(taskIds);
-        FLOW_TASK_MAPPER.deleteByIds(taskIds);
+        userService.deleteByTaskIds(taskIds);
+        flowTaskMapper.deleteByIds(taskIds);
     }
 }

+ 8 - 2
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java

@@ -33,8 +33,8 @@ import org.dromara.workflow.common.constant.FlowConstant;
 import org.dromara.workflow.domain.FlowCategory;
 import org.dromara.workflow.domain.vo.FlowDefinitionVo;
 import org.dromara.workflow.mapper.FlwCategoryMapper;
+import org.dromara.workflow.service.IFlwCommonService;
 import org.dromara.workflow.service.IFlwDefinitionService;
-import org.dromara.workflow.utils.WorkflowUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -64,6 +64,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     private final FlowNodeMapper flowNodeMapper;
     private final FlowSkipMapper flowSkipMapper;
     private final FlwCategoryMapper flwCategoryMapper;
+    private final IFlwCommonService flwCommonService;
 
     /**
      * 查询流程定义列表
@@ -125,7 +126,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
         List<String> errorMsg = new ArrayList<>();
         if (CollUtil.isNotEmpty(flowNodes)) {
             for (FlowNode flowNode : flowNodes) {
-                String applyNodeCode = WorkflowUtils.applyNodeCode(id);
+                String applyNodeCode = flwCommonService.applyNodeCode(id);
                 if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
                     errorMsg.add(flowNode.getNodeName());
                 }
@@ -219,6 +220,11 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
         flowCategory.setCategoryId(null);
         flowCategory.setTenantId(tenantId);
+        flowCategory.setCreateDept(null);
+        flowCategory.setCreateBy(null);
+        flowCategory.setCreateTime(null);
+        flowCategory.setUpdateBy(null);
+        flowCategory.setUpdateTime(null);
         flwCategoryMapper.insert(flowCategory);
         List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
         List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));

+ 6 - 4
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java

@@ -46,9 +46,9 @@ import org.dromara.workflow.domain.vo.FlowVariableVo;
 import org.dromara.workflow.handler.FlowProcessEventHandler;
 import org.dromara.workflow.mapper.FlwCategoryMapper;
 import org.dromara.workflow.mapper.FlwInstanceMapper;
+import org.dromara.workflow.service.IFlwCommonService;
 import org.dromara.workflow.service.IFlwInstanceService;
 import org.dromara.workflow.service.IFlwTaskService;
-import org.dromara.workflow.utils.WorkflowUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -76,6 +76,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     private final IFlwTaskService flwTaskService;
     private final FlwInstanceMapper flwInstanceMapper;
     private final FlwCategoryMapper flwCategoryMapper;
+    private final IFlwCommonService flwCommonService;
 
     /**
      * 分页查询正在运行的流程实例
@@ -245,15 +246,15 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
             }
             String message = bo.getMessage();
             BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
-            String applyNodeCode = WorkflowUtils.applyNodeCode(definition.getId());
+            String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
             //撤销
-            WorkflowUtils.backTask(message, instance.getId(), applyNodeCode, BusinessStatusEnum.CANCEL.getStatus(), BusinessStatusEnum.CANCEL.getStatus());
+            flwCommonService.backTask(message, instance.getId(), applyNodeCode, BusinessStatusEnum.CANCEL.getStatus(), BusinessStatusEnum.CANCEL.getStatus());
             //判断或签节点是否有多个,只保留一个
             List<Task> currentTaskList = taskService.list(FlowEngine.newTask().setInstanceId(instance.getId()));
             if (CollUtil.isNotEmpty(currentTaskList)) {
                 if (currentTaskList.size() > 1) {
                     currentTaskList.remove(0);
-                    WorkflowUtils.deleteRunTask(StreamUtils.toList(currentTaskList, Task::getId));
+                    flwCommonService.deleteRunTask(StreamUtils.toList(currentTaskList, Task::getId));
                 }
             }
 
@@ -373,6 +374,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
         Instance instance = insService.getById(instanceId);
         if (instance != null) {
             taskService.mergeVariable(instance, variable);
+            insService.updateById(instance);
         }
     }
 

+ 190 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java

@@ -0,0 +1,190 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.DictTypeDTO;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.ui.service.NodeExtService;
+import org.dromara.warm.flow.ui.vo.NodeExt;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.ButtonPermissionEnum;
+import org.dromara.workflow.common.enums.NodeExtEnum;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+/**
+ * 流程设计器-节点扩展属性
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwNodeExtServiceImpl implements NodeExtService {
+
+    /**
+     * 权限页code
+     */
+    private static final String PERMISSION_TAB = "wf_button_tab";
+
+    /**
+     * 权限页名称
+     */
+    private static final String PERMISSION_TAB_NAME = "权限";
+
+    /**
+     * 枚举类型标识
+     */
+    private static final String ENUM_TYPE_PREFIX = "enum:";
+
+    /**
+     * 基础设置
+     */
+    private static final int TYPE_BASE_SETTING = 1;
+
+    /**
+     * 新页签
+     */
+    private static final int TYPE_NEW_TAB = 2;
+
+    /**
+     * 存储不同 dictType 对应的配置信息
+     */
+    private static final Map<String, Map<String, Object>> CHILD_NODE_MAP = new HashMap<>();
+
+    static {
+        CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getName(),
+            Map.of("label", "权限按钮", "type", 4, "must", false, "multiple", true));
+    }
+
+    private final DictService dictService;
+
+    /**
+     * 获取节点扩展属性
+     *
+     * @return 结果
+     */
+    @Override
+    public List<NodeExt> getNodeExt() {
+        List<NodeExt> nodeExtList = new ArrayList<>();
+        // 构建按钮权限页面
+        nodeExtList.add(buildNodeExt(PERMISSION_TAB, PERMISSION_TAB_NAME, TYPE_NEW_TAB,
+            ENUM_TYPE_PREFIX + ButtonPermissionEnum.class.getName()));
+        return nodeExtList;
+    }
+
+    /**
+     * 构建一个 NodeExt 对象
+     *
+     * @param code        编码,此json中唯一
+     * @param name        名称,如果type为新页签时,作为页签名称
+     * @param type        节点类型,1:基础设置,2:新页签
+     * @param sourceTypes 字典/枚举类型来源(逗号分隔)
+     * @return 返回构建好的 NodeExt 对象
+     */
+    private NodeExt buildNodeExt(String code, String name, int type, String sourceTypes) {
+        NodeExt nodeExt = new NodeExt();
+        nodeExt.setCode(code);
+        nodeExt.setType(type);
+        nodeExt.setName(name);
+        nodeExt.setChilds(StringUtils.splitList(sourceTypes)
+            .stream().map(this::buildChildNode)
+            .filter(ObjectUtil::isNotNull)
+            .toList()
+        );
+        return nodeExt;
+    }
+
+    /**
+     * 构建一个 ChildNode 对象
+     *
+     * @param sourceType 字典类型
+     * @return 返回构建好的 ChildNode 对象
+     */
+    private NodeExt.ChildNode buildChildNode(String sourceType) {
+        return sourceType.startsWith(ENUM_TYPE_PREFIX) ?
+            buildChildNodeFromEnum(sourceType.substring(ENUM_TYPE_PREFIX.length())) : buildChildNodeFromDict(sourceType);
+    }
+
+    /**
+     * 根据枚举构建一个 ChildNode 对象
+     *
+     * @param enumClassName 枚举名称
+     * @return 返回构建好的 ChildNode 对象
+     */
+    private NodeExt.ChildNode buildChildNodeFromEnum(String enumClassName) {
+        try {
+            Class<?> enumClass = Class.forName(enumClassName);
+            if (!enumClass.isEnum()) {
+                return null;
+            }
+            NodeExt.ChildNode childNode = buildChildNodeMap(enumClassName);
+            // 编码,此json中唯
+            childNode.setCode(ENUM_TYPE_PREFIX + enumClassName);
+            // 字典,下拉框和复选框时用到
+            childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
+                .filter(NodeExtEnum.class::isInstance)
+                .map(NodeExtEnum.class::cast)
+                .map(x ->
+                    new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected())
+                ).toList());
+            return childNode;
+        } catch (ClassNotFoundException e) {
+            log.error("Enum class not found: {}", enumClassName, e);
+        }
+        return null;
+    }
+
+    /**
+     * 根据字典构建一个 ChildNode 对象
+     *
+     * @param dictType 字典类型
+     * @return 返回构建好的 ChildNode 对象
+     */
+    private NodeExt.ChildNode buildChildNodeFromDict(String dictType) {
+        DictTypeDTO dictTypeDTO = dictService.getDictType(dictType);
+        if (ObjectUtil.isNull(dictTypeDTO)) {
+            return null;
+        }
+        NodeExt.ChildNode childNode = buildChildNodeMap(dictType);
+        // 编码,此json中唯一
+        childNode.setCode(dictType);
+        // label名称
+        childNode.setLabel(dictTypeDTO.getDictName());
+        // 描述
+        childNode.setDesc(dictTypeDTO.getRemark());
+        // 字典,下拉框和复选框时用到
+        childNode.setDict(dictService.getDictData(dictType)
+            .stream().map(x ->
+                new NodeExt.DictItem(x.getDictLabel(), x.getDictValue(), Convert.toBool(x.getIsDefault(), false))
+            ).toList());
+        return childNode;
+    }
+
+    /**
+     * 根据 CHILD_NODE_MAP 中的配置信息,构建一个基本的 ChildNode 对象
+     * 该方法用于设置 ChildNode 的常规属性,例如 label、type、是否必填、是否多选等
+     *
+     * @param key CHILD_NODE_MAP 的 key
+     * @return 返回构建好的 ChildNode 对象
+     */
+    private NodeExt.ChildNode buildChildNodeMap(String key) {
+        NodeExt.ChildNode childNode = new NodeExt.ChildNode();
+        Map<String, Object> map = CHILD_NODE_MAP.get(key);
+        // label名称
+        childNode.setLabel((String) map.get("label"));
+        // 1:输入框 2:输入框 3:下拉框 4:选择框
+        childNode.setType(Convert.toInt(map.get("type"), 1));
+        // 是否必填
+        childNode.setMust(Convert.toBool(map.get("must"), false));
+        // 是否多选
+        childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
+        return childNode;
+    }
+
+}

+ 143 - 20
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java

@@ -30,9 +30,12 @@ import org.dromara.warm.flow.core.entity.*;
 import org.dromara.warm.flow.core.enums.NodeType;
 import org.dromara.warm.flow.core.enums.SkipType;
 import org.dromara.warm.flow.core.service.*;
+import org.dromara.warm.flow.core.utils.ExpressionUtil;
+import org.dromara.warm.flow.core.utils.MapUtil;
 import org.dromara.warm.flow.orm.entity.*;
 import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
 import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
+import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
 import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
 import org.dromara.workflow.common.ConditionalOnEnable;
 import org.dromara.workflow.common.enums.TaskAssigneeType;
@@ -44,8 +47,9 @@ import org.dromara.workflow.handler.FlowProcessEventHandler;
 import org.dromara.workflow.handler.WorkflowPermissionHandler;
 import org.dromara.workflow.mapper.FlwCategoryMapper;
 import org.dromara.workflow.mapper.FlwTaskMapper;
+import org.dromara.workflow.service.IFlwCommonService;
+import org.dromara.workflow.service.IFlwTaskAssigneeService;
 import org.dromara.workflow.service.IFlwTaskService;
-import org.dromara.workflow.utils.WorkflowUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -79,6 +83,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     private final UserService userService;
     private final FlwTaskMapper flwTaskMapper;
     private final FlwCategoryMapper flwCategoryMapper;
+    private final FlowNodeMapper flowNodeMapper;
+    private final IFlwTaskAssigneeService flwTaskAssigneeService;
+    private final IFlwCommonService flwCommonService;
 
     /**
      * 启动任务
@@ -103,6 +110,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (ObjectUtil.isNotNull(flowInstance)) {
             BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
             List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
+            taskService.mergeVariable(flowInstance, variables);
+            insService.updateById(flowInstance);
             StartProcessReturnDTO dto = new StartProcessReturnDTO();
             dto.setProcessInstanceId(taskList.get(0).getInstanceId());
             dto.setTaskId(taskList.get(0).getId());
@@ -155,6 +164,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
                 flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true);
             }
+            // 设置弹窗处理人
+            Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
+            if (CollUtil.isNotEmpty(assigneeMap)) {
+                completeTaskBo.getVariables().putAll(assigneeMap);
+            }
             // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
             FlowParams flowParams = new FlowParams();
             flowParams.variable(completeTaskBo.getVariables());
@@ -167,7 +181,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             Instance instance = taskService.skip(taskId, flowParams);
             this.setHandler(instance, flowTask, flowCopyList);
             // 消息通知
-            WorkflowUtils.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice);
+            flwCommonService.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice);
+            //设置下一环节处理人
+            setNextHandler(ins.getId());
             return true;
         } catch (Exception e) {
             log.error(e.getMessage(), e);
@@ -175,6 +191,60 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
     }
 
+    /**
+     * 设置下一环节处理人
+     *
+     * @param instanceId 实例ID
+     */
+    private void setNextHandler(Long instanceId) {
+        Instance inst = insService.getById(instanceId);
+        List<FlowTask> flowTaskList = selectByInstId(instanceId);
+        Map<String, Object> variableMap = inst.getVariableMap();
+        for (FlowTask task : flowTaskList) {
+            if (variableMap != null && variableMap.containsKey(task.getNodeCode())) {
+                String userIds = variableMap.get(task.getNodeCode()).toString();
+                // 批量删除现有任务的办理人记录
+                flwCommonService.getFlowUserService().deleteByTaskIds(List.of(task.getId()));
+                // 批量新增任务办理人记录
+                Set<User> users = flwCommonService.buildFlowUser(List.of(userIds.split(StringUtils.SEPARATOR)), task.getId());
+                flwCommonService.getFlowUserService().saveBatch(new ArrayList<>(users));
+                variableMap.remove(task.getNodeCode());
+            }
+        }
+        taskService.mergeVariable(inst, variableMap);
+    }
+
+    /**
+     * 设置弹窗处理人
+     *
+     * @param assigneeMap  处理人
+     * @param variablesMap 变量
+     */
+    private Map<String, Object> setPopAssigneeMap(Map<String, Object> assigneeMap, Map<String, Object> variablesMap) {
+        Map<String, Object> map = new HashMap<>();
+        if (CollUtil.isEmpty(assigneeMap)) {
+            return map;
+        }
+        for (Map.Entry<String, Object> entry : assigneeMap.entrySet()) {
+            if (variablesMap.containsKey(entry.getKey())) {
+                String userIds = variablesMap.get(entry.getKey()).toString();
+                if (StringUtils.isNotBlank(userIds)) {
+                    Set<String> hashSet = new HashSet<>();
+                    //弹窗传入的选人
+                    List<String> popUserIds = Arrays.asList(entry.getValue().toString().split(StringUtils.SEPARATOR));
+                    //已有的选人
+                    List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
+                    hashSet.addAll(popUserIds);
+                    hashSet.addAll(variableUserIds);
+                    map.put(entry.getKey(), String.join(StringUtils.SEPARATOR, hashSet));
+                }
+            } else {
+                map.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return map;
+    }
+
     /**
      * 设置办理人
      *
@@ -195,7 +265,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
         List<Long> taskIdList = StreamUtils.toList(flowTasks, FlowTask::getId);
         // 获取与当前任务关联的用户列表
-        List<User> associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList);
+        List<User> associatedUsers = flwCommonService.getFlowUserService().getByAssociateds(taskIdList);
         if (CollUtil.isEmpty(associatedUsers)) {
             return;
         }
@@ -204,16 +274,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         for (FlowTask flowTask : flowTasks) {
             List<User> users = StreamUtils.filter(associatedUsers, user -> Objects.equals(user.getAssociated(), flowTask.getId()));
             if (CollUtil.isNotEmpty(users)) {
-                userList.addAll(WorkflowUtils.buildUser(users, flowTask.getId()));
+                userList.addAll(flwCommonService.buildUser(users, flowTask.getId()));
             }
         }
         // 批量删除现有任务的办理人记录
-        WorkflowUtils.getFlowUserService().deleteByTaskIds(taskIdList);
+        flwCommonService.getFlowUserService().deleteByTaskIds(taskIdList);
         // 确保要保存的 userList 不为空
         if (CollUtil.isEmpty(userList)) {
             return;
         }
-        WorkflowUtils.getFlowUserService().saveBatch(userList);
+        flwCommonService.getFlowUserService().saveBatch(userList);
     }
 
     /**
@@ -253,7 +323,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                 return flowUser;
             }).collect(Collectors.toList());
         // 批量保存抄送人员
-        WorkflowUtils.getFlowUserService().saveBatch(userList);
+        flwCommonService.getFlowUserService().saveBatch(userList);
     }
 
     /**
@@ -381,7 +451,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
             Long definitionId = task.getDefinitionId();
             Definition definition = defService.getById(definitionId);
-            String applyNodeCode = WorkflowUtils.applyNodeCode(definitionId);
+            String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
             FlowParams flowParams = FlowParams.build();
             flowParams.nodeCode(bo.getNodeCode());
             flowParams.message(message);
@@ -394,7 +464,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             Instance instance = insService.getById(inst.getId());
             this.setHandler(instance, task, null);
             // 消息通知
-            WorkflowUtils.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
+            flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
             return true;
         } catch (Exception e) {
             log.error(e.getMessage(), e);
@@ -487,14 +557,52 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         flowTaskVo.setFlowCode(definition.getFlowCode());
         flowTaskVo.setFlowName(definition.getFlowName());
         flowTaskVo.setBusinessId(instance.getBusinessId());
-        List<Node> nodeList = nodeService.getByNodeCodes(Collections.singletonList(flowTaskVo.getNodeCode()), instance.getDefinitionId());
-        if (CollUtil.isNotEmpty(nodeList)) {
-            Node node = nodeList.get(0);
-            flowTaskVo.setNodeRatio(node.getNodeRatio());
+        //设置按钮权限
+        FlowNode flowNode = getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId());
+        if (ObjectUtil.isNull(flowNode)) {
+            throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在");
         }
+        flowTaskVo.setButtonList(flowTaskVo.getButtonList(flowNode.getExt()));
+        flowTaskVo.setNodeRatio(flowNode.getNodeRatio());
+        flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId())));
         return flowTaskVo;
     }
 
+    /**
+     * 获取下一节点信息
+     *
+     * @param bo 参数
+     */
+    @Override
+    public List<FlowNode> getNextNodeList(FlowNextNodeBo bo) {
+        String taskId = bo.getTaskId();
+        Map<String, Object> variables = bo.getVariables();
+        Task task = taskService.getById(taskId);
+        Instance instance = insService.getById(task.getInstanceId());
+        Definition definition = defService.getById(task.getDefinitionId());
+        Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
+        //获取下一节点列表
+        List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable);
+        List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
+        if (CollUtil.isNotEmpty(nextNodeList)) {
+            //构建以下节点数据
+            List<Task> buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, null));
+            //办理人变量替换
+            ExpressionUtil.evalVariable(buildNextTaskList, mergeVariable);
+            for (FlowNode flowNode : nextFlowNodes) {
+                buildNextTaskList.stream().filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())).findFirst().ifPresent(t -> {
+                    if (CollUtil.isNotEmpty(t.getPermissionList())) {
+                        List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageId(String.join(StringUtils.SEPARATOR, t.getPermissionList()));
+                        if (CollUtil.isNotEmpty(users)) {
+                            flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId())));
+                        }
+                    }
+                });
+            }
+        }
+        return nextFlowNodes;
+    }
+
     /**
      * 按照任务id查询任务
      *
@@ -577,10 +685,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
 
         Long taskId = bo.getTaskId();
-        FlowTaskVo flowTaskVo = selectById(taskId);
+        Task task = taskService.getById(taskId);
+        FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
         if ("addSignature".equals(taskOperation) || "reductionSignature".equals(taskOperation)) {
-            if (flowTaskVo.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
-                throw new ServiceException(flowTaskVo.getNodeName() + "不是会签节点!");
+            if (flowNode.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
+                throw new ServiceException(task.getNodeName() + "不是会签节点!");
             }
         }
         // 设置任务状态并执行对应的任务操作
@@ -628,7 +737,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
             // 批量删除现有任务的办理人记录
             if (CollUtil.isNotEmpty(flowTasks)) {
-                WorkflowUtils.getFlowUserService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
+                flwCommonService.getFlowUserService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
                 List<User> userList = flowTasks.stream()
                     .map(flowTask -> {
                         FlowUser flowUser = new FlowUser();
@@ -639,7 +748,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     })
                     .collect(Collectors.toList());
                 if (CollUtil.isNotEmpty(userList)) {
-                    WorkflowUtils.getFlowUserService().saveBatch(userList);
+                    flwCommonService.getFlowUserService().saveBatch(userList);
                 }
             }
         } catch (Exception e) {
@@ -658,7 +767,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     public Map<Long, List<UserDTO>> currentTaskAllUser(List<Long> taskIdList) {
         Map<Long, List<UserDTO>> map = new HashMap<>();
         // 获取与当前任务关联的用户列表
-        List<User> associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList);
+        List<User> associatedUsers = flwCommonService.getFlowUserService().getByAssociateds(taskIdList);
         Map<Long, List<User>> listMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
         for (Map.Entry<Long, List<User>> entry : listMap.entrySet()) {
             List<User> value = entry.getValue();
@@ -678,10 +787,24 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     public List<UserDTO> currentTaskAllUser(Long taskId) {
         // 获取与当前任务关联的用户列表
-        List<User> userList = WorkflowUtils.getFlowUserService().getByAssociateds(Collections.singletonList(taskId));
+        List<User> userList = flwCommonService.getFlowUserService().getByAssociateds(Collections.singletonList(taskId));
         if (CollUtil.isEmpty(userList)) {
             return Collections.emptyList();
         }
         return userService.selectListByIds(StreamUtils.toList(userList, e -> Long.valueOf(e.getProcessedBy())));
     }
+
+    /**
+     * 按照节点编码查询节点
+     *
+     * @param nodeCode     节点编码
+     * @param definitionId 流程定义id
+     */
+    @Override
+    public FlowNode getByNodeCode(String nodeCode, Long definitionId) {
+        return flowNodeMapper.selectOne(new LambdaQueryWrapper<FlowNode>()
+            .eq(FlowNode::getNodeCode, nodeCode)
+            .eq(FlowNode::getDefinitionId, definitionId));
+    }
+
 }

+ 23 - 10
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java

@@ -9,9 +9,9 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.event.ProcessCreateTaskEvent;
 import org.dromara.common.core.domain.event.ProcessDeleteEvent;
 import org.dromara.common.core.domain.event.ProcessEvent;
-import org.dromara.common.core.domain.event.ProcessTaskEvent;
 import org.dromara.common.core.enums.BusinessStatusEnum;
 import org.dromara.common.core.service.WorkflowService;
 import org.dromara.common.core.utils.MapstructUtils;
@@ -47,6 +47,19 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
     private final TestLeaveMapper baseMapper;
     private final WorkflowService workflowService;
 
+    /**
+     * spel条件表达:判断小于2
+     *
+     * @param leaveDays 待判断的变量(可不传自行返回true或false)
+     * @return boolean
+     */
+    public boolean eval(Integer leaveDays) {
+        if (leaveDays < 2) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * 查询请假
      */
@@ -123,7 +136,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
     }
 
     /**
-     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等)
+     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
      * 正常使用只需#processEvent.flowCode=='leave1'
      * 示例为了方便则使用startsWith匹配了全部示例key
      *
@@ -151,19 +164,19 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
     }
 
     /**
-     * 执行办理任务监听
-     * 示例:也可通过  @EventListener(condition = "#processTaskEvent.flowCode=='leave1'")进行判断
+     * 执行任务创建监听
+     * 示例:也可通过  @EventListener(condition = "#processCreateTaskEvent.flowCode=='leave1'")进行判断
      * 在方法中判断流程节点key
-     * if ("xxx".equals(processTaskEvent.getNodeCode())) {
+     * if ("xxx".equals(processCreateTaskEvent.getNodeCode())) {
      * //执行业务逻辑
      * }
      *
-     * @param processTaskEvent 参数
+     * @param processCreateTaskEvent 参数
      */
-    @EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')")
-    public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
-        log.info("当前任务执行了{}", processTaskEvent.toString());
-        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessId()));
+    @EventListener(condition = "#processCreateTaskEvent.flowCode.startsWith('leave')")
+    public void processCreateTaskHandler(ProcessCreateTaskEvent processCreateTaskEvent) {
+        log.info("当前任务创建了{}", processCreateTaskEvent.toString());
+        TestLeave testLeave = baseMapper.selectById(Long.valueOf(processCreateTaskEvent.getBusinessId()));
         testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
         baseMapper.updateById(testLeave);
     }

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml

@@ -31,7 +31,7 @@
                 d.version,
                 uu.processed_by,
                 uu.type
-            from flow_task as t
+            from flow_task t
                     left join flow_user uu on uu.associated = t.id
                     left join flow_definition d on t.definition_id = d.id
                     left join flow_instance i on t.instance_id = i.id

+ 4 - 4
script/docker/docker-compose.yml

@@ -98,7 +98,7 @@ services:
     network_mode: "host"
 
   ruoyi-server1:
-    image: ruoyi/ruoyi-server:5.3.0
+    image: ruoyi/ruoyi-server:5.3.1-BETA
     container_name: ruoyi-server1
     environment:
       # 时区上海
@@ -113,7 +113,7 @@ services:
     network_mode: "host"
 
   ruoyi-server2:
-    image: ruoyi/ruoyi-server:5.3.0
+    image: ruoyi/ruoyi-server:5.3.1-BETA
     container_name: ruoyi-server2
     environment:
       # 时区上海
@@ -128,7 +128,7 @@ services:
     network_mode: "host"
 
   ruoyi-monitor-admin:
-    image: ruoyi/ruoyi-monitor-admin:5.3.0
+    image: ruoyi/ruoyi-monitor-admin:5.3.1-BETA
     container_name: ruoyi-monitor-admin
     environment:
       # 时区上海
@@ -140,7 +140,7 @@ services:
     network_mode: "host"
 
   ruoyi-snailjob-server:
-    image: ruoyi/ruoyi-snailjob-server:5.3.0
+    image: ruoyi/ruoyi-snailjob-server:5.3.1-BETA
     container_name: ruoyi-snailjob-server
     environment:
       # 时区上海

+ 2 - 0
script/docker/nginx/conf/nginx.conf

@@ -14,6 +14,8 @@ http {
     keepalive_timeout  65;
     # 限制body大小
     client_max_body_size 100m;
+    # 开启静态资源压缩
+    gzip_static on;
 
     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                           '$status $body_bytes_sent "$http_referer" '

+ 212 - 0
script/leave/leave6.json

@@ -0,0 +1,212 @@
+{
+  "flowCode" : "leave6",
+  "flowName" : "请假申请-排他并行会签",
+  "category" : "100",
+  "version" : "4",
+  "formCustom" : "N",
+  "formPath" : "/workflow/leaveEdit/index",
+  "nodeList" : [ {
+    "nodeType" : 0,
+    "nodeCode" : "122b89a5-7c6f-40a3-aa09-7a263f902054",
+    "nodeName" : "开始",
+    "nodeRatio" : 0.000,
+    "coordinate" : "240,300|240,300",
+    "formCustom" : "N",
+    "ext" : "[]",
+    "skipList" : [ {
+      "nowNodeCode" : "122b89a5-7c6f-40a3-aa09-7a263f902054",
+      "nextNodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+      "skipType" : "PASS",
+      "coordinate" : "260,300;350,300"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+    "nodeName" : "申请人",
+    "nodeRatio" : 0.000,
+    "coordinate" : "400,300|400,300",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd",
+      "nextNodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+      "skipType" : "PASS",
+      "coordinate" : "450,300;510,300"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+    "nodeName" : "副经理",
+    "permissionFlag" : "role:1@@role:3@@role:4",
+    "nodeRatio" : 0.000,
+    "coordinate" : "860,200|860,200",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+      "nextNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+      "skipType" : "PASS",
+      "coordinate" : "910,200;1000,200;1000,275"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+    "nodeName" : "组长",
+    "permissionFlag" : "1",
+    "nodeRatio" : 0.000,
+    "coordinate" : "860,400|860,400",
+    "formCustom" : "N",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+      "nextNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+      "skipType" : "PASS",
+      "coordinate" : "910,400;1000,400;1000,325"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+    "nodeName" : "副组长",
+    "permissionFlag" : "1",
+    "nodeRatio" : 0.000,
+    "coordinate" : "560,300|560,300",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination,copy,trust,transfer\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742",
+      "nextNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13",
+      "skipType" : "PASS",
+      "coordinate" : "610,300;675,300"
+    } ]
+  }, {
+    "nodeType" : 3,
+    "nodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13",
+    "nodeRatio" : 0.000,
+    "coordinate" : "700,300",
+    "formCustom" : "N",
+    "ext" : "[]",
+    "skipList" : [ {
+      "nowNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13",
+      "nextNodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9",
+      "skipName" : "大于两天",
+      "skipType" : "PASS",
+      "skipCondition" : "default@@${leaveDays > 2}",
+      "coordinate" : "700,275;700,200;810,200|700,237"
+    }, {
+      "nowNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13",
+      "nextNodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60",
+      "skipType" : "PASS",
+      "skipCondition" : "spel@@#{@testLeaveServiceImpl.eval(#leaveDays)}",
+      "coordinate" : "700,325;700,400;810,400"
+    } ]
+  }, {
+    "nodeType" : 3,
+    "nodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1000,300",
+    "formCustom" : "N",
+    "ext" : "[]",
+    "skipList" : [ {
+      "nowNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32",
+      "nextNodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496",
+      "skipType" : "PASS",
+      "coordinate" : "1025,300;1130,300"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496",
+    "nodeName" : "经理会签",
+    "permissionFlag" : "1@@3",
+    "nodeRatio" : 100.000,
+    "coordinate" : "1180,300|1180,300",
+    "formCustom" : "N",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination,pop,addSign,subSign\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496",
+      "nextNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+      "skipType" : "PASS",
+      "coordinate" : "1230,300;1315,300"
+    } ]
+  }, {
+    "nodeType" : 4,
+    "nodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1340,300",
+    "formCustom" : "N",
+    "ext" : "[]",
+    "skipList" : [ {
+      "nowNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+      "nextNodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+      "skipType" : "PASS",
+      "coordinate" : "1340,325;1340,400;1430,400"
+    }, {
+      "nowNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992",
+      "nextNodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+      "skipType" : "PASS",
+      "coordinate" : "1340,275;1340,200;1430,200"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+    "nodeName" : "总经理",
+    "permissionFlag" : "3@@1",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1480,200|1480,200",
+    "formCustom" : "N",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4",
+      "nextNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519",
+      "skipType" : "PASS",
+      "coordinate" : "1530,200;1640,200;1640,275"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+    "nodeName" : "副总经理",
+    "permissionFlag" : "1@@3",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1480,400|1480,400",
+    "formCustom" : "N",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5",
+      "nextNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519",
+      "skipType" : "PASS",
+      "coordinate" : "1530,400;1640,400;1640,325"
+    } ]
+  }, {
+    "nodeType" : 4,
+    "nodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1640,300",
+    "formCustom" : "N",
+    "ext" : "[]",
+    "skipList" : [ {
+      "nowNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519",
+      "nextNodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828",
+      "skipType" : "PASS",
+      "coordinate" : "1665,300;1770,300"
+    } ]
+  }, {
+    "nodeType" : 1,
+    "nodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828",
+    "nodeName" : "董事",
+    "permissionFlag" : "1",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1820,300|1820,300",
+    "formCustom" : "N",
+    "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination\"}]",
+    "skipList" : [ {
+      "nowNodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828",
+      "nextNodeCode" : "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31",
+      "skipType" : "PASS",
+      "coordinate" : "1870,300;1960,300"
+    } ]
+  }, {
+    "nodeType" : 2,
+    "nodeCode" : "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31",
+    "nodeName" : "结束",
+    "nodeRatio" : 0.000,
+    "coordinate" : "1980,300|1980,300",
+    "formCustom" : "N",
+    "ext" : "[]"
+  } ]
+}

+ 4 - 2
script/sql/oracle/oracle_ry_vue_5.X.sql

@@ -75,7 +75,7 @@ create table sys_tenant (
     tenant_id         varchar2(20)  not null,
     contact_user_name varchar2(20)  default '',
     contact_phone     varchar2(20)  default '',
-    company_name      varchar2(50)  default '',
+    company_name      varchar2(30)  default '',
     license_number    varchar2(30)  default '',
     address           varchar2(200) default '',
     intro             varchar2(200) default '',
@@ -352,7 +352,7 @@ comment on column sys_role.tenant_id             is '租户编号';
 comment on column sys_role.role_name             is '角色名称';
 comment on column sys_role.role_key              is '角色权限字符串';
 comment on column sys_role.role_sort             is '显示顺序';
-comment on column sys_role.data_scope            is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)';
+comment on column sys_role.data_scope            is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)';
 comment on column sys_role.menu_check_strictly   is '菜单树选择项是否关联显示';
 comment on column sys_role.dept_check_strictly   is '部门树选择项是否关联显示';
 comment on column sys_role.status                is '角色状态(0正常 1停用)';
@@ -1159,6 +1159,7 @@ create table sys_oss (
   file_suffix     varchar2(10)   not null,
   url             varchar2(500)  not null,
   service         varchar2(20)   default 'minio' not null,
+  ext1            varchar2(500)  default '',
   create_dept     number(20)     default null,
   create_by       number(20)     default null,
   create_time     date,
@@ -1176,6 +1177,7 @@ comment on column sys_oss.original_name     is '原名';
 comment on column sys_oss.file_suffix       is '文件后缀名';
 comment on column sys_oss.url               is 'URL地址';
 comment on column sys_oss.service           is '服务商';
+comment on column sys_oss.ext1              is '扩展字段';
 comment on column sys_oss.create_dept       is '创建部门';
 comment on column sys_oss.create_time       is '创建时间';
 comment on column sys_oss.create_by         is '上传者';

+ 2 - 2
script/sql/oracle/oracle_ry_workflow.sql

@@ -47,7 +47,6 @@ create table FLOW_NODE
     NODE_NAME       VARCHAR2(100),
     NODE_RATIO      NUMBER(6, 3),
     COORDINATE      VARCHAR2(100),
-    SKIP_ANY_NODE   VARCHAR2(100) default 'N',
     ANY_NODE_SKIP   VARCHAR2(100),
     LISTENER_TYPE   VARCHAR2(100),
     LISTENER_PATH   VARCHAR2(500),
@@ -58,6 +57,7 @@ create table FLOW_NODE
     VERSION         VARCHAR2(20),
     CREATE_TIME     DATE,
     UPDATE_TIME     DATE,
+    EXT             VARCHAR2(500),
     DEL_FLAG        VARCHAR2(1)   default '0',
     TENANT_ID       VARCHAR2(40),
     PERMISSION_FLAG VARCHAR2(200)
@@ -73,7 +73,6 @@ comment on column FLOW_NODE.NODE_CODE is '流程节点编码';
 comment on column FLOW_NODE.NODE_NAME is '流程节点名称';
 comment on column FLOW_NODE.NODE_RATIO is '流程签署比例值';
 comment on column FLOW_NODE.COORDINATE is '坐标';
-comment on column FLOW_NODE.SKIP_ANY_NODE is '是否可以退回任意节点(Y是 N否)即将删除';
 comment on column FLOW_NODE.ANY_NODE_SKIP is '任意结点跳转';
 comment on column FLOW_NODE.LISTENER_TYPE is '监听器类型';
 comment on column FLOW_NODE.LISTENER_PATH is '监听器路径';
@@ -84,6 +83,7 @@ comment on column FLOW_NODE.FORM_PATH is '审批表单路径';
 comment on column FLOW_NODE.VERSION is '版本';
 comment on column FLOW_NODE.CREATE_TIME is '创建时间';
 comment on column FLOW_NODE.UPDATE_TIME is '更新时间';
+comment on column FLOW_NODE.EXT is '扩展属性';
 comment on column FLOW_NODE.DEL_FLAG is '删除标志';
 comment on column FLOW_NODE.TENANT_ID is '租户id';
 comment on column FLOW_NODE.PERMISSION_FLAG is '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';

+ 4 - 2
script/sql/postgres/postgres_ry_vue_5.X.sql

@@ -75,7 +75,7 @@ create table if not exists sys_tenant
     tenant_id         varchar(20)   not null,
     contact_user_name varchar(20)   default null::varchar,
     contact_phone     varchar(20)   default null::varchar,
-    company_name      varchar(50)   default null::varchar,
+    company_name      varchar(30)   default null::varchar,
     license_number    varchar(30)   default null::varchar,
     address           varchar(200)  default null::varchar,
     intro             varchar(200)  default null::varchar,
@@ -353,7 +353,7 @@ comment on column sys_role.tenant_id            is '租户编号';
 comment on column sys_role.role_name            is '角色名称';
 comment on column sys_role.role_key             is '角色权限字符串';
 comment on column sys_role.role_sort            is '显示顺序';
-comment on column sys_role.data_scope           is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)';
+comment on column sys_role.data_scope           is '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)';
 comment on column sys_role.menu_check_strictly  is '菜单树选择项是否关联显示';
 comment on column sys_role.dept_check_strictly  is '部门树选择项是否关联显示';
 comment on column sys_role.status               is '角色状态(0正常 1停用)';
@@ -1160,6 +1160,7 @@ create table if not exists sys_oss
     original_name varchar(255) default ''::varchar not null,
     file_suffix   varchar(10)  default ''::varchar not null,
     url           varchar(500) default ''::varchar not null,
+    ext1          varchar(500) default ''::varchar,
     create_dept   int8,
     create_by     int8,
     create_time   timestamp,
@@ -1176,6 +1177,7 @@ comment on column sys_oss.file_name         is '文件名';
 comment on column sys_oss.original_name     is '原名';
 comment on column sys_oss.file_suffix       is '文件后缀名';
 comment on column sys_oss.url               is 'URL地址';
+comment on column sys_oss.ext1              is '扩展字段';
 comment on column sys_oss.create_by         is '上传人';
 comment on column sys_oss.create_dept       is '创建部门';
 comment on column sys_oss.create_time       is '创建时间';

+ 2 - 2
script/sql/postgres/postgres_ry_workflow.sql

@@ -50,7 +50,6 @@ CREATE TABLE flow_node
     permission_flag varchar(200)  NULL,                                 -- 权限标识(权限类型:权限标识,可以多个,用逗号隔开)
     node_ratio      numeric(6, 3) NULL,                                 -- 流程签署比例值
     coordinate      varchar(100)  NULL,                                 -- 坐标
-    skip_any_node   varchar(100)  NULL DEFAULT 'N':: character varying, -- 是否可以退回任意节点(Y是 N否)即将删除
     any_node_skip   varchar(100)  NULL,                                 -- 任意结点跳转
     listener_type   varchar(100)  NULL,                                 -- 监听器类型
     listener_path   varchar(400)  NULL,                                 -- 监听器路径
@@ -61,6 +60,7 @@ CREATE TABLE flow_node
     "version"       varchar(20)   NOT NULL,                             -- 版本
     create_time     timestamp     NULL,                                 -- 创建时间
     update_time     timestamp     NULL,                                 -- 更新时间
+    ext             varchar(500)  NULL,                                 -- 扩展属性
     del_flag        bpchar(1)     NULL DEFAULT '0':: character varying, -- 删除标志
     tenant_id       varchar(40)   NULL,                                 -- 租户id
     CONSTRAINT flow_node_pkey PRIMARY KEY (id)
@@ -75,7 +75,6 @@ COMMENT ON COLUMN flow_node.node_name IS '流程节点名称';
 COMMENT ON COLUMN flow_node.permission_flag IS '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';
 COMMENT ON COLUMN flow_node.node_ratio IS '流程签署比例值';
 COMMENT ON COLUMN flow_node.coordinate IS '坐标';
-COMMENT ON COLUMN flow_node.skip_any_node IS '是否可以退回任意节点(Y是 N否)即将删除';
 COMMENT ON COLUMN flow_node.any_node_skip IS '任意结点跳转';
 COMMENT ON COLUMN flow_node.listener_type IS '监听器类型';
 COMMENT ON COLUMN flow_node.listener_path IS '监听器路径';
@@ -86,6 +85,7 @@ COMMENT ON COLUMN flow_node.form_path IS '审批表单路径';
 COMMENT ON COLUMN flow_node."version" IS '版本';
 COMMENT ON COLUMN flow_node.create_time IS '创建时间';
 COMMENT ON COLUMN flow_node.update_time IS '更新时间';
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
 COMMENT ON COLUMN flow_node.del_flag IS '删除标志';
 COMMENT ON COLUMN flow_node.tenant_id IS '租户id';
 

+ 3 - 2
script/sql/ry_vue_5.X.sql

@@ -45,7 +45,7 @@ create table sys_tenant
     tenant_id         varchar(20)   not null        comment '租户编号',
     contact_user_name varchar(20)                   comment '联系人',
     contact_phone     varchar(20)                   comment '联系电话',
-    company_name      varchar(50)                   comment '企业名称',
+    company_name      varchar(30)                   comment '企业名称',
     license_number    varchar(30)                   comment '统一社会信用代码',
     address           varchar(200)                  comment '地址',
     intro             varchar(200)                  comment '企业简介',
@@ -208,7 +208,7 @@ create table sys_role (
     role_name            varchar(30)     not null                   comment '角色名称',
     role_key             varchar(100)    not null                   comment '角色权限字符串',
     role_sort            int(4)          not null                   comment '显示顺序',
-    data_scope           char(1)         default '1'                comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
+    data_scope           char(1)         default '1'                comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)',
     menu_check_strictly  tinyint(1)      default 1                  comment '菜单树选择项是否关联显示',
     dept_check_strictly  tinyint(1)      default 1                  comment '部门树选择项是否关联显示',
     status               char(1)         not null                   comment '角色状态(0正常 1停用)',
@@ -828,6 +828,7 @@ create table sys_oss (
     original_name   varchar(255) not null default ''        comment '原名',
     file_suffix     varchar(10)  not null default ''        comment '文件后缀名',
     url             varchar(500) not null                   comment 'URL地址',
+    ext1            text                  default null      comment '扩展字段',
     create_dept     bigint(20)            default null      comment '创建部门',
     create_time     datetime              default null      comment '创建时间',
     create_by       bigint(20)            default null      comment '上传人',

+ 2 - 2
script/sql/ry_workflow.sql

@@ -24,7 +24,7 @@ CREATE TABLE `flow_definition`
 
 CREATE TABLE `flow_node`
 (
-    `id`              bigint unsigned NOT NULL COMMENT '主键id',
+    `id`              bigint        NOT NULL COMMENT '主键id',
     `node_type`       tinyint(1)      NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
     `definition_id`   bigint          NOT NULL COMMENT '流程定义id',
     `node_code`       varchar(100)    NOT NULL COMMENT '流程节点编码',
@@ -32,7 +32,6 @@ CREATE TABLE `flow_node`
     `permission_flag` varchar(200)  DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)',
     `node_ratio`      decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值',
     `coordinate`      varchar(100)  DEFAULT NULL COMMENT '坐标',
-    `skip_any_node`   varchar(100)  DEFAULT 'N' COMMENT '是否可以退回任意节点(Y是 N否)即将删除',
     `any_node_skip`   varchar(100)  DEFAULT NULL COMMENT '任意结点跳转',
     `listener_type`   varchar(100)  DEFAULT NULL COMMENT '监听器类型',
     `listener_path`   varchar(400)  DEFAULT NULL COMMENT '监听器路径',
@@ -43,6 +42,7 @@ CREATE TABLE `flow_node`
     `version`         varchar(20)     NOT NULL COMMENT '版本',
     `create_time`     datetime      DEFAULT NULL COMMENT '创建时间',
     `update_time`     datetime      DEFAULT NULL COMMENT '更新时间',
+    `ext`             text          COMMENT '扩展属性',
     `del_flag`        char(1)       DEFAULT '0' COMMENT '删除标志',
     `tenant_id`       varchar(40)   DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE

+ 9 - 2
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql

@@ -222,7 +222,7 @@ CREATE TABLE sys_tenant
     tenant_id             nvarchar(20)                    NOT NULL,
     contact_user_name     nvarchar(20)                    NULL,
     contact_phone         nvarchar(20)                    NULL,
-    company_name          nvarchar(50)                    NULL,
+    company_name          nvarchar(30)                    NULL,
     license_number        nvarchar(30)                    NULL,
     address               nvarchar(200)                   NULL,
     intro                 nvarchar(200)                   NULL,
@@ -2318,7 +2318,7 @@ EXEC sys.sp_addextendedproperty
     'COLUMN', N'role_sort'
 GO
 EXEC sys.sp_addextendedproperty
-    'MS_Description', N'数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)' ,
+    'MS_Description', N'数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)' ,
     'SCHEMA', N'dbo',
     'TABLE', N'sys_role',
     'COLUMN', N'data_scope'
@@ -2923,6 +2923,7 @@ CREATE TABLE sys_oss
     original_name nvarchar(255) DEFAULT ''        NOT NULL,
     file_suffix   nvarchar(10)  DEFAULT ''        NOT NULL,
     url           nvarchar(500)                   NOT NULL,
+    ext1          nvarchar(500) DEFAULT ''        NULL,
     create_dept   bigint                          NULL,
     create_time   datetime2(7)                    NULL,
     create_by     bigint                          NULL,
@@ -2972,6 +2973,12 @@ EXEC sp_addextendedproperty
     'TABLE', N'sys_oss',
     'COLUMN', N'url'
 GO
+EXEC sp_addextendedproperty
+    'MS_Description', N'扩展字段',
+    'SCHEMA', N'dbo',
+    'TABLE', N'sys_oss',
+    'COLUMN', N'ext1'
+GO
 EXEC sys.sp_addextendedproperty
     'MS_Description', N'创建部门' ,
     'SCHEMA', N'dbo',

+ 8 - 8
script/sql/sqlserver/sqlserver_ry_workflow.sql

@@ -149,7 +149,6 @@ CREATE TABLE flow_node (
     permission_flag nvarchar(200) NULL,
     node_ratio decimal(6,3)  NULL,
     coordinate nvarchar(100) NULL,
-    skip_any_node nvarchar(100) DEFAULT('N') NULL,
     any_node_skip nvarchar(100) NULL,
     listener_type nvarchar(100) NULL,
     listener_path nvarchar(400) NULL,
@@ -160,6 +159,7 @@ CREATE TABLE flow_node (
     version nvarchar(20) NOT NULL,
     create_time datetime2(7)  NULL,
     update_time datetime2(7)  NULL,
+    ext nvarchar(500) NULL,
     del_flag nchar(1) DEFAULT('0') NULL,
     tenant_id nvarchar(40) NULL,
     CONSTRAINT PK__flow_nod__3213E83F372470DE PRIMARY KEY CLUSTERED (id)
@@ -225,13 +225,6 @@ EXEC sp_addextendedproperty
 'COLUMN', N'coordinate'
 GO
 
-EXEC sp_addextendedproperty
-'MS_Description', N'是否可以退回任意节点(Y是 N否)即将删除',
-'SCHEMA', N'dbo',
-'TABLE', N'flow_node',
-'COLUMN', N'skip_any_node'
-GO
-
 EXEC sp_addextendedproperty
 'MS_Description', N'任意结点跳转',
 'SCHEMA', N'dbo',
@@ -302,6 +295,13 @@ EXEC sp_addextendedproperty
 'COLUMN', N'update_time'
 GO
 
+EXEC sp_addextendedproperty
+'MS_Description', N'扩展属性',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'ext'
+GO
+
 EXEC sp_addextendedproperty
 'MS_Description', N'删除标志',
 'SCHEMA', N'dbo',

+ 6 - 0
script/sql/update/oracle/update_5.3.0-5.3.1.sql

@@ -0,0 +1,6 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD (ext VARCHAR2(500));
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
+
+ALTER TABLE sys_oss ADD (ext1 VARCHAR2(500));
+COMMENT ON COLUMN sys_oss.ext1 IS '扩展属性';

+ 6 - 0
script/sql/update/postgres/update_5.3.0-5.3.1.sql

@@ -0,0 +1,6 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD COLUMN ext varchar(500);
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
+
+ALTER TABLE sys_oss ADD COLUMN ext1 varchar(500));
+COMMENT ON COLUMN sys_oss.ext1 IS '扩展属性';

+ 18 - 0
script/sql/update/sqlserver/update_5.3.0-5.3.1.sql

@@ -0,0 +1,18 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD ext nvarchar(500) NULL;
+
+EXEC sp_addextendedproperty
+'MS_Description', N'扩展属性',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'ext'
+GO
+
+ALTER TABLE sys_oss ADD ext1 nvarchar(500) NULL;
+
+EXEC sp_addextendedproperty
+'MS_Description', N'扩展属性',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_oss',
+'COLUMN', N'ext1'
+GO

+ 6 - 0
script/sql/update/update_5.3.0-5.3.1.sql

@@ -0,0 +1,6 @@
+ALTER TABLE `flow_node` DROP COLUMN `skip_any_node`;
+ALTER TABLE `flow_node`
+    ADD COLUMN `ext` text NULL COMMENT '扩展属性' AFTER `update_time`;
+
+ALTER TABLE `sys_oss`
+    ADD COLUMN `ext1` text NULL COMMENT '扩展属性' AFTER `url`;