Browse Source

!812 发布 5.5.2 版本 2025年最后一版
Merge pull request !812 from 疯狂的狮子Li/dev

疯狂的狮子Li 6 tháng trước cách đây
mục cha
commit
da4dffcfce
92 tập tin đã thay đổi với 1324 bổ sung612 xóa
  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. 2 1
      README.md
  5. 8 8
      pom.xml
  6. 4 3
      ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
  7. 0 0
      ruoyi-admin/src/main/resources/ip2region_v4.xdb
  8. 1 1
      ruoyi-common/ruoyi-common-bom/pom.xml
  9. 0 3
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
  10. 0 30
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/properties/ThreadPoolProperties.java
  11. 6 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/SystemConstants.java
  12. 2 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java
  13. 82 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java
  14. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java
  15. 78 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
  16. 87 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java
  17. 7 34
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
  18. 113 21
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
  19. 23 0
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java
  20. 20 3
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java
  21. 17 11
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
  22. 10 0
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
  23. 19 0
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java
  24. 3 8
      ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
  25. 1 1
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
  26. 2 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
  27. 129 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java
  28. 34 6
      ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
  29. 6 10
      ruoyi-common/ruoyi-common-redis/pom.xml
  30. 4 3
      ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java
  31. 13 0
      ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
  32. 7 2
      ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
  33. 1 1
      ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
  34. 2 2
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
  35. 11 1
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java
  36. 43 5
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
  37. 2 0
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
  38. 3 1
      ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java
  39. 5 3
      ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java
  40. 4 4
      ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java
  41. 3 4
      ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
  42. 4 8
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java
  43. 2 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
  44. 53 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
  45. 9 2
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
  46. 16 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java
  47. 1 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java
  48. 9 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java
  49. 9 9
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java
  50. 1 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
  51. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java
  52. 0 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java
  53. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowInstanceBo.java
  54. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java
  55. 14 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java
  56. 4 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java
  57. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java
  58. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java
  59. 6 2
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
  60. 8 7
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java
  61. 54 8
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
  62. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java
  63. 1 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
  64. 12 12
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java
  65. 3 0
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
  66. 17 12
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
  67. 5 4
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
  68. 60 91
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
  69. 12 12
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java
  70. 143 144
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
  71. 5 1
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
  72. 15 25
      ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
  73. 4 1
      ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
  74. 4 4
      script/docker/docker-compose.yml
  75. 0 5
      script/docker/nginx/conf/nginx.conf
  76. 1 1
      script/sql/oracle/oracle_ry_job.sql
  77. 8 12
      script/sql/oracle/oracle_ry_workflow.sql
  78. 1 1
      script/sql/postgres/postgres_ry_job.sql
  79. 6 10
      script/sql/postgres/postgres_ry_workflow.sql
  80. 1 1
      script/sql/ry_job.sql
  81. 1 3
      script/sql/ry_workflow.sql
  82. 1 1
      script/sql/sqlserver/sqlserver_ry_job.sql
  83. 7 23
      script/sql/sqlserver/sqlserver_ry_workflow.sql
  84. 5 5
      script/sql/update/oracle/update_5.4.1-5.5.0.sql
  85. 4 0
      script/sql/update/oracle/update_5.5.1-5.5.2.sql
  86. 5 5
      script/sql/update/postgres/update_5.4.1-5.5.0.sql
  87. 10 10
      script/sql/update/postgres/update_5.5.0-5.5.1.sql
  88. 6 0
      script/sql/update/postgres/update_5.5.1-5.5.2.sql
  89. 5 5
      script/sql/update/sqlserver/update_5.4.1-5.5.0.sql
  90. 22 0
      script/sql/update/sqlserver/update_5.5.1-5.5.2.sql
  91. 5 5
      script/sql/update/update_5.4.1-5.5.0.sql
  92. 3 0
      script/sql/update/update_5.5.1-5.5.2.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.5.1" />
+        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.2" />
         <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.5.1" />
+        <option name="imageTag" value="ruoyi/ruoyi-server:5.5.2" />
         <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.5.1" />
+        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.2" />
         <option name="buildOnly" value="true" />
         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
       </settings>

+ 2 - 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/5.X/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.5.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.5.2-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)]()
@@ -39,6 +39,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
 aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
 Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
+Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
 
 [如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
 

+ 8 - 8
pom.xml

@@ -13,13 +13,13 @@
     <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 
     <properties>
-        <revision>5.5.1</revision>
-        <spring-boot.version>3.5.7</spring-boot.version>
+        <revision>5.5.2</revision>
+        <spring-boot.version>3.5.9</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.13</springdoc.version>
+        <springdoc.version>2.8.14</springdoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
         <fastexcel.version>1.3.0</fastexcel.version>
         <velocity.version>2.3</velocity.version>
@@ -28,17 +28,17 @@
         <p6spy.version>3.9.1</p6spy.version>
         <hutool.version>5.8.40</hutool.version>
         <spring-boot-admin.version>3.5.5</spring-boot-admin.version>
-        <redisson.version>3.51.0</redisson.version>
+        <redisson.version>3.52.0</redisson.version>
         <lock4j.version>2.2.7</lock4j.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
-        <snailjob.version>1.8.0</snailjob.version>
+        <snailjob.version>1.9.0</snailjob.version>
         <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
         <lombok.version>1.18.40</lombok.version>
         <bouncycastle.version>1.80</bouncycastle.version>
         <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
-        <ip2region.version>2.7.0</ip2region.version>
+        <ip2region.version>3.3.1</ip2region.version>
         <!-- OSS 配置 -->
         <aws.sdk.version>2.28.22</aws.sdk.version>
         <!-- SMS 配置 -->
@@ -46,9 +46,9 @@
         <!-- 限制框架中的fastjson版本 -->
         <fastjson.version>1.2.83</fastjson.version>
         <!-- 面向运行时的D-ORM依赖 -->
-        <anyline.version>8.7.2-20250603</anyline.version>
+        <anyline.version>8.7.3-20251210</anyline.version>
         <!-- 工作流配置 -->
-        <warm-flow.version>1.8.2</warm-flow.version>
+        <warm-flow.version>1.8.4</warm-flow.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>

+ 4 - 3
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java

@@ -48,6 +48,7 @@ import org.springframework.web.bind.annotation.*;
 
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -106,7 +107,7 @@ public class AuthController {
         Long userId = LoginHelper.getUserId();
         scheduledExecutorService.schedule(() -> {
             SseMessageDto dto = new SseMessageDto();
-            dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
+            dto.setMessage(DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Vue-Plus 后台管理系统");
             dto.setUserIds(List.of(userId));
             SseMessageUtils.publishMessage(dto);
         }, 5, TimeUnit.SECONDS);
@@ -147,8 +148,8 @@ public class AuthController {
         StpUtil.checkLogin();
         // 获取第三方登录信息
         AuthResponse<AuthUser> response = SocialUtils.loginAuth(
-                loginBody.getSource(), loginBody.getSocialCode(),
-                loginBody.getSocialState(), socialProperties);
+            loginBody.getSource(), loginBody.getSocialCode(),
+            loginBody.getSocialState(), socialProperties);
         AuthUser authUserData = response.getData();
         // 判断授权响应是否成功
         if (!response.ok()) {

+ 0 - 0
ruoyi-admin/src/main/resources/ip2region.xdb → ruoyi-admin/src/main/resources/ip2region_v4.xdb


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

@@ -14,7 +14,7 @@
     </description>
 
     <properties>
-        <revision>5.5.1</revision>
+        <revision>5.5.2</revision>
     </properties>
 
     <dependencyManagement>

+ 0 - 3
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java

@@ -3,10 +3,8 @@ package org.dromara.common.core.config;
 import jakarta.annotation.PreDestroy;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
-import org.dromara.common.core.config.properties.ThreadPoolProperties;
 import org.dromara.common.core.utils.SpringUtils;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.task.VirtualThreadTaskExecutor;
 
@@ -19,7 +17,6 @@ import java.util.concurrent.*;
  **/
 @Slf4j
 @AutoConfiguration
-@EnableConfigurationProperties(ThreadPoolProperties.class)
 public class ThreadPoolConfig {
 
     /**

+ 0 - 30
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/properties/ThreadPoolProperties.java

@@ -1,30 +0,0 @@
-package org.dromara.common.core.config.properties;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * 线程池 配置属性
- *
- * @author Lion Li
- */
-@Data
-@ConfigurationProperties(prefix = "thread-pool")
-public class ThreadPoolProperties {
-
-    /**
-     * 是否开启线程池
-     */
-    private boolean enabled;
-
-    /**
-     * 队列最大长度
-     */
-    private int queueCapacity;
-
-    /**
-     * 线程池维护线程所允许的空闲时间
-     */
-    private int keepAliveSeconds;
-
-}

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

@@ -82,4 +82,10 @@ public interface SystemConstants {
      */
     Long DEFAULT_DEPT_ID = 100L;
 
+    /**
+     * 排除敏感属性字段
+     */
+    String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
+
+
 }

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

@@ -67,7 +67,8 @@ public class CompleteTaskDTO implements Serializable {
 
     public Map<String, Object> getVariables() {
         if (variables == null) {
-            return new HashMap<>(16);
+            variables = new HashMap<>(16);
+            return variables;
         }
         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
         return variables;

+ 82 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/ConfigService.java

@@ -1,5 +1,11 @@
 package org.dromara.common.core.service;
 
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+
+import java.math.BigDecimal;
+import java.util.List;
+
 /**
  * 通用 参数配置服务
  *
@@ -15,4 +21,80 @@ public interface ConfigService {
      */
     String getConfigValue(String configKey);
 
+    /**
+     * 根据参数 key 获取布尔值
+     *
+     * @param configKey 参数 key
+     * @return Boolean 值
+     */
+    default Boolean getConfigBool(String configKey) {
+        return Convert.toBool(getConfigValue(configKey));
+    }
+
+    /**
+     * 根据参数 key 获取整数值
+     *
+     * @param configKey 参数 key
+     * @return Integer 值
+     */
+    default Integer getConfigInt(String configKey) {
+        return Convert.toInt(getConfigValue(configKey));
+    }
+
+    /**
+     * 根据参数 key 获取长整型值
+     *
+     * @param configKey 参数 key
+     * @return Long 值
+     */
+    default Long getConfigLong(String configKey) {
+        return Convert.toLong(getConfigValue(configKey));
+    }
+
+    /**
+     * 根据参数 key 获取 BigDecimal 值
+     *
+     * @param configKey 参数 key
+     * @return BigDecimal 值
+     */
+    default BigDecimal getConfigDecimal(String configKey) {
+        return Convert.toBigDecimal(getConfigValue(configKey));
+    }
+
+    /**
+     * 根据参数 key 获取 Map 类型的配置
+     *
+     * @param configKey 参数 key
+     * @return Dict 对象,如果配置为空或无法解析,返回空 Dict
+     */
+    Dict getConfigMap(String configKey);
+
+    /**
+     * 根据参数 key 获取 Map 类型的配置列表
+     *
+     * @param configKey 参数 key
+     * @return Dict 列表,如果配置为空或无法解析,返回空列表
+     */
+    List<Dict> getConfigArrayMap(String configKey);
+
+    /**
+     * 根据参数 key 获取指定类型的配置对象
+     *
+     * @param configKey 参数 key
+     * @param clazz     目标对象类型
+     * @param <T>       目标对象泛型
+     * @return 对象实例,如果配置为空或无法解析,返回 null
+     */
+    <T> T getConfigObject(String configKey, Class<T> clazz);
+
+    /**
+     * 根据参数 key 获取指定类型的配置列表
+     *
+     * @param configKey 参数 key
+     * @param clazz     目标元素类型
+     * @param <T>       元素类型泛型
+     * @return 指定类型列表,如果配置为空或无法解析,返回空列表
+     */
+    <T> List<T> getConfigArray(String configKey, Class<T> clazz);
+
 }

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java

@@ -20,7 +20,7 @@ public interface WorkflowService {
      * @param businessIds 业务id
      * @return 结果
      */
-    boolean deleteInstance(List<Long> businessIds);
+    boolean deleteInstance(List<String> businessIds);
 
     /**
      * 获取当前流程状态

+ 78 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java

@@ -1,5 +1,7 @@
 package org.dromara.common.core.utils;
 
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.dromara.common.core.enums.FormatsType;
 import org.dromara.common.core.exception.ServiceException;
@@ -297,4 +299,80 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
         }
     }
 
+    /**
+     * 根据指定日期时间获取时间段(凌晨 / 上午 / 中午 / 下午 / 晚上)
+     *
+     * @param date 日期时间
+     * @return 时间段描述
+     */
+    public static String getTodayHour(Date date) {
+        int hour = DateUtil.hour(date, true);
+        if (hour <= 6) {
+            return "凌晨";
+        } else if (hour < 12) {
+            return "上午";
+        } else if (hour == 12) {
+            return "中午";
+        } else if (hour <= 18) {
+            return "下午";
+        } else {
+            return "晚上";
+        }
+    }
+
+    /**
+     * 将日期格式化为仿微信的友好时间
+     * <p>
+     * 规则说明:
+     * 1. 未来时间:yyyy-MM-dd HH:mm
+     * 2. 今天:
+     * - 1 分钟内:刚刚
+     * - 1 小时内:X 分钟前
+     * - 超过 1 小时:凌晨/上午/中午/下午/晚上 HH:mm
+     * 3. 昨天:昨天 HH:mm
+     * 4. 本周:周X HH:mm
+     * 5. 今年内:MM-dd HH:mm
+     * 6. 非今年:yyyy-MM-dd HH:mm
+     *
+     * @param date 日期时间
+     * @return 格式化后的时间描述
+     */
+    public static String formatFriendlyTime(Date date) {
+        if (date == null) {
+            return "";
+        }
+        Date now = DateUtil.date();
+
+        // 未来时间或非今年
+        if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) {
+            return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date);
+        }
+
+        // 今天
+        if (DateUtil.isSameDay(date, now)) {
+            long minutes = DateUtil.between(date, now, DateUnit.MINUTE);
+            if (minutes < 1) {
+                return "刚刚";
+            }
+            if (minutes < 60) {
+                return minutes + "分钟前";
+            }
+            return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
+        }
+
+        // 昨天
+        if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
+            return "昨天 " + DateUtil.format(date, "HH:mm");
+        }
+
+        // 本周
+        if (DateUtil.isSameWeek(date, now, true)) {
+            return DateUtil.dayOfWeekEnum(date).toChinese("周")
+                + " " + DateUtil.format(date, "HH:mm");
+        }
+
+        // 今年内其它时间
+        return DateUtil.format(date, "MM-dd HH:mm");
+    }
+
 }

+ 87 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DesensitizedUtils.java

@@ -0,0 +1,87 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.util.DesensitizedUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 脱敏工具类
+ *
+ * @author AprilWind
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DesensitizedUtils extends DesensitizedUtil {
+
+    /**
+     * 灵活脱敏方法
+     *
+     * @param value         原始字符串
+     * @param prefixVisible 前面可见长度
+     * @param suffixVisible 后面可见长度
+     * @param maskLength    中间掩码长度(固定显示多少 *,如果总长度不足则自动缩减)
+     * @return 脱敏后字符串
+     */
+    public static String mask(String value, int prefixVisible, int suffixVisible, int maskLength) {
+        if (StrUtil.isBlank(value)) {
+            return value;
+        }
+
+        int len = value.length();
+        int prefixMaskLimit = prefixVisible + maskLength;
+        int fullLimit = prefixMaskLimit + suffixVisible;
+
+        // 规则 1:长度 <= 中间掩码长度 → 全掩码
+        if (len <= maskLength) {
+            return StrUtil.repeat('*', len);
+        }
+        String mask = StrUtil.repeat('*', maskLength);
+
+        // 规则 2:长度 <= 前缀 + 中间掩码
+        if (len <= prefixMaskLimit) {
+            return value.substring(0, len - maskLength) + mask;
+        }
+
+        String prefix = value.substring(0, prefixVisible);
+
+        // 规则 3:长度 <= 前缀 + 中间掩码 + 后缀
+        if (len <= fullLimit) {
+            int suffixLen = len - prefixMaskLimit;
+            return prefix + mask + value.substring(len - suffixLen);
+        }
+
+        // 规则 4:标准形态
+        return prefix + mask + value.substring(len - suffixVisible);
+    }
+
+    /**
+     * 高安全级别脱敏方法(Token / 私钥)
+     *
+     * @param value         原始字符串
+     * @param prefixVisible 前面可见长度(推荐0~4)
+     * @param suffixVisible 后面可见长度(推荐0~4)
+     * @return 脱敏后字符串
+     */
+    public static String maskHighSecurity(String value, int prefixVisible, int suffixVisible) {
+        if (StrUtil.isBlank(value)) {
+            return value;
+        }
+        int len = value.length();
+
+        // 规则1:长度 <= 前缀可见长度 → 全部掩码
+        if (len <= prefixVisible) {
+            return StrUtil.repeat('*', len);
+        }
+
+        // 规则2:长度 <= 前缀 + 后缀可见长度 → 优先掩码后面
+        if (len <= prefixVisible + suffixVisible) {
+            return value.substring(0, len - prefixVisible) + StrUtil.repeat('*', prefixVisible);
+        }
+
+        // 规则3:标准形态 → 前后可见,中间全部掩码
+        return value.substring(0, prefixVisible)
+            + StrUtil.repeat('*', len - prefixVisible - suffixVisible)
+            + value.substring(len - suffixVisible);
+    }
+
+}

+ 7 - 34
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java

@@ -20,51 +20,24 @@ public class AddressUtils {
     public static final String UNKNOWN_IP = "XX XX";
     // 内网地址
     public static final String LOCAL_ADDRESS = "内网IP";
-    // 未知地址
-    public static final String UNKNOWN_ADDRESS = "未知";
 
     public static String getRealAddressByIP(String ip) {
         // 处理空串并过滤HTML标签
         ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
         // 判断是否为IPv4
-        if (NetUtils.isIPv4(ip)) {
-            return resolverIPv4Region(ip);
-        }
+        boolean isIPv4 = NetUtils.isIPv4(ip);
         // 判断是否为IPv6
-        if (NetUtils.isIPv6(ip)) {
-            return resolverIPv6Region(ip);
-        }
+        boolean isIPv6 = NetUtils.isIPv6(ip);
         // 如果不是IPv4或IPv6,则返回未知IP
-        return UNKNOWN_IP;
-    }
-
-    /**
-     * 根据IPv4地址查询IP归属行政区域
-     * @param ip ipv4地址
-     * @return 归属行政区域
-     */
-    private static String resolverIPv4Region(String ip){
-        // 内网不查询
-        if (NetUtils.isInnerIP(ip)) {
-            return LOCAL_ADDRESS;
+        if (!isIPv4 && !isIPv6) {
+            return UNKNOWN_IP;
         }
-        return RegionUtils.getCityInfo(ip);
-    }
-
-    /**
-     * 根据IPv6地址查询IP归属行政区域
-     * @param ip ipv6地址
-     * @return 归属行政区域
-     */
-    private static String resolverIPv6Region(String ip){
         // 内网不查询
-        if (NetUtils.isInnerIPv6(ip)) {
+        if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) {
             return LOCAL_ADDRESS;
         }
-        log.warn("ip2region不支持IPV6地址解析:{}", ip);
-        // 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
-        // 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
-        return UNKNOWN_ADDRESS;
+        // Tips:Ip2Region 提供了精简的IPv6地址库,精简的IPv6地址库并不能完全支持IPv6地址的查询,且准确度上可能会存在问题,如需要准确的IPv6地址查询,建议自行实现
+        return RegionUtils.getRegion(ip);
     }
 
 }

+ 113 - 21
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java

@@ -1,50 +1,142 @@
 package org.dromara.common.core.utils.ip;
 
-import cn.hutool.core.io.resource.NoResourceException;
 import cn.hutool.core.io.resource.ResourceUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.StringUtils;
-import org.lionsoul.ip2region.xdb.Searcher;
+import org.lionsoul.ip2region.service.Config;
+import org.lionsoul.ip2region.service.Ip2Region;
+import org.lionsoul.ip2region.xdb.Util;
+
+import java.io.InputStream;
+import java.time.Duration;
 
 /**
- * 根据ip地址定位工具类,离线方式
- * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
+ * IP地址行政区域工具类
+ * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">ip2region xdb java 查询客户端实现</a>
+ * xdb数据库文件下载:<a href="https://gitee.com/lionsoul/ip2region/tree/master/data">ip2region data</a>
  *
- * @author lishuyan
+ * @author 秋辞未寒
  */
 @Slf4j
 public class RegionUtils {
 
-    // IP地址库文件名称
-    public static final String IP_XDB_FILENAME = "ip2region.xdb";
+    // 默认IPv4地址库文件路径
+    // 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb
+    public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb";
+
+    // 默认IPv6地址库文件路径
+    // 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
+    public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
 
-    private static final Searcher SEARCHER;
+    // 未知地址
+    public static final String UNKNOWN_ADDRESS = "未知";
 
+    // Ip2Region服务实例
+    private static Ip2Region ip2Region;
+
+    // 初始化Ip2Region服务实例
     static {
         try {
-            // 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
-            // 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
-            SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
-            log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
-        } catch (NoResourceException e) {
-            throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
+            // 注意:Ip2Region 的xdb文件加载策略 CachePolicy 有三种,分别是:BufferCache(全量读取xdb到内存中)、VIndexCache(默认策略,按需读取并缓存)、NoCache(实时读取)
+            // 本项目工具使用的 CachePolicy 为 BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
+            // 因为加载整个xdb文件会耗费非常大的内存,如果你不希望加载整个xdb到内存中,更推荐使用 VIndexCache 或 NoCache(即实时读取文件)策略和 setXdbPath/setXdbFile 加载方法(需要注意的一点,setXdbPath 和 setXdbFile 不支持读取ClassPath(即源码和resource目录)中的文件)。
+            // 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 NoCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
+
+            // IPv4配置
+            Config v4Config = Config.custom()
+                .setCachePolicy(Config.BufferCache)
+                .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
+                .asV4();
+
+            // IPv6配置
+            Config v6Config = null;
+            InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
+            if (v6XdbInputStream == null) {
+                log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
+            } else {
+                v6Config = Config.custom()
+                    .setCachePolicy(Config.BufferCache)
+                    .setXdbInputStream(v6XdbInputStream)
+                    .asV6();
+            }
+
+            // 初始化Ip2Region实例
+            RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
+            log.debug("IP工具初始化成功,加载IP地址库数据成功!");
+        } catch (Exception e) {
+            throw new ServiceException("RegionUtils初始化失败,原因:{}", e.getMessage());
+        }
+    }
+
+    /**
+     * 根据IP地址离线获取城市
+     *
+     * @param ipString ip地址字符串
+     */
+    public static String getRegion(String ipString) {
+        try {
+            String region = ip2Region.search(ipString);
+            if (StringUtils.isBlank(region)) {
+                region = UNKNOWN_ADDRESS;
+            }
+            return region;
         } catch (Exception e) {
-            throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
+            log.error("IP地址离线获取城市异常 {}", ipString);
+            return UNKNOWN_ADDRESS;
         }
+
     }
 
     /**
      * 根据IP地址离线获取城市
+     *
+     * @param ipBytes ip地址字节数组
+     */
+    public static String getRegion(byte[] ipBytes) {
+        try {
+            String region = ip2Region.search(ipBytes);
+            if (StringUtils.isBlank(region)) {
+                region = UNKNOWN_ADDRESS;
+            }
+            return region;
+        } catch (Exception e) {
+            log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes));
+            return UNKNOWN_ADDRESS;
+        }
+    }
+
+    /**
+     * 关闭Ip2Region服务
+     */
+    public static void close() {
+        if (ip2Region == null) {
+            return;
+        }
+        try {
+            ip2Region.close(10000);
+        } catch (Exception e) {
+            log.error("Ip2Region服务关闭异常", e);
+        }
+    }
+
+    /**
+     * 关闭Ip2Region服务
+     *
+     * @param timeout 关闭超时时间
      */
-    public static String getCityInfo(String ip) {
+    public static void close(final Duration timeout) {
+        if (ip2Region == null) {
+            return;
+        }
+        if (timeout == null) {
+            close();
+            return;
+        }
         try {
-            // 3、执行查询
-            String region = SEARCHER.search(StringUtils.trim(ip));
-            return region.replace("0|", "").replace("|0", "");
+            ip2Region.close(timeout.toMillis());
         } catch (Exception e) {
-            log.error("IP地址离线获取城市异常 {}", ip);
-            return "未知";
+            log.error("Ip2Region服务关闭异常", e);
         }
     }
 

+ 23 - 0
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDynamicOptions.java

@@ -0,0 +1,23 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.excel.core.ExcelOptionsProvider;
+
+import java.lang.annotation.*;
+
+/**
+ * Excel动态下拉选项注解
+ *
+ * @author Angus
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDynamicOptions {
+
+    /**
+     * 提供者类全限定名
+     * <p>
+     * {@link org.dromara.common.excel.core.ExcelOptionsProvider} 接口实现类 class
+     */
+    Class<? extends ExcelOptionsProvider> providerClass();
+}

+ 20 - 3
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java

@@ -29,7 +29,10 @@ public class CellMergeHandler {
         // 行合并开始下标
         this.rowIndex = hasTitle ? 1 : 0;
     }
-
+    private CellMergeHandler(final boolean hasTitle, final int rowIndex) {
+        this.hasTitle = hasTitle;
+        this.rowIndex = hasTitle ? rowIndex : 0;
+    }
     @SneakyThrows
     public List<CellRangeAddress> handle(List<?> rows) {
         // 如果入参为空集合则返回空集
@@ -103,6 +106,10 @@ public class CellMergeHandler {
                 }
 
                 if (isAddResult && i > current) {
+                    //如果是同一行,则跳过合并
+                    if (current + rowIndex == lastRow) {
+                        continue;
+                    }
                     result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
                 }
             }
@@ -147,12 +154,12 @@ public class CellMergeHandler {
     private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
         final String[] mergeBy = cellMerge.mergeBy();
         if (StrUtil.isAllNotBlank(mergeBy)) {
-            //比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
+            // 比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
             for (String fieldName : mergeBy) {
                 final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
                 final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
                 if (!Objects.equals(valPre, valCurrent)) {
-                    //依赖字段如有任一不等值,则标记为不可合并
+                    // 依赖字段如有任一不等值,则标记为不可合并
                     return false;
                 }
             }
@@ -177,6 +184,16 @@ public class CellMergeHandler {
             return new FieldColumnIndex(colIndex, cellMerge);
         }
     }
+    /**
+     * 创建一个单元格合并处理器实例
+     *
+     * @param hasTitle 是否合并标题
+     * @param rowIndex 行索引
+     * @return 单元格合并处理器
+     */
+    public static CellMergeHandler of(final boolean hasTitle, final int rowIndex) {
+        return new CellMergeHandler(hasTitle, rowIndex);
+    }
 
     /**
      * 创建一个单元格合并处理器实例

+ 17 - 11
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java

@@ -2,15 +2,16 @@ package org.dromara.common.excel.core;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.idev.excel.metadata.Head;
-import cn.idev.excel.write.handler.WorkbookWriteHandler;
-import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
+import cn.idev.excel.write.handler.SheetWriteHandler;
 import cn.idev.excel.write.merge.AbstractMergeStrategy;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.util.CellRangeAddress;
 
-import java.util.*;
+import java.util.List;
 
 /**
  * 列值重复合并策略
@@ -18,7 +19,7 @@ import java.util.*;
  * @author Lion Li
  */
 @Slf4j
-public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
+public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWriteHandler {
 
     private final List<CellRangeAddress> cellList;
 
@@ -30,29 +31,34 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
         this.cellList = CellMergeHandler.of(hasTitle).handle(list);
     }
 
+    public CellMergeStrategy(List<?> list, boolean hasTitle, int rowIndex) {
+        this.cellList = CellMergeHandler.of(hasTitle, rowIndex).handle(list);
+    }
+
     @Override
     protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
-        if (CollUtil.isEmpty(cellList)){
+        if (CollUtil.isEmpty(cellList)) {
             return;
         }
-        //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
+        // 单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
         final int rowIndex = cell.getRowIndex();
         for (CellRangeAddress cellAddresses : cellList) {
             final int firstRow = cellAddresses.getFirstRow();
-            if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
+            if (cellAddresses.isInRange(cell) && rowIndex != firstRow) {
                 cell.setBlank();
             }
         }
     }
 
     @Override
-    public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
-        if (CollUtil.isEmpty(cellList)){
+    public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
+        if (CollUtil.isEmpty(cellList)) {
             return;
         }
-        //当前表格写完后,统一写入
+        // 在 Sheet 创建时提前写入合并区域;后续写入只会影响首格,不会移除合并
+        final Sheet sheet = writeSheetHolder.getSheet();
         for (CellRangeAddress item : cellList) {
-            context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
+            sheet.addMergedRegion(item);
         }
     }
 

+ 10 - 0
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java

@@ -23,6 +23,7 @@ import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.annotation.ExcelDynamicOptions;
 import org.dromara.common.excel.annotation.ExcelEnumFormat;
 
 import java.lang.reflect.Field;
@@ -117,6 +118,15 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
                 List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
                 options = StreamUtils.toList(values, Convert::toStr);
+            } else if (field.isAnnotationPresent(ExcelDynamicOptions.class)) {
+                // 处理动态下拉选项
+                ExcelDynamicOptions dynamicOptions = field.getDeclaredAnnotation(ExcelDynamicOptions.class);
+                // 获取提供者实例
+                ExcelOptionsProvider provider = SpringUtils.getBean(dynamicOptions.providerClass());
+                Set<String> providerOptions = provider.getOptions();
+                if (CollUtil.isNotEmpty(providerOptions)) {
+                    options = new ArrayList<>(providerOptions);
+                }
             }
             if (ObjectUtil.isNotEmpty(options)) {
                 // 仅当下拉可选项不为空时执行

+ 19 - 0
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelOptionsProvider.java

@@ -0,0 +1,19 @@
+package org.dromara.common.excel.core;
+
+import java.util.Set;
+
+/**
+ * Excel下拉选项数据提供接口
+ *
+ * @author Angus
+ */
+public interface ExcelOptionsProvider {
+
+    /**
+     * 获取下拉选项数据
+     *
+     * @return 下拉选项列表
+     */
+    Set<String> getOptions();
+
+}

+ 3 - 8
ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java

@@ -13,6 +13,7 @@ import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.AfterThrowing;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
+import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.domain.model.LoginUser;
 import org.dromara.common.core.utils.ServletUtils;
 import org.dromara.common.core.utils.SpringUtils;
@@ -39,12 +40,6 @@ import java.util.*;
 @AutoConfiguration
 public class LogAspect {
 
-    /**
-     * 排除敏感属性字段
-     */
-    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
-
-
     /**
      * 计时 key
      */
@@ -160,7 +155,7 @@ public class LogAspect {
             String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
             operLog.setOperParam(StringUtils.substring(params, 0, 3800));
         } else {
-            MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
+            MapUtil.removeAny(paramsMap, SystemConstants.EXCLUDE_PROPERTIES);
             MapUtil.removeAny(paramsMap, excludeParamNames);
             operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
         }
@@ -174,7 +169,7 @@ public class LogAspect {
         if (ArrayUtil.isEmpty(paramsArray)) {
             return params.toString();
         }
-        String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
+        String[] exclude = ArrayUtil.addAll(excludeParamNames, SystemConstants.EXCLUDE_PROPERTIES);
         for (Object o : paramsArray) {
             if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                 String str = "";

+ 1 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java

@@ -42,7 +42,7 @@ public class DataBaseHelper {
             String databaseProductName = metaData.getDatabaseProductName();
             return DataBaseType.find(databaseProductName);
         } catch (SQLException e) {
-            throw new ServiceException(e.getMessage());
+            throw new RuntimeException("获取数据库类型失败", e);
         }
     }
 

+ 2 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java

@@ -112,7 +112,7 @@ public class DataPermissionHelper {
     /**
      * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
      */
-    public static void enableIgnore() {
+    private static void enableIgnore() {
         IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
         if (ObjectUtil.isNull(ignoreStrategy)) {
             InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
@@ -126,7 +126,7 @@ public class DataPermissionHelper {
     /**
      * 关闭忽略数据权限
      */
-    public static void disableIgnore() {
+    private static void disableIgnore() {
         IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
         if (ObjectUtil.isNotNull(ignoreStrategy)) {
             boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())

+ 129 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/utils/IdGeneratorUtil.java

@@ -0,0 +1,129 @@
+package org.dromara.common.mybatis.utils;
+
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+
+/**
+ * ID 生成工具类
+ *
+ * @author AprilWind
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class IdGeneratorUtil {
+
+    private static final IdentifierGenerator GENERATOR = SpringUtils.getBean(IdentifierGenerator.class);
+
+    /**
+     * 生成字符串类型主键 ID
+     * <p>
+     * 调用 {@link IdentifierGenerator#nextId(Object)},返回 String 格式 ID。
+     * </p>
+     *
+     * @return 字符串格式主键 ID
+     */
+    public static String nextId() {
+        return GENERATOR.nextId(null).toString();
+    }
+
+    /**
+     * 生成 Long 类型主键 ID
+     * <p>
+     * 自动将生成的数字型主键转换为 Long 类型
+     * </p>
+     *
+     * @return Long 类型主键 ID
+     */
+    public static Long nextLongId() {
+        return GENERATOR.nextId(null).longValue();
+    }
+
+    /**
+     * 生成 Number 类型主键 ID
+     * <p>
+     * 推荐在需要保留原始 Number 类型时使用
+     * </p>
+     *
+     * @return Number 类型主键 ID
+     */
+    public static Number nextNumberId() {
+        return GENERATOR.nextId(null);
+    }
+
+    /**
+     * 根据实体生成数字型主键 ID
+     * <p>
+     * 若自定义的 {@link IdentifierGenerator} 根据实体内容生成 ID,则可以使用本方法
+     * </p>
+     *
+     * @param entity 实体对象
+     * @return Number 类型主键 ID
+     */
+    public static Number nextId(Object entity) {
+        return GENERATOR.nextId(entity);
+    }
+
+    /**
+     * 根据实体生成字符串主键 ID
+     * <p>
+     * 与 {@link #nextId(Object)} 类似,但返回 String 类型
+     * </p>
+     *
+     * @param entity 实体对象
+     * @return 字符串格式主键 ID
+     */
+    public static String nextStringId(Object entity) {
+        return GENERATOR.nextId(entity).toString();
+    }
+
+    /**
+     * 生成 32 位 UUID
+     * <p>
+     * 底层使用 {@link IdWorker#get32UUID()}
+     * </p>
+     *
+     * @return 32 位 UUID 字符串
+     */
+    public static String nextUUID() {
+        return IdWorker.get32UUID();
+    }
+
+    /**
+     * 根据实体生成 32 位 UUID
+     * <p>
+     * 默认 {@link IdentifierGenerator#nextUUID(Object)} 实现忽略实体,但保留该方法便于扩展。
+     * </p>
+     *
+     * @param entity 实体对象
+     * @return 32 位 UUID 字符串
+     */
+    public static String nextUUID(Object entity) {
+        return GENERATOR.nextUUID(entity);
+    }
+
+    /**
+     * 生成带指定前缀的字符串主键 ID
+     * <p>
+     * 示例:prefix = "ORD",生成结果形如:{@code ORD20251211000123}
+     * </p>
+     *
+     * @param prefix 自定义前缀
+     * @return 带前缀的字符串主键 ID
+     */
+    public static String nextIdWithPrefix(String prefix) {
+        return prefix + nextId();
+    }
+
+    /**
+     * 生成带前缀的 UUID
+     *
+     * @param prefix 前缀
+     * @return prefix + UUID
+     */
+    public static String nextUUIDWithPrefix(String prefix) {
+        return prefix + nextUUID();
+    }
+
+}

+ 34 - 6
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java

@@ -14,7 +14,9 @@ import org.dromara.common.oss.exception.OssException;
 import org.dromara.common.oss.properties.OssProperties;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
-import software.amazon.awssdk.core.async.*;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.core.async.ResponsePublisher;
 import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3AsyncClient;
@@ -33,6 +35,7 @@ import java.nio.channels.WritableByteChannel;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
+import java.util.Map;
 import java.util.Optional;
 import java.util.function.Consumer;
 
@@ -94,7 +97,11 @@ public class OssClient {
                 .region(of())
                 .forcePathStyle(isStyle)
                 .httpClient(NettyNioAsyncHttpClient.builder()
-                    .connectionTimeout(Duration.ofSeconds(60)).build())
+                    .connectionTimeout(Duration.ofSeconds(60))
+                    .connectionAcquisitionTimeout(Duration.ofSeconds(30))
+                    .maxConcurrency(100)
+                    .maxPendingConnectionAcquires(1000)
+                    .build())
                 .build();
 
             //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
@@ -317,13 +324,13 @@ public class OssClient {
     }
 
     /**
-     * 获取私有URL链接
+     * 创建下载请求的预签名URL
      *
      * @param objectKey   对象KEY
      * @param expiredTime 链接授权到期时间
      */
-    public String getPrivateUrl(String objectKey, Duration expiredTime) {
-        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
+    public String createPresignedGetUrl(String objectKey, Duration expiredTime) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取下载对象的预签名 URL
         URL url = presigner.presignGetObject(
                 x -> x.signatureDuration(expiredTime)
                     .getObjectRequest(
@@ -332,7 +339,28 @@ public class OssClient {
                             .build())
                     .build())
             .url();
-        return url.toString();
+        return url.toExternalForm();
+    }
+
+    /**
+     * 创建上传请求的预签名URL
+     *
+     * @param objectKey   对象KEY
+     * @param expiredTime 链接授权到期时间
+     * @param metadata 元数据
+     */
+    public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
+        // 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL
+        URL url = presigner.presignPutObject(
+                x -> x.signatureDuration(expiredTime)
+                    .putObjectRequest(
+                        y -> y.bucket(properties.getBucketName())
+                            .key(objectKey)
+                            .metadata(metadata)
+                            .build())
+                    .build())
+            .url();
+        return url.toExternalForm();
     }
 
     /**

+ 6 - 10
ruoyi-common/ruoyi-common-redis/pom.xml

@@ -43,16 +43,12 @@
             <artifactId>jackson-datatype-jsr310</artifactId>
         </dependency>
 
-<!--        &lt;!&ndash; redis序列化替代方案 比json快无数的跨语言二进制序列化 &ndash;&gt;-->
-<!--        <dependency>-->
-<!--            <groupId>org.apache.fury</groupId>-->
-<!--            <artifactId>fury-core</artifactId>-->
-<!--            <version>0.9.0</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>org.slf4j</groupId>-->
-<!--            <artifactId>slf4j-api</artifactId>-->
-<!--        </dependency>-->
+        <!-- redis序列化替代方案 比json快无数的跨语言二进制序列化 -->
+        <dependency>
+            <groupId>org.apache.fory</groupId>
+            <artifactId>fory-core</artifactId>
+            <version>0.13.1</version>
+        </dependency>
 
     </dependencies>
 

+ 4 - 3
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java

@@ -53,9 +53,10 @@ public class RedisConfig {
             om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
             // 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
             om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
-//            LoggerFactory.useSlf4jLogging(true);
-//            FuryCodec furyCodec = new FuryCodec();
-//            CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
+            // org.apache.fory.logging.LoggerFactory 包别引入错了
+            // LoggerFactory.useSlf4jLogging(true);
+            // ForyCodec foryCodec = new ForyCodec();
+            // CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, foryCodec, foryCodec);
             TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
             // 组合序列化 key 使用 String 内容使用通用 json 格式
             CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);

+ 13 - 0
ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java

@@ -3,6 +3,7 @@ package org.dromara.common.sensitive.core;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.DesensitizedUtil;
 import lombok.AllArgsConstructor;
+import org.dromara.common.core.utils.DesensitizedUtils;
 
 import java.util.function.Function;
 
@@ -80,6 +81,18 @@ public enum SensitiveStrategy {
      */
     FIRST_MASK(DesensitizedUtil::firstMask),
 
+    /**
+     * 通用字符串脱敏
+     * 可配置前后可见长度和中间掩码长度
+     * 默认示例:前4位可见,后4位可见,中间固定4个*
+     */
+    STRING_MASK(s -> DesensitizedUtils.mask(s, 4, 4, 4)),
+
+    /**
+     * 高安全级别脱敏(Token / 私钥):前2位可见,后2位可见,中间全部掩码
+     */
+    MASK_HIGH_SECURITY(s -> DesensitizedUtils.maskHighSecurity(s, 2, 2)),
+
     /**
      * 清空为""
      */

+ 7 - 2
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java

@@ -28,9 +28,14 @@ public class SocialLoginConfigProperties {
     private String redirectUri;
 
     /**
-     * 是否获取unionId
+     * 是否需要申请unionid,目前只针对qq登录
      */
-    private boolean unionId;
+    private Boolean unionId;
+
+    /**
+     * Microsoft Entra ID(原微软 AAD)中的租户 ID
+     */
+    private String tenantId;
 
     /**
      * Coding 企业名称

+ 1 - 1
ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java

@@ -57,7 +57,7 @@ public class SocialUtils  {
             case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE);
             case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE);
             case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
-            case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
+            case "microsoft" -> new AuthMicrosoftRequest(builder.tenantId(obj.getTenantId()).build(), STATE_CACHE);
             case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
             case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
             case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);

+ 2 - 2
ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java

@@ -55,7 +55,7 @@ public class TenantHelper {
     /**
      * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
      */
-    public static void enableIgnore() {
+    private static void enableIgnore() {
         IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
         if (ObjectUtil.isNull(ignoreStrategy)) {
             InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
@@ -69,7 +69,7 @@ public class TenantHelper {
     /**
      * 关闭忽略租户
      */
-    public static void disableIgnore() {
+    private static void disableIgnore() {
         IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
         if (ObjectUtil.isNotNull(ignoreStrategy)) {
             boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())

+ 11 - 1
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/handler/GlobalExceptionHandler.java

@@ -15,6 +15,7 @@ import org.dromara.common.core.exception.base.BaseException;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.expression.ExpressionException;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.validation.BindException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -43,7 +44,7 @@ public class GlobalExceptionHandler {
      */
     @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
     public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
-                                                                HttpServletRequest request) {
+                                                       HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
         return R.fail(HttpStatus.HTTP_BAD_METHOD, e.getMessage());
@@ -210,4 +211,13 @@ public class GlobalExceptionHandler {
         return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
     }
 
+    /**
+     * SpEL 表达式相关异常
+     */
+    @ExceptionHandler(ExpressionException.class)
+    public R<Void> handleSpelException(ExpressionException e, HttpServletRequest request) {
+        log.error("请求地址'{}',SpEL解析异常: {}", request.getRequestURI(), e.getMessage());
+        return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "SpEL解析失败:" + e.getMessage());
+    }
+
 }

+ 43 - 5
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/interceptor/PlusWebInvokeTimeInterceptor.java

@@ -2,11 +2,17 @@ package org.dromara.common.web.interceptor;
 
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.StopWatch;
+import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
@@ -14,8 +20,10 @@ import org.springframework.http.MediaType;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 
-import java.io.BufferedReader;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * web的调用时间统计拦截器
@@ -31,19 +39,25 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         String url = request.getMethod() + " " + request.getRequestURI();
-
         // 打印请求参数
         if (isJsonRequest(request)) {
             String jsonParam = "";
             if (request instanceof RepeatedlyRequestWrapper) {
-                BufferedReader reader = request.getReader();
-                jsonParam = IoUtil.read(reader);
+                jsonParam = IoUtil.read(request.getReader());
+                if (StringUtils.isNotBlank(jsonParam)) {
+                    ObjectMapper objectMapper = JsonUtils.getObjectMapper();
+                    JsonNode rootNode = objectMapper.readTree(jsonParam);
+                    removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
+                    jsonParam = rootNode.toString();
+                }
             }
             log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
         } else {
             Map<String, String[]> parameterMap = request.getParameterMap();
             if (MapUtil.isNotEmpty(parameterMap)) {
-                String parameters = JsonUtils.toJsonString(parameterMap);
+                Map<String, String[]> map = new LinkedHashMap<>(parameterMap);
+                MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES);
+                String parameters = JsonUtils.toJsonString(map);
                 log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
             } else {
                 log.info("[PLUS]开始请求 => URL[{}],无参数", url);
@@ -57,6 +71,30 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
         return true;
     }
 
+    private void removeSensitiveFields(JsonNode node, String[] excludeProperties) {
+        if (node == null) {
+            return;
+        }
+        if (node.isObject()) {
+            ObjectNode objectNode = (ObjectNode) node;
+            // 收集要删除的字段名(避免 ConcurrentModification)
+            Set<String> fieldsToRemove = new HashSet<>();
+            objectNode.fieldNames().forEachRemaining(fieldName -> {
+                if (ArrayUtil.contains(excludeProperties, fieldName)) {
+                    fieldsToRemove.add(fieldName);
+                }
+            });
+            fieldsToRemove.forEach(objectNode::remove);
+            // 递归处理子节点
+            objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties));
+        } else if (node.isArray()) {
+            ArrayNode arrayNode = (ArrayNode) node;
+            for (JsonNode child : arrayNode) {
+                removeSensitiveFields(child, excludeProperties);
+            }
+        }
+    }
+
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 

+ 2 - 0
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java

@@ -62,6 +62,8 @@ public class TestDemoServiceImpl implements ITestDemoService {
     private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getDeptId() != null, TestDemo::getDeptId, bo.getDeptId());
+        lqw.eq(bo.getUserId() != null, TestDemo::getUserId, bo.getUserId());
         lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
         lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
         lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,

+ 3 - 1
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java

@@ -2,6 +2,7 @@ package org.dromara.demo.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.demo.domain.TestTree;
@@ -9,7 +10,6 @@ import org.dromara.demo.domain.bo.TestTreeBo;
 import org.dromara.demo.domain.vo.TestTreeVo;
 import org.dromara.demo.mapper.TestTreeMapper;
 import org.dromara.demo.service.ITestTreeService;
-import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
 import java.util.Collection;
@@ -44,6 +44,8 @@ public class TestTreeServiceImpl implements ITestTreeService {
     private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getDeptId() != null, TestTree::getDeptId, bo.getDeptId());
+        lqw.eq(bo.getUserId() != null, TestTree::getUserId, bo.getUserId());
         lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName());
         lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
             TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));

+ 5 - 3
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java

@@ -90,10 +90,12 @@ public class GenController extends BaseController {
     /**
      * 导入表结构(保存)
      *
-     * @param tables 表名串
+     * @param tables   表名串
+     * @param dataName 数据源名称
      */
     @SaCheckPermission("tool:gen:import")
     @Log(title = "代码生成", businessType = BusinessType.IMPORT)
+    @Lock4j(keys = {"#dataName"}, acquireTimeout = 10000)
     @RepeatSubmit()
     @PostMapping("/importTable")
     public R<Void> importTableSave(String tables, String dataName) {
@@ -175,7 +177,7 @@ public class GenController extends BaseController {
      */
     @SaCheckPermission("tool:gen:edit")
     @Log(title = "代码生成", businessType = BusinessType.UPDATE)
-    @Lock4j
+    @Lock4j(keys = {"#tableId"}, acquireTimeout = 5000)
     @GetMapping("/synchDb/{tableId}")
     public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
         genTableService.synchDb(tableId);
@@ -214,7 +216,7 @@ public class GenController extends BaseController {
      */
     @SaCheckPermission("tool:gen:list")
     @GetMapping(value = "/getDataNames")
-    public R<Object> getCurrentDataSourceNameList(){
+    public R<Object> getCurrentDataSourceNameList() {
         return R.ok(DataBaseHelper.getDataSourceNameList());
     }
 }

+ 4 - 4
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/domain/GenTableColumn.java

@@ -4,13 +4,12 @@ import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.domain.BaseEntity;
+import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import org.apache.ibatis.type.JdbcType;
-
-import jakarta.validation.constraints.NotBlank;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
 
 /**
  * 代码生成业务字段表 gen_table_column
@@ -115,6 +114,7 @@ public class GenTableColumn extends BaseEntity {
     /**
      * 字典类型
      */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
     private String dictType;
 
     /**

+ 3 - 4
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java

@@ -8,7 +8,6 @@ import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
@@ -28,6 +27,7 @@ import org.dromara.common.core.utils.file.FileUtils;
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.utils.IdGeneratorUtil;
 import org.dromara.generator.constant.GenConstants;
 import org.dromara.generator.domain.GenTable;
 import org.dromara.generator.domain.GenTableColumn;
@@ -60,7 +60,6 @@ public class GenTableServiceImpl implements IGenTableService {
 
     private final GenTableMapper baseMapper;
     private final GenTableColumnMapper genTableColumnMapper;
-    private final IdentifierGenerator identifierGenerator;
 
     private static final String[] TABLE_IGNORE = new String[]{"sj_", "flow_", "gen_"};
 
@@ -322,7 +321,7 @@ public class GenTableServiceImpl implements IGenTableService {
         GenTable table = baseMapper.selectGenTableById(tableId);
         List<Long> menuIds = new ArrayList<>();
         for (int i = 0; i < 6; i++) {
-            menuIds.add(identifierGenerator.nextId(null).longValue());
+            menuIds.add(IdGeneratorUtil.nextLongId());
         }
         table.setMenuIds(menuIds);
         // 设置主键列信息
@@ -468,7 +467,7 @@ public class GenTableServiceImpl implements IGenTableService {
         GenTable table = baseMapper.selectGenTableById(tableId);
         List<Long> menuIds = new ArrayList<>();
         for (int i = 0; i < 6; i++) {
-            menuIds.add(identifierGenerator.nextId(null).longValue());
+            menuIds.add(IdGeneratorUtil.nextLongId());
         }
         table.setMenuIds(menuIds);
         // 设置主键列信息

+ 4 - 8
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java

@@ -2,21 +2,20 @@ package org.dromara.system.controller.system;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.validate.QueryGroup;
-import org.dromara.common.web.core.BaseController;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
 import org.dromara.system.domain.bo.SysOssBo;
 import org.dromara.system.domain.vo.SysOssUploadVo;
 import org.dromara.system.domain.vo.SysOssVo;
 import org.dromara.system.service.ISysOssService;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.constraints.NotEmpty;
-import lombok.RequiredArgsConstructor;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -70,9 +69,6 @@ public class SysOssController extends BaseController {
     @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
     @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
-        if (ObjectUtil.isNull(file)) {
-            return R.fail("上传文件不能为空");
-        }
         SysOssVo oss = ossService.upload(file);
         SysOssUploadVo uploadVo = new SysOssUploadVo();
         uploadVo.setUrl(oss.getUrl());

+ 2 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java

@@ -2,6 +2,7 @@ package org.dromara.system.controller.system;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.digest.BCrypt;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
@@ -114,7 +115,7 @@ public class SysProfileController extends BaseController {
     @Log(title = "用户头像", businessType = BusinessType.UPDATE)
     @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     public R<AvatarVo> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) {
-        if (!avatarfile.isEmpty()) {
+        if (ObjectUtil.isNotNull(avatarfile) && !avatarfile.isEmpty()) {
             String extension = FileUtil.extName(avatarfile.getOriginalFilename());
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
                 return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");

+ 53 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java

@@ -1,6 +1,7 @@
 package org.dromara.system.service.impl;
 
 import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -14,6 +15,7 @@ import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.ObjectUtils;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.CacheUtils;
@@ -82,6 +84,7 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
 
     /**
      * 获取注册开关
+     *
      * @param tenantId 租户id
      * @return true开启,false关闭
      */
@@ -212,4 +215,54 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
         return SpringUtils.getAopProxy(this).selectConfigByKey(configKey);
     }
 
+    /**
+     * 根据参数 key 获取 Map 类型的配置
+     *
+     * @param configKey 参数 key
+     * @return Dict 对象,如果配置为空或无法解析,返回空 Dict
+     */
+    @Override
+    public Dict getConfigMap(String configKey) {
+        String configValue = getConfigValue(configKey);
+        return JsonUtils.parseMap(configValue);
+    }
+
+    /**
+     * 根据参数 key 获取 Map 类型的配置列表
+     *
+     * @param configKey 参数 key
+     * @return Dict 列表,如果配置为空或无法解析,返回空列表
+     */
+    @Override
+    public List<Dict> getConfigArrayMap(String configKey) {
+        String configValue = getConfigValue(configKey);
+        return JsonUtils.parseArrayMap(configValue);
+    }
+
+    /**
+     * 根据参数 key 获取指定类型的配置对象
+     *
+     * @param configKey 参数 key
+     * @param clazz     目标对象类型
+     * @return 对象实例,如果配置为空或无法解析,返回 null
+     */
+    @Override
+    public <T> T getConfigObject(String configKey, Class<T> clazz) {
+        String configValue = getConfigValue(configKey);
+        return JsonUtils.parseObject(configValue, clazz);
+    }
+
+    /**
+     * 根据参数 key 获取指定类型的配置列表=
+     *
+     * @param configKey 参数 key
+     * @param clazz     目标元素类型
+     * @return 指定类型列表,如果配置为空或无法解析,返回空列表
+     */
+    @Override
+    public <T> List<T> getConfigArray(String configKey, Class<T> clazz) {
+        String configValue = getConfigValue(configKey);
+        return JsonUtils.parseArray(configValue, clazz);
+    }
+
 }

+ 9 - 2
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java

@@ -192,6 +192,9 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
      */
     @Override
     public SysOssVo upload(MultipartFile file) {
+        if (ObjectUtil.isNull(file) || file.isEmpty()) {
+            throw new ServiceException("上传文件不能为空");
+        }
         String originalfileName = file.getOriginalFilename();
         String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
         OssClient storage = OssFactory.instance();
@@ -216,12 +219,16 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
      */
     @Override
     public SysOssVo upload(File file) {
+        if (ObjectUtil.isNull(file) || !file.isFile() || file.length() <= 0) {
+            throw new ServiceException("上传文件不能为空");
+        }
         String originalfileName = file.getName();
         String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
         OssClient storage = OssFactory.instance();
+        long length = file.length();
         UploadResult uploadResult = storage.uploadSuffix(file, suffix);
         SysOssExt ext1 = new SysOssExt();
-        ext1.setFileSize(file.length());
+        ext1.setFileSize(length);
         // 保存文件信息
         return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
     }
@@ -270,7 +277,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
         OssClient storage = OssFactory.instance(oss.getService());
         // 仅修改桶类型为 private 的URL,临时URL时长为120s
         if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
-            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
+            oss.setUrl(storage.createPresignedGetUrl(oss.getFileName(), Duration.ofSeconds(120)));
         }
         return oss;
     }

+ 16 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java

@@ -92,4 +92,20 @@ public interface FlowConstant {
      * 业务编码
      */
     String BUSINESS_CODE = "businessCode";
+
+    /**
+     * 忽略-办理权限校验(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE = "ignore";
+
+    /**
+     * 忽略-委派处理(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE_DEPUTE = "ignoreDepute";
+
+    /**
+     * 忽略-会签票签处理(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE_COOPERATE = "ignoreCooperate";
+
 }

+ 1 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwDefinitionController.java

@@ -187,6 +187,7 @@ public class FlwDefinitionController extends BaseController {
     @RepeatSubmit()
     @PutMapping("/active/{id}")
     @Transactional(rollbackFor = Exception.class)
+    @Log(title = "流程定义", businessType = BusinessType.UPDATE)
     public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
         return R.ok(active ? defService.active(id) : defService.unActive(id));
     }

+ 9 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java

@@ -1,7 +1,9 @@
 package org.dromara.workflow.controller;
 
+import cn.hutool.core.convert.Convert;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
@@ -75,8 +77,9 @@ public class FlwInstanceController extends BaseController {
      * @param businessIds 业务id
      */
     @DeleteMapping("/deleteByBusinessIds/{businessIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
     public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
-        return toAjax(flwInstanceService.deleteByBusinessIds(businessIds));
+        return toAjax(flwInstanceService.deleteByBusinessIds(StreamUtils.toList(businessIds, Convert::toStr)));
     }
 
     /**
@@ -85,6 +88,7 @@ public class FlwInstanceController extends BaseController {
      * @param instanceIds 实例id
      */
     @DeleteMapping("/deleteByInstanceIds/{instanceIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
     public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
         return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
     }
@@ -95,6 +99,7 @@ public class FlwInstanceController extends BaseController {
      * @param instanceIds 实例id
      */
     @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
     public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
         return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
     }
@@ -106,6 +111,7 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/cancelProcessApply")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
     public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
         return toAjax(flwInstanceService.cancelProcessApply(bo));
     }
@@ -118,6 +124,7 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/active/{id}")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
     public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
         return R.ok(active ? insService.active(id) : insService.unActive(id));
     }
@@ -160,6 +167,7 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/updateVariable")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
     public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
         return toAjax(flwInstanceService.updateVariable(bo));
     }

+ 9 - 9
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java

@@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 流程spel达式定义
+ * 流程spel达式定义
  *
  * @author Michelle.Chung
  * @date 2025-07-04
@@ -38,7 +38,7 @@ public class FlwSpelController extends BaseController {
     private final IFlwSpelService flwSpelService;
 
     /**
-     * 查询流程spel达式定义列表
+     * 查询流程spel达式定义列表
      */
     @SaCheckPermission("workflow:spel:list")
     @GetMapping("/list")
@@ -47,7 +47,7 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 获取流程spel达式定义详细信息
+     * 获取流程spel达式定义详细信息
      *
      * @param id 主键
      */
@@ -58,10 +58,10 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      */
     @SaCheckPermission("workflow:spel:add")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowSpelBo bo) {
@@ -69,10 +69,10 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      */
     @SaCheckPermission("workflow:spel:edit")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowSpelBo bo) {
@@ -80,12 +80,12 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 删除流程spel达式定义
+     * 删除流程spel达式定义
      *
      * @param ids 主键串
      */
     @SaCheckPermission("workflow:spel:remove")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
         return toAjax(flwSpelService.deleteWithValidByIds(List.of(ids), true));

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

@@ -216,6 +216,7 @@ public class FlwTaskController extends BaseController {
      * @return 结果
      */
     @PostMapping("/urgeTask")
+    @Log(title = "任务管理", businessType = BusinessType.INSERT)
     public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
         return toAjax(flwTaskService.urgeTask(bo));
     }

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java

@@ -10,7 +10,7 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
 import java.io.Serial;
 
 /**
- * 流程spel达式定义对象 flow_spel
+ * 流程spel达式定义对象 flow_spel
  *
  * @author Michelle.Chung
  * @date 2025-07-04

+ 0 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java

@@ -1,6 +1,5 @@
 package org.dromara.workflow.domain.bo;
 
-import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.dromara.common.core.validate.AddGroup;

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

@@ -50,6 +50,6 @@ public class FlowInstanceBo implements Serializable {
     /**
      * 申请人Ids
      */
-    private List<Long> createByIds;
+    private List<String> createByIds;
 
 }

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

@@ -10,7 +10,7 @@ import jakarta.validation.constraints.*;
 import org.dromara.workflow.domain.FlowSpel;
 
 /**
- * 流程spel达式定义业务对象 flow_spel
+ * 流程spel达式定义业务对象 flow_spel
  *
  * @author Michelle.Chung
  * @date 2025-07-04

+ 14 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowTaskBo.java

@@ -1,10 +1,13 @@
 package org.dromara.workflow.domain.bo;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 任务请求对象
@@ -42,6 +45,11 @@ public class FlowTaskBo implements Serializable {
      */
     private Long instanceId;
 
+    /**
+     * 流程状态
+     */
+    private String flowStatus;
+
     /**
      * 权限列表
      */
@@ -52,4 +60,10 @@ public class FlowTaskBo implements Serializable {
      */
     private List<Long> createByIds;
 
+    /**
+     * 请求参数
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Map<String, Object> params = new HashMap<>();
+
 }

+ 4 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java

@@ -253,4 +253,8 @@ public class FlowHisTaskVo implements Serializable {
         this.cooperateTypeName = CooperateType.getValueByKey(cooperateType);
     }
 
+    public String getCreateTime() {
+        return DateUtils.formatFriendlyTime(createTime);
+    }
+
 }

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java

@@ -78,7 +78,7 @@ public class FlowInstanceVo {
     private String variable;
 
     /**
-     * 流程状态(0待提交 1审批中 2 审批通过 3自动通过 8已完成 9已退回 10失效)
+     * 流程状态
      */
     private String flowStatus;
 

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java

@@ -14,7 +14,7 @@ import java.util.Date;
 
 
 /**
- * 流程spel达式定义视图对象 flow_spel
+ * 流程spel达式定义视图对象 flow_spel
  *
  * @author Michelle.Chung
  * @date 2025-07-04

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

@@ -1,6 +1,7 @@
 package org.dromara.workflow.domain.vo;
 
 import lombok.Data;
+import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.translation.annotation.Translation;
 import org.dromara.common.translation.constant.TransConstant;
 import org.dromara.warm.flow.core.entity.User;
@@ -8,7 +9,6 @@ import org.dromara.workflow.common.constant.FlowConstant;
 
 import java.io.Serial;
 import java.io.Serializable;
-import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -163,7 +163,7 @@ public class FlowTaskVo implements Serializable {
     /**
      * 流程签署比例值 大于0为票签,会签
      */
-    private BigDecimal nodeRatio;
+    private String nodeRatio;
 
     /**
      * 申请人id
@@ -212,4 +212,8 @@ public class FlowTaskVo implements Serializable {
     private String businessTitle;
     //业务扩展信息结束
 
+    public String getCreateTime() {
+        return DateUtils.formatFriendlyTime(createTime);
+    }
+
 }

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

@@ -7,6 +7,7 @@ import org.dromara.common.core.domain.event.ProcessEvent;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
 import org.dromara.workflow.common.ConditionalOnEnable;
 import org.springframework.stereotype.Component;
 
@@ -55,22 +56,22 @@ public class FlowProcessEventHandler {
      *
      * @param flowCode   流程定义编码
      * @param instance   实例数据
-     * @param taskId     任务id
+     * @param nextTask   任务
      * @param params     上一个任务的办理参数
      */
-    public void processTaskHandler(String flowCode, Instance instance, Long taskId, Map<String, Object> params) {
+    public void processTaskHandler(String flowCode, Instance instance, Task nextTask, Map<String, Object> params) {
         String tenantId = TenantHelper.getTenantId();
         log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}",
-            tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId);
+            tenantId, flowCode, instance.getBusinessId(), nextTask.getNodeType(), nextTask.getNodeCode(), nextTask.getNodeName(), nextTask.getId());
         ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
         processTaskEvent.setTenantId(tenantId);
         processTaskEvent.setFlowCode(flowCode);
         processTaskEvent.setInstanceId(instance.getId());
         processTaskEvent.setBusinessId(instance.getBusinessId());
-        processTaskEvent.setNodeType(instance.getNodeType());
-        processTaskEvent.setNodeCode(instance.getNodeCode());
-        processTaskEvent.setNodeName(instance.getNodeName());
-        processTaskEvent.setTaskId(taskId);
+        processTaskEvent.setNodeType(nextTask.getNodeType());
+        processTaskEvent.setNodeCode(nextTask.getNodeCode());
+        processTaskEvent.setNodeName(nextTask.getNodeName());
+        processTaskEvent.setTaskId(nextTask.getId());
         processTaskEvent.setStatus(instance.getFlowStatus());
         processTaskEvent.setParams(params);
         SpringUtils.context().publishEvent(processTaskEvent);

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

@@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.TypeReference;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.enums.BusinessStatusEnum;
@@ -73,6 +74,9 @@ public class WorkflowGlobalListener implements GlobalListener {
         String ext = listenerVariable.getNode().getExt();
         if (StringUtils.isNotBlank(ext)) {
             Map<String, Object> variable = listenerVariable.getVariable();
+            if (CollUtil.isNotEmpty(variable)) {
+                variable = new HashMap<>();
+            }
             NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
             Set<String> copyList = nodeExt.getCopySettings();
             if (CollUtil.isNotEmpty(copyList)) {
@@ -104,20 +108,61 @@ public class WorkflowGlobalListener implements GlobalListener {
         Definition definition = listenerVariable.getDefinition();
         Instance instance = listenerVariable.getInstance();
         String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
+        String hisStatus = flowParams != null ? flowParams.getHisStatus() : null;
+
         for (Task flowTask : nextTasks) {
-            // 如果办理或者退回并行存在需要指定办理人,则直接覆盖办理人
-            if (variable.containsKey(flowTask.getNodeCode()) && TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) {
-                String userIds = variable.get(flowTask.getNodeCode()).toString();
-                flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR)));
-                variable.remove(flowTask.getNodeCode());
+            String nodeCode = flowTask.getNodeCode();
+
+            // 处理办理或退回时指定办理人的情况
+            if (TaskStatusEnum.PASS.getStatus().equals(hisStatus)) {
+                processTaskPermission(variable, flowTask, hisStatus);
+            } else if (TaskStatusEnum.BACK.getStatus().equals(hisStatus)) {
+                processTaskPermission(variable, flowTask, hisStatus);
             }
+
             // 如果是申请节点,则把启动人添加到办理人
-            if (flowTask.getNodeCode().equals(applyNodeCode)) {
+            if (nodeCode.equals(applyNodeCode) && StringUtils.isNotBlank(instance.getCreateBy())) {
                 flowTask.setPermissionList(List.of(instance.getCreateBy()));
             }
         }
     }
 
+    /**
+     * 处理任务权限设置
+     *
+     * @param variable   变量集合
+     * @param flowTask   流程任务
+     * @param taskStatus 任务状态
+     */
+    private void processTaskPermission(Map<String, Object> variable, Task flowTask, String taskStatus) {
+        String nodeKey = taskStatus + StrUtil.COLON + flowTask.getNodeCode();
+
+        // 检查是否存在状态相关的变量
+        if (!variable.containsKey(nodeKey)) {
+            return;
+        }
+
+        // 获取用户ID字符串
+        Object userIdsObj = variable.get(nodeKey);
+        if (userIdsObj == null) {
+            return;
+        }
+
+        String userIds = userIdsObj.toString();
+        if (StringUtils.isBlank(userIds)) {
+            return;
+        }
+
+        // 分割用户ID并设置权限列表
+        String[] userIdArray = userIds.split(StringUtils.SEPARATOR);
+        if (userIdArray.length > 0) {
+            flowTask.setPermissionList(List.of(userIdArray));
+            // 移除已处理的状态变量
+            variable.remove(nodeKey);
+            FlowEngine.insService().removeVariables(flowTask.getInstanceId(),nodeKey);
+        }
+    }
+
     /**
      * 完成监听器,当前任务完成后执行
      *
@@ -144,7 +189,8 @@ public class WorkflowGlobalListener implements GlobalListener {
         //申请人提交事件
         Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT);
         if (submit != null && submit) {
-            flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, instance.getFlowStatus(), variable, true);
+            String status = determineFlowStatus(instance);
+            flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, variable, true);
         } else {
             // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件)
             String status = determineFlowStatus(instance);
@@ -165,7 +211,7 @@ public class WorkflowGlobalListener implements GlobalListener {
         //发布任务事件
         if (CollUtil.isNotEmpty(nextTasks)) {
             for (Task nextTask : nextTasks) {
-                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(), params);
+                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask, params);
             }
         }
         if (ObjectUtil.isNull(flowParams)) {

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java

@@ -5,7 +5,7 @@ import org.dromara.workflow.domain.vo.FlowSpelVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
 /**
- * 流程spel达式定义Mapper接口
+ * 流程spel达式定义Mapper接口
  *
  * @author Michelle.Chung
  * @date 2025-07-04

+ 1 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java

@@ -75,7 +75,7 @@ public interface IFlwInstanceService {
      * @param businessIds 业务id
      * @return 结果
      */
-    boolean deleteByBusinessIds(List<Long> businessIds);
+    boolean deleteByBusinessIds(List<String> businessIds);
 
     /**
      * 按照实例id删除流程实例

+ 12 - 12
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java

@@ -12,7 +12,7 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 流程spel达式定义Service接口
+ * 流程spel达式定义Service接口
  *
  * @author Michelle.Chung
  * @date 2025-07-04
@@ -20,48 +20,48 @@ import java.util.Map;
 public interface IFlwSpelService {
 
     /**
-     * 查询流程spel达式定义
+     * 查询流程spel达式定义
      *
      * @param id 主键
-     * @return 流程spel达式定义
+     * @return 流程spel达式定义
      */
     FlowSpelVo queryById(Long id);
 
     /**
-     * 分页查询流程spel达式定义列表
+     * 分页查询流程spel达式定义列表
      *
      * @param bo        查询条件
      * @param pageQuery 分页参数
-     * @return 流程spel达式定义分页列表
+     * @return 流程spel达式定义分页列表
      */
     TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery);
 
     /**
-     * 查询符合条件的流程spel达式定义列表
+     * 查询符合条件的流程spel达式定义列表
      *
      * @param bo 查询条件
-     * @return 流程spel达式定义列表
+     * @return 流程spel达式定义列表
      */
     List<FlowSpelVo> queryList(FlowSpelBo bo);
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否新增成功
      */
     Boolean insertByBo(FlowSpelBo bo);
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否修改成功
      */
     Boolean updateByBo(FlowSpelBo bo);
 
     /**
-     * 校验并批量删除流程spel达式定义信息
+     * 校验并批量删除流程spel达式定义信息
      *
      * @param ids     待删除的主键集合
      * @param isValid 是否进行有效性校验

+ 3 - 0
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java

@@ -206,6 +206,9 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
         if (ObjectUtil.isNull(oldCategory)) {
             throw new ServiceException("流程分类不存在,无法修改");
         }
+        if (oldCategory.getParentId() == 0L && category.getParentId() != 0L) {
+            throw new ServiceException("不允许修改顶级分类的父级节点");
+        }
         if (!oldCategory.getParentId().equals(category.getParentId())) {
             FlowCategory newParentCategory = baseMapper.selectById(category.getParentId());
             if (ObjectUtil.isNotNull(newParentCategory)) {

+ 17 - 12
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java

@@ -88,23 +88,28 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
             if (ObjectUtil.isEmpty(messageTypeEnum)) {
                 continue;
             }
-            switch (messageTypeEnum) {
-                case SYSTEM_MESSAGE -> {
-                    SseMessageDto dto = new SseMessageDto();
-                    dto.setUserIds(userIds);
-                    dto.setMessage(message);
-                    SseMessageUtils.publishMessage(dto);
+            try {
+                switch (messageTypeEnum) {
+                    case SYSTEM_MESSAGE -> {
+                        SseMessageDto dto = new SseMessageDto();
+                        dto.setUserIds(userIds);
+                        dto.setMessage(message);
+                        SseMessageUtils.publishMessage(dto);
+                    }
+                    case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
+                    case SMS_MESSAGE -> {
+                        // TODO: 补充短信发送逻辑
+                        log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
+                    }
+                    default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
                 }
-                case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
-                case SMS_MESSAGE -> {
-                    //todo 短信发送
-                }
-                default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
+            } catch (Exception ex) {
+                // 记录错误但不抛出,确保主逻辑不受影响
+                log.error("【消息发送失败】类型={},原因={}", messageTypeEnum, ex.getMessage(), ex);
             }
         }
     }
 
-
     /**
      * 申请人节点编码
      *

+ 5 - 4
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java

@@ -208,10 +208,6 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void syncDef(String tenantId) {
-        List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
-        if (CollUtil.isEmpty(flowDefinitions)) {
-            return;
-        }
         FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
             .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID)
             .eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
@@ -223,6 +219,11 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
         flowCategory.setUpdateBy(null);
         flowCategory.setUpdateTime(null);
         flwCategoryMapper.insert(flowCategory);
+
+        List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
+        if (CollUtil.isEmpty(flowDefinitions)) {
+            return;
+        }
         List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
         List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
         List<FlowSkip> flowSkips = flowSkipMapper.selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));

+ 60 - 91
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java

@@ -181,8 +181,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean deleteByBusinessIds(List<Long> businessIds) {
-        List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr)));
+    public boolean deleteByBusinessIds(List<String> businessIds) {
+        List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, businessIds));
         if (CollUtil.isEmpty(flowInstances)) {
             log.warn("未找到对应的流程实例信息,无法执行删除操作。");
             return false;
@@ -211,27 +211,17 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
             Function.identity()
         );
 
-        try {
-            // 逐一触发删除事件
-            instances.forEach(instance -> {
-                Definition definition = definitionMap.get(instance.getDefinitionId());
-                if (ObjectUtil.isNull(definition)) {
-                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                    return;
-                }
-                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-            });
-            // 删除实例
-            boolean remove = insService.remove(instanceIds);
-            if (!remove) {
-                log.warn("删除流程实例失败!");
-                throw new ServiceException("删除流程实例失败");
+        // 逐一触发删除事件
+        instances.forEach(instance -> {
+            Definition definition = definitionMap.get(instance.getDefinitionId());
+            if (ObjectUtil.isNull(definition)) {
+                log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+                return;
             }
-        } catch (Exception e) {
-            log.warn("操作失败!{}", e.getMessage());
-            throw new ServiceException(e.getMessage());
-        }
-        return true;
+            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+        });
+        // 删除实例
+        return insService.remove(instanceIds);
     }
 
     /**
@@ -254,27 +244,22 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
             Definition::getId,
             Function.identity()
         );
-        try {
-            // 逐一触发删除事件
-            instances.forEach(instance -> {
-                Definition definition = definitionMap.get(instance.getDefinitionId());
-                if (ObjectUtil.isNull(definition)) {
-                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                    return;
-                }
-                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-            });
-            List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
-            if (CollUtil.isNotEmpty(flowTaskList)) {
-                FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+        // 逐一触发删除事件
+        instances.forEach(instance -> {
+            Definition definition = definitionMap.get(instance.getDefinitionId());
+            if (ObjectUtil.isNull(definition)) {
+                log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+                return;
             }
-            FlowEngine.taskService().deleteByInsIds(instanceIds);
-            FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
-            FlowEngine.insService().removeByIds(instanceIds);
-        } catch (Exception e) {
-            log.warn("操作失败!{}", e.getMessage());
-            throw new ServiceException(e.getMessage());
+            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+        });
+        List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
+        if (CollUtil.isNotEmpty(flowTaskList)) {
+            FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
         }
+        FlowEngine.taskService().deleteByInsIds(instanceIds);
+        FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+        FlowEngine.insService().removeByIds(instanceIds);
         return true;
     }
 
@@ -286,29 +271,24 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean cancelProcessApply(FlowCancelBo bo) {
-        try {
-            Instance instance = selectInstByBusinessId(bo.getBusinessId());
-            if (instance == null) {
-                throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
-            }
-            Definition definition = defService.getById(instance.getDefinitionId());
-            if (definition == null) {
-                throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
-            }
-            String message = bo.getMessage();
-            String userIdStr = LoginHelper.getUserIdStr();
-            BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
-            FlowParams flowParams = FlowParams.build()
-                .message(message)
-                .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
-                .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
-                .handler(userIdStr)
-                .ignore(true);
-            taskService.revoke(instance.getId(), flowParams);
-        } catch (Exception e) {
-            log.error("撤销失败: {}", e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Instance instance = selectInstByBusinessId(bo.getBusinessId());
+        if (instance == null) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
         }
+        Definition definition = defService.getById(instance.getDefinitionId());
+        if (definition == null) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
+        }
+        String message = bo.getMessage();
+        String userIdStr = LoginHelper.getUserIdStr();
+        BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
+        FlowParams flowParams = FlowParams.build()
+            .message(message)
+            .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
+            .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
+            .handler(userIdStr)
+            .ignore(true);
+        taskService.revoke(instance.getId(), flowParams);
         return true;
     }
 
@@ -422,20 +402,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
         if (flowInstance == null) {
             throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
         }
-        try {
-            Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
-            if (!variableMap.containsKey(bo.getKey())) {
-                log.error("变量不存在: {}", bo.getKey());
-                return false;
-            }
-            variableMap.put(bo.getKey(), bo.getValue());
-            flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
-            flowInstanceMapper.updateById(flowInstance);
-        } catch (Exception e) {
-            log.error("设置流程变量失败: {}", e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
+        if (!variableMap.containsKey(bo.getKey())) {
+            log.error("变量不存在: {}", bo.getKey());
+            return false;
         }
-        return true;
+        variableMap.put(bo.getKey(), bo.getValue());
+        flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
+        return flowInstanceMapper.updateById(flowInstance) > 0;
     }
 
     /**
@@ -480,21 +454,16 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean processInvalid(FlowInvalidBo bo) {
-        try {
-            Instance instance = insService.getById(bo.getId());
-            if (instance != null) {
-                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
-            }
-            FlowParams flowParams = FlowParams.build()
-                .message(bo.getComment())
-                .flowStatus(BusinessStatusEnum.INVALID.getStatus())
-                .hisStatus(TaskStatusEnum.INVALID.getStatus())
-                .ignore(true);
-            taskService.terminationByInsId(bo.getId(), flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Instance instance = insService.getById(bo.getId());
+        if (instance != null) {
+            BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
         }
+        FlowParams flowParams = FlowParams.build()
+            .message(bo.getComment())
+            .flowStatus(BusinessStatusEnum.INVALID.getStatus())
+            .hisStatus(TaskStatusEnum.INVALID.getStatus())
+            .ignore(true);
+        taskService.terminationByInsId(bo.getId(), flowParams);
+        return true;
     }
 }

+ 12 - 12
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java

@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 流程spel达式定义Service业务层处理
+ * 流程spel达式定义Service业务层处理
  *
  * @author Michelle.Chung
  * @date 2025-07-04
@@ -42,10 +42,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     private final FlwSpelMapper baseMapper;
 
     /**
-     * 查询流程spel达式定义
+     * 查询流程spel达式定义
      *
      * @param id 主键
-     * @return 流程spel达式定义
+     * @return 流程spel达式定义
      */
     @Override
     public FlowSpelVo queryById(Long id){
@@ -53,11 +53,11 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 分页查询流程spel达式定义列表
+     * 分页查询流程spel达式定义列表
      *
      * @param bo        查询条件
      * @param pageQuery 分页参数
-     * @return 流程spel达式定义分页列表
+     * @return 流程spel达式定义分页列表
      */
     @Override
     public TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery) {
@@ -67,10 +67,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 查询符合条件的流程spel达式定义列表
+     * 查询符合条件的流程spel达式定义列表
      *
      * @param bo 查询条件
-     * @return 流程spel达式定义列表
+     * @return 流程spel达式定义列表
      */
     @Override
     public List<FlowSpelVo> queryList(FlowSpelBo bo) {
@@ -92,9 +92,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否新增成功
      */
     @Override
@@ -109,9 +109,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否修改成功
      */
     @Override
@@ -129,7 +129,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 校验并批量删除流程spel达式定义信息
+     * 校验并批量删除流程spel达式定义信息
      *
      * @param ids     待删除的主键集合
      * @param isValid 是否进行有效性校验

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

@@ -5,9 +5,10 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.lock.annotation.Lock4j;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
@@ -25,10 +26,12 @@ import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.utils.IdGeneratorUtil;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.warm.flow.core.FlowEngine;
 import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.*;
+import org.dromara.warm.flow.core.enums.CooperateType;
 import org.dromara.warm.flow.core.enums.NodeType;
 import org.dromara.warm.flow.core.enums.SkipType;
 import org.dromara.warm.flow.core.enums.UserType;
@@ -60,7 +63,6 @@ import org.dromara.workflow.service.IFlwTaskService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.math.BigDecimal;
 import java.util.*;
 
 import static org.dromara.workflow.common.constant.FlowConstant.*;
@@ -84,7 +86,6 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     private final FlowInstanceMapper flowInstanceMapper;
     private final FlowTaskMapper flowTaskMapper;
     private final FlowHisTaskMapper flowHisTaskMapper;
-    private final IdentifierGenerator identifierGenerator;
     private final UserService userService;
     private final FlwTaskMapper flwTaskMapper;
     private final FlwCategoryMapper flwCategoryMapper;
@@ -101,6 +102,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @Lock4j(keys = {"#startProcessBo.flowCode + #startProcessBo.businessId"})
     public StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo) {
         String businessId = startProcessBo.getBusinessId();
         if (StringUtils.isBlank(businessId)) {
@@ -137,6 +139,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
 
         // 将流程定义内的扩展参数设置到变量中
         Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
+        if (ObjectUtil.isNull(definition)) {
+            throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
+        }
         Dict dict = JsonUtils.parseMap(definition.getExt());
         boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
         variables.put(FlowConstant.AUTO_PASS, autoPass);
@@ -146,12 +151,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             .flowCode(startProcessBo.getFlowCode())
             .variable(startProcessBo.getVariables())
             .flowStatus(BusinessStatusEnum.DRAFT.getStatus());
-        Instance instance;
-        try {
-            instance = insService.start(businessId, flowParams);
-        } catch (Exception e) {
-            throw new ServiceException(e.getMessage());
-        }
+        Instance instance = insService.start(businessId, flowParams);
         // 保存流程实例业务信息
         this.buildFlowInstanceBizExt(instance, bizExt);
         // 申请人执行流程
@@ -197,53 +197,52 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @Lock4j(keys = {"#completeTaskBo.taskId"})
     public boolean completeTask(CompleteTaskBo completeTaskBo) {
-        try {
-            // 获取任务ID并查询对应的流程任务和实例信息
-            Long taskId = completeTaskBo.getTaskId();
-            List<String> messageType = completeTaskBo.getMessageType();
-            String notice = completeTaskBo.getNotice();
-            // 获取抄送人
-            List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
-            // 设置抄送人
-            Map<String, Object> variables = completeTaskBo.getVariables();
-            variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
-            // 消息类型
-            variables.put(FlowConstant.MESSAGE_TYPE, messageType);
-            // 消息通知
-            variables.put(FlowConstant.MESSAGE_NOTICE, notice);
-
-
-            FlowTask flowTask = flowTaskMapper.selectById(taskId);
-            if (ObjectUtil.isNull(flowTask)) {
-                throw new ServiceException("流程任务不存在或任务已审批!");
-            }
-            Instance ins = insService.getById(flowTask.getInstanceId());
-            // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
-            if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
-                variables.put(FlowConstant.SUBMIT, true);
-            }
-            // 设置弹窗处理人
-            Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
-            if (CollUtil.isNotEmpty(assigneeMap)) {
-                variables.putAll(assigneeMap);
-            }
-            // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
-            FlowParams flowParams = FlowParams.build()
-                .handler(completeTaskBo.getHandler())
-                .variable(variables)
-                .skipType(SkipType.PASS.getKey())
-                .message(completeTaskBo.getMessage())
-                .flowStatus(BusinessStatusEnum.WAITING.getStatus())
-                .hisStatus(TaskStatusEnum.PASS.getStatus())
-                .hisTaskExt(completeTaskBo.getFileId());
-            Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
-            skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
-        }
+        // 获取任务ID并查询对应的流程任务和实例信息
+        Long taskId = completeTaskBo.getTaskId();
+        List<String> messageType = completeTaskBo.getMessageType();
+        String notice = completeTaskBo.getNotice();
+        // 获取抄送人
+        List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
+        // 设置抄送人
+        Map<String, Object> variables = completeTaskBo.getVariables();
+        variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
+        // 消息类型
+        variables.put(FlowConstant.MESSAGE_TYPE, messageType);
+        // 消息通知
+        variables.put(FlowConstant.MESSAGE_NOTICE, notice);
+
+
+        FlowTask flowTask = flowTaskMapper.selectById(taskId);
+        if (ObjectUtil.isNull(flowTask)) {
+            throw new ServiceException("流程任务不存在或任务已审批!");
+        }
+        Instance ins = insService.getById(flowTask.getInstanceId());
+        // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
+        if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
+            variables.put(FlowConstant.SUBMIT, true);
+        }
+        // 设置弹窗处理人
+        Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
+        if (CollUtil.isNotEmpty(assigneeMap)) {
+            variables.putAll(assigneeMap);
+        }
+        // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
+        FlowParams flowParams = FlowParams.build()
+            .handler(completeTaskBo.getHandler())
+            .variable(variables)
+            .ignore(Convert.toBool(variables.getOrDefault(VAR_IGNORE, false)))
+            .ignoreDepute(Convert.toBool(variables.getOrDefault(VAR_IGNORE_DEPUTE, false)))
+            .ignoreCooperate(Convert.toBool(variables.getOrDefault(VAR_IGNORE_COOPERATE, false)))
+            .skipType(SkipType.PASS.getKey())
+            .message(completeTaskBo.getMessage())
+            .flowStatus(BusinessStatusEnum.WAITING.getStatus())
+            .hisStatus(TaskStatusEnum.PASS.getStatus())
+            .hisTaskExt(completeTaskBo.getFileId());
+        Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
+        skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
+        return true;
     }
 
     /**
@@ -305,10 +304,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
                     hashSet.addAll(popUserIds);
                     hashSet.addAll(variableUserIds);
-                    map.put(entry.getKey(), StringUtils.joinComma(hashSet));
+                    map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
+                    map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
                 }
             } else {
-                map.put(entry.getKey(), entry.getValue());
+                map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
+                map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
             }
         }
         return map;
@@ -333,7 +334,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
         flowNode.setNodeName(flowHisTask.getTargetNodeName());
         //生成新的任务id
-        long taskId = identifierGenerator.nextId(null).longValue();
+        long taskId = IdGeneratorUtil.nextLongId();
         task.setId(taskId);
         task.setNodeName("【抄送】" + task.getNodeName());
         Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000);
@@ -447,15 +448,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     }
 
     private QueryWrapper<FlowTaskBo> buildQueryWrapper(FlowTaskBo flowTaskBo) {
+        Map<String, Object> params = flowTaskBo.getParams();
         QueryWrapper<FlowTaskBo> wrapper = Wrappers.query();
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getNodeName()), "t.node_name", flowTaskBo.getNodeName());
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowName()), "t.flow_name", flowTaskBo.getFlowName());
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowCode()), "t.flow_code", flowTaskBo.getFlowCode());
+        wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowStatus()), "t.flow_status", flowTaskBo.getFlowStatus());
         wrapper.in(CollUtil.isNotEmpty(flowTaskBo.getCreateByIds()), "t.create_by", flowTaskBo.getCreateByIds());
         if (StringUtils.isNotBlank(flowTaskBo.getCategory())) {
             List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowTaskBo.getCategory()));
             wrapper.in("t.category", StreamUtils.toList(categoryIds, Convert::toStr));
         }
+        wrapper.between(params.get("beginTime") != null && params.get("endTime") != null,
+            "t.create_time", params.get("beginTime"), params.get("endTime"));
         wrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
         return wrapper;
     }
@@ -468,40 +473,35 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean backProcess(BackProcessBo bo) {
-        try {
-            Long taskId = bo.getTaskId();
-            String notice = bo.getNotice();
-            List<String> messageType = bo.getMessageType();
-            String message = bo.getMessage();
-            FlowTask task = flowTaskMapper.selectById(taskId);
-            if (ObjectUtil.isNull(task)) {
-                throw new ServiceException("任务不存在!");
-            }
-            Instance inst = insService.getById(task.getInstanceId());
-            BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
-            Long definitionId = task.getDefinitionId();
-            String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
-
-            Map<String, Object> variable = new HashMap<>();
-            // 消息类型
-            variable.put(FlowConstant.MESSAGE_TYPE, messageType);
-            // 消息通知
-            variable.put(FlowConstant.MESSAGE_NOTICE, notice);
-
-            FlowParams flowParams = FlowParams.build()
-                .nodeCode(bo.getNodeCode())
-                .variable(variable)
-                .message(message)
-                .skipType(SkipType.REJECT.getKey())
-                .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
-                .hisStatus(TaskStatusEnum.BACK.getStatus())
-                .hisTaskExt(bo.getFileId());
-            taskService.skip(task.getId(), flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Long taskId = bo.getTaskId();
+        String notice = bo.getNotice();
+        List<String> messageType = bo.getMessageType();
+        String message = bo.getMessage();
+        FlowTask task = flowTaskMapper.selectById(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw new ServiceException("任务不存在!");
         }
+        Instance inst = insService.getById(task.getInstanceId());
+        BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
+        Long definitionId = task.getDefinitionId();
+        String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
+
+        Map<String, Object> variable = new HashMap<>();
+        // 消息类型
+        variable.put(FlowConstant.MESSAGE_TYPE, messageType);
+        // 消息通知
+        variable.put(FlowConstant.MESSAGE_NOTICE, notice);
+
+        FlowParams flowParams = FlowParams.build()
+            .nodeCode(bo.getNodeCode())
+            .variable(variable)
+            .message(message)
+            .skipType(SkipType.REJECT.getKey())
+            .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
+            .hisStatus(TaskStatusEnum.BACK.getStatus())
+            .hisTaskExt(bo.getFileId());
+        taskService.skip(task.getId(), flowParams);
+        return true;
     }
 
     /**
@@ -529,8 +529,22 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
         //获取可驳回的前置节点
         List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
-        if (CollUtil.isNotEmpty(nodes)) {
-            return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
+        List<HisTask> hisTaskList = hisTaskService.getByInsId(task.getInstanceId());
+
+        Map<String, Node> nodeMap = StreamUtils.toIdentityMap(nodes, Node::getNodeCode);
+        Set<String> added = new HashSet<>();
+        List<Node> backNodeList = new ArrayList<>();
+        for (HisTask hisTask : hisTaskList) {
+            Node nodeValue = nodeMap.get(hisTask.getNodeCode());
+            if (nodeValue != null
+                && NodeType.BETWEEN.getKey().equals(nodeValue.getNodeType())
+                && added.add(nodeValue.getNodeCode())) {
+                backNodeList.add(nodeValue);
+            }
+        }
+        if (CollUtil.isNotEmpty(backNodeList)) {
+            Collections.reverse(backNodeList);
+            return backNodeList;
         }
         return nodes;
     }
@@ -543,26 +557,21 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean terminationTask(FlowTerminationBo bo) {
-        try {
-            Long taskId = bo.getTaskId();
-            Task task = taskService.getById(taskId);
-            if (task == null) {
-                throw new ServiceException("任务不存在!");
-            }
-            Instance instance = insService.getById(task.getInstanceId());
-            if (ObjectUtil.isNotNull(instance)) {
-                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
-            }
-            FlowParams flowParams = FlowParams.build()
-                .message(bo.getComment())
-                .flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
-                .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
-            taskService.termination(taskId, flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Long taskId = bo.getTaskId();
+        Task task = taskService.getById(taskId);
+        if (task == null) {
+            throw new ServiceException("任务不存在!");
+        }
+        Instance instance = insService.getById(task.getInstanceId());
+        if (ObjectUtil.isNotNull(instance)) {
+            BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
         }
+        FlowParams flowParams = FlowParams.build()
+            .message(bo.getComment())
+            .flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
+            .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
+        taskService.termination(taskId, flowParams);
+        return true;
     }
 
     /**
@@ -739,8 +748,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         Task task = taskService.getById(taskId);
         FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
         if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
-            if (flowNode.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
-                throw new ServiceException(task.getNodeName() + "不是会签节点!");
+            if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
+                throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
             }
         }
         // 设置任务状态并执行对应的任务操作
@@ -784,23 +793,18 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (CollUtil.isEmpty(taskIdList)) {
             return false;
         }
-        try {
-            List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
-            // 批量删除现有任务的办理人记录
-            if (CollUtil.isNotEmpty(flowTasks)) {
-                FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
-                List<User> userList = StreamUtils.toList(flowTasks, flowTask ->
-                    new FlowUser()
-                        .setType(TaskAssigneeType.APPROVER.getCode())
-                        .setProcessedBy(userId)
-                        .setAssociated(flowTask.getId()));
-                if (CollUtil.isNotEmpty(userList)) {
-                    FlowEngine.userService().saveBatch(userList);
-                }
+        List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
+        // 批量删除现有任务的办理人记录
+        if (CollUtil.isNotEmpty(flowTasks)) {
+            FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
+            List<User> userList = StreamUtils.toList(flowTasks, flowTask ->
+                new FlowUser()
+                    .setType(TaskAssigneeType.APPROVER.getCode())
+                    .setProcessedBy(userId)
+                    .setAssociated(flowTask.getId()));
+            if (CollUtil.isNotEmpty(userList)) {
+                FlowEngine.userService().saveBatch(userList);
             }
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
         }
         return true;
     }
@@ -840,21 +844,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public boolean urgeTask(FlowUrgeTaskBo bo) {
-        try {
-            if (CollUtil.isEmpty(bo.getTaskIdList())) {
-                return false;
-            }
-            List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
-            if (CollUtil.isEmpty(userList)) {
-                return false;
-            }
-            List<String> messageType = bo.getMessageType();
-            String message = bo.getMessage();
-            flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        if (CollUtil.isEmpty(bo.getTaskIdList())) {
+            return false;
+        }
+        List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
+        if (CollUtil.isEmpty(userList)) {
+            return false;
         }
+        List<String> messageType = bo.getMessageType();
+        String message = bo.getMessage();
+        flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
         return true;
     }
 }

+ 5 - 1
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java

@@ -18,6 +18,7 @@ import org.dromara.common.core.enums.BusinessStatusEnum;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.service.WorkflowService;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.domain.BaseEntity;
 import org.dromara.common.mybatis.core.page.PageQuery;
@@ -167,7 +168,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean deleteWithValidByIds(List<Long> ids) {
-        workflowService.deleteInstance(ids);
+        workflowService.deleteInstance(StreamUtils.toList(ids, Convert::toStr));
         return baseMapper.deleteByIds(ids) > 0;
     }
 
@@ -199,7 +200,10 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
                 testLeave.setApplyCode(businessCode);
             }
             testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
+            log.info("申请人提交");
         }
+        String status = BusinessStatusEnum.findByStatus(processEvent.getStatus());
+        log.info("当前流程状态为{}", status);
         baseMapper.updateById(testLeave);
     }
 

+ 15 - 25
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java

@@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.dto.CompleteTaskDTO;
 import org.dromara.common.core.domain.dto.StartProcessDTO;
 import org.dromara.common.core.domain.dto.StartProcessReturnDTO;
-import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.service.WorkflowService;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.warm.flow.orm.entity.FlowInstance;
@@ -46,7 +45,7 @@ public class WorkflowServiceImpl implements WorkflowService {
      * @return 结果
      */
     @Override
-    public boolean deleteInstance(List<Long> businessIds) {
+    public boolean deleteInstance(List<String> businessIds) {
         return flwInstanceService.deleteByBusinessIds(businessIds);
     }
 
@@ -161,28 +160,19 @@ public class WorkflowServiceImpl implements WorkflowService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean startCompleteTask(StartProcessDTO startProcess) {
-        try {
-            StartProcessBo processBo = new StartProcessBo();
-            processBo.setBusinessId(startProcess.getBusinessId());
-            processBo.setFlowCode(startProcess.getFlowCode());
-            processBo.setVariables(startProcess.getVariables());
-            processBo.setHandler(startProcess.getHandler());
-            processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
-
-            StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo);
-            CompleteTaskBo taskBo = new CompleteTaskBo();
-            taskBo.setTaskId(result.getTaskId());
-            taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
-            taskBo.setVariables(startProcess.getVariables());
-            taskBo.setHandler(startProcess.getHandler());
-
-            boolean flag = flwTaskService.completeTask(taskBo);
-            if (!flag) {
-                throw new ServiceException("流程发起异常");
-            }
-            return true;
-        } catch (Exception e) {
-            throw new ServiceException(e.getMessage());
-        }
+        StartProcessBo processBo = new StartProcessBo();
+        processBo.setBusinessId(startProcess.getBusinessId());
+        processBo.setFlowCode(startProcess.getFlowCode());
+        processBo.setVariables(startProcess.getVariables());
+        processBo.setHandler(startProcess.getHandler());
+        processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
+
+        StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo);
+        CompleteTaskBo taskBo = new CompleteTaskBo();
+        taskBo.setTaskId(result.getTaskId());
+        taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
+        taskBo.setVariables(startProcess.getVariables());
+        taskBo.setHandler(startProcess.getHandler());
+        return flwTaskService.completeTask(taskBo);
     }
 }

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

@@ -27,7 +27,10 @@
                 d.flow_code,
                 d.form_custom,
                 d.category,
-                COALESCE(t.form_path, d.form_path) as form_path,
+                COALESCE(
+                    NULLIF(TRIM(t.form_path), ''),
+                    NULLIF(TRIM(d.form_path), '')
+                ) AS form_path,
                 d.version,
                 uu.processed_by,
                 uu.type,

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

@@ -99,7 +99,7 @@ services:
     network_mode: "host"
 
   ruoyi-server1:
-    image: ruoyi/ruoyi-server:5.5.1
+    image: ruoyi/ruoyi-server:5.5.2
     container_name: ruoyi-server1
     environment:
       # 时区上海
@@ -115,7 +115,7 @@ services:
     network_mode: "host"
 
   ruoyi-server2:
-    image: ruoyi/ruoyi-server:5.5.1
+    image: ruoyi/ruoyi-server:5.5.2
     container_name: ruoyi-server2
     environment:
       # 时区上海
@@ -131,7 +131,7 @@ services:
     network_mode: "host"
 
   ruoyi-monitor-admin:
-    image: ruoyi/ruoyi-monitor-admin:5.5.1
+    image: ruoyi/ruoyi-monitor-admin:5.5.2
     container_name: ruoyi-monitor-admin
     environment:
       # 时区上海
@@ -143,7 +143,7 @@ services:
     network_mode: "host"
 
   ruoyi-snailjob-server:
-    image: ruoyi/ruoyi-snailjob-server:5.5.1
+    image: ruoyi/ruoyi-snailjob-server:5.5.2
     container_name: ruoyi-snailjob-server
     environment:
       # 时区上海

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

@@ -106,11 +106,6 @@ http {
             proxy_buffering off;
             # 禁用代理缓存
             proxy_cache off;
-            # 按 IP 限制连接数(防 CC 攻击) 小型站:10~20 就够 中型站:50~100
-            limit_conn perip 20;
-
-            # 按 Server 限制总并发连接数 根据服务器的最大并发处理能力来定 太小会限制合法用户访问,太大会占满服务器资源
-            limit_conn perserver 500;
             proxy_pass http://server/;
         }
 

+ 1 - 1
script/sql/oracle/oracle_ry_job.sql

@@ -738,7 +738,7 @@ CREATE TABLE sj_retry_summary
     id            number GENERATED ALWAYS AS IDENTITY,
     namespace_id  varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
     group_name    varchar2(64) DEFAULT ''                                 NULL,
-    scene_name    varchar2(50) DEFAULT ''                                 NULL,
+    scene_name    varchar2(64) DEFAULT ''                                 NULL,
     trigger_at    date         DEFAULT CURRENT_TIMESTAMP                  NOT NULL,
     running_num   number       DEFAULT 0                                  NOT NULL,
     finish_num    number       DEFAULT 0                                  NOT NULL,

+ 8 - 12
script/sql/oracle/oracle_ry_workflow.sql

@@ -55,13 +55,12 @@ create table FLOW_NODE
     DEFINITION_ID   NUMBER(20)    not null,
     NODE_CODE       VARCHAR2(100) not null,
     NODE_NAME       VARCHAR2(100),
-    NODE_RATIO      NUMBER(6, 3),
+    PERMISSION_FLAG VARCHAR2(200),
+    NODE_RATIO      VARCHAR2(200),
     COORDINATE      VARCHAR2(100),
     ANY_NODE_SKIP   VARCHAR2(100),
     LISTENER_TYPE   VARCHAR2(100),
     LISTENER_PATH   VARCHAR2(500),
-    HANDLER_TYPE    VARCHAR2(100),
-    HANDLER_PATH    VARCHAR2(400),
     FORM_CUSTOM     VARCHAR2(1)   default 'N',
     FORM_PATH       VARCHAR2(100),
     VERSION         VARCHAR2(20),
@@ -71,8 +70,7 @@ create table FLOW_NODE
     UPDATE_BY       VARCHAR2(64) default '',
     EXT             CLOB,
     DEL_FLAG        VARCHAR2(1)   default '0',
-    TENANT_ID       VARCHAR2(40),
-    PERMISSION_FLAG VARCHAR2(200)
+    TENANT_ID       VARCHAR2(40)
 );
 
 alter table FLOW_NODE
@@ -89,8 +87,6 @@ comment on column FLOW_NODE.COORDINATE is '坐标';
 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 '监听器路径';
-comment on column FLOW_NODE.HANDLER_TYPE is '处理器类型';
-comment on column FLOW_NODE.HANDLER_PATH is '处理器路径';
 comment on column FLOW_NODE.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
 comment on column FLOW_NODE.FORM_PATH is '审批表单路径';
 comment on column FLOW_NODE.VERSION is '版本';
@@ -493,11 +489,11 @@ INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '
 INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
 
 INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '流程达式定义菜单');
-INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
 
 INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, SYSDATE, NULL, NULL, '请假申请菜单');
 INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, SYSDATE, NULL, NULL, '');

+ 1 - 1
script/sql/postgres/postgres_ry_job.sql

@@ -684,7 +684,7 @@ CREATE TABLE sj_retry_summary
     id            bigserial PRIMARY KEY,
     namespace_id  varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
     group_name    varchar(64) NOT NULL DEFAULT '',
-    scene_name    varchar(50) NOT NULL DEFAULT '',
+    scene_name    varchar(64) NOT NULL DEFAULT '',
     trigger_at    timestamp   NOT NULL DEFAULT CURRENT_TIMESTAMP,
     running_num   int         NOT NULL DEFAULT 0,
     finish_num    int         NOT NULL DEFAULT 0,

+ 6 - 10
script/sql/postgres/postgres_ry_workflow.sql

@@ -51,13 +51,11 @@ CREATE TABLE flow_node
     node_code       varchar(100)  NOT NULL,
     node_name       varchar(100)  NULL,
     permission_flag varchar(200)  NULL,
-    node_ratio      numeric(6, 3) NULL,
+    node_ratio      varchar(200)  NULL,
     coordinate      varchar(100)  NULL,
     any_node_skip   varchar(100)  NULL,
     listener_type   varchar(100)  NULL,
     listener_path   varchar(400)  NULL,
-    handler_type    varchar(100)  NULL,
-    handler_path    varchar(400)  NULL,
     form_custom     bpchar(1)     NULL DEFAULT 'N':: character varying,
     form_path       varchar(100)  NULL,
     "version"       varchar(20)   NOT NULL,
@@ -83,8 +81,6 @@ COMMENT ON COLUMN flow_node.coordinate IS '坐标';
 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 '监听器路径';
-COMMENT ON COLUMN flow_node.handler_type IS '处理器类型';
-COMMENT ON COLUMN flow_node.handler_path IS '处理器路径';
 COMMENT ON COLUMN flow_node.form_custom IS '审批表单是否自定义(Y是 N否)';
 COMMENT ON COLUMN flow_node.form_path IS '审批表单路径';
 COMMENT ON COLUMN flow_node."version" IS '版本';
@@ -471,11 +467,11 @@ INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '
 INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, now(), NULL, NULL, '');
 
 INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '流程达式定义菜单');
-INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
 
 INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, now(), NULL, NULL, '请假申请菜单');
 INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, now(), NULL, NULL, '');

+ 1 - 1
script/sql/ry_job.sql

@@ -423,7 +423,7 @@ CREATE TABLE `sj_retry_summary`
     `id`            bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
     `namespace_id`  VARCHAR(64)     NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
     `group_name`    VARCHAR(64)     NOT NULL DEFAULT '' COMMENT '组名称',
-    `scene_name`    VARCHAR(50)     NOT NULL DEFAULT '' COMMENT '场景名称',
+    `scene_name`    VARCHAR(64)     NOT NULL DEFAULT '' COMMENT '场景名称',
     `trigger_at`    datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间',
     `running_num`   int             NOT NULL DEFAULT 0 COMMENT '重试中-日志数量',
     `finish_num`    int             NOT NULL DEFAULT 0 COMMENT '重试完成-日志数量',

+ 1 - 3
script/sql/ry_workflow.sql

@@ -33,13 +33,11 @@ CREATE TABLE `flow_node`
     `node_code`       varchar(100)    NOT NULL COMMENT '流程节点编码',
     `node_name`       varchar(100)  DEFAULT NULL COMMENT '流程节点名称',
     `permission_flag` varchar(200)  DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用@@隔开)',
-    `node_ratio`      decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值',
+    `node_ratio`      varchar(200)  DEFAULT NULL COMMENT '流程签署比例值',
     `coordinate`      varchar(100)  DEFAULT NULL COMMENT '坐标',
     `any_node_skip`   varchar(100)  DEFAULT NULL COMMENT '任意结点跳转',
     `listener_type`   varchar(100)  DEFAULT NULL COMMENT '监听器类型',
     `listener_path`   varchar(400)  DEFAULT NULL COMMENT '监听器路径',
-    `handler_type`    varchar(100)  DEFAULT NULL COMMENT '处理器类型',
-    `handler_path`    varchar(400)  DEFAULT NULL COMMENT '处理器路径',
     `form_custom`     char(1)       DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
     `form_path`       varchar(100)  DEFAULT NULL COMMENT '审批表单路径',
     `version`         varchar(20)     NOT NULL COMMENT '版本',

+ 1 - 1
script/sql/sqlserver/sqlserver_ry_job.sql

@@ -2248,7 +2248,7 @@ CREATE TABLE sj_retry_summary
     id            bigint       NOT NULL PRIMARY KEY IDENTITY,
     namespace_id  nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
     group_name    nvarchar(64) NOT NULL DEFAULT '',
-    scene_name    nvarchar(50) NOT NULL DEFAULT '',
+    scene_name    nvarchar(64) NOT NULL DEFAULT '',
     trigger_at    datetime2    NOT NULL DEFAULT CURRENT_TIMESTAMP,
     running_num   int          NOT NULL DEFAULT 0,
     finish_num    int          NOT NULL DEFAULT 0,

+ 7 - 23
script/sql/sqlserver/sqlserver_ry_workflow.sql

@@ -174,13 +174,11 @@ CREATE TABLE flow_node (
     node_code nvarchar(100) NOT NULL,
     node_name nvarchar(100) NULL,
     permission_flag nvarchar(200) NULL,
-    node_ratio decimal(6,3)  NULL,
+    node_ratio nvarchar(200)  NULL,
     coordinate nvarchar(100) NULL,
     any_node_skip nvarchar(100) NULL,
     listener_type nvarchar(100) NULL,
     listener_path nvarchar(400) NULL,
-    handler_type nvarchar(100) NULL,
-    handler_path nvarchar(400) NULL,
     form_custom nchar(1) DEFAULT('N') NULL,
     form_path nvarchar(100) NULL,
     version nvarchar(20) NOT NULL,
@@ -275,20 +273,6 @@ EXEC sp_addextendedproperty
 'COLUMN', N'listener_path'
 GO
 
-EXEC sp_addextendedproperty
-'MS_Description', N'处理器类型',
-'SCHEMA', N'dbo',
-'TABLE', N'flow_node',
-'COLUMN', N'handler_type'
-GO
-
-EXEC sp_addextendedproperty
-'MS_Description', N'处理器路径',
-'SCHEMA', N'dbo',
-'TABLE', N'flow_node',
-'COLUMN', N'handler_path'
-GO
-
 EXEC sp_addextendedproperty
 'MS_Description', N'审批表单是否自定义(Y是 N否)',
 'SCHEMA', N'dbo',
@@ -1261,7 +1245,7 @@ EXEC sp_addextendedproperty
     'COLUMN', N'component_name'
 GO
 
--- method_name 字段注释
+EXEC sp_addextendedproperty
     'MS_Description', N'方法名',
     'SCHEMA', N'dbo',
     'TABLE', N'flow_spel',
@@ -1618,15 +1602,15 @@ GO
 
 INSERT sys_menu VALUES (11801, N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单');
 GO
-INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
 
 -- 请假测试相关按钮

+ 5 - 5
script/sql/update/oracle/update_5.4.1-5.5.0.sql

@@ -38,11 +38,11 @@ INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'i
 INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, SYSDATE, 1, SYSDATE);
 
 INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '流程达式定义菜单');
-INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
 
 ALTER TABLE flow_definition ADD model_value VARCHAR2(40) DEFAULT 'CLASSICS' NOT NULL;
 COMMENT ON COLUMN flow_definition.model_value IS '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)';

+ 4 - 0
script/sql/update/oracle/update_5.5.1-5.5.2.sql

@@ -0,0 +1,4 @@
+ALTER TABLE flow_node MODIFY (node_ratio VARCHAR2(200) DEFAULT NULL NULL);
+COMMENT ON COLUMN flow_node.node_ratio IS '流程签署比例值';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;

+ 5 - 5
script/sql/update/postgres/update_5.4.1-5.5.0.sql

@@ -37,11 +37,11 @@ INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'i
 INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, now(), 1, now());
 
 INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '流程达式定义菜单');
-INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
 
 ALTER TABLE flow_definition ADD COLUMN model_value VARCHAR(40) NOT NULL DEFAULT 'CLASSICS';
 COMMENT ON COLUMN flow_definition.model_value IS '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)';

+ 10 - 10
script/sql/update/postgres/update_5.5.0-5.5.1.sql

@@ -1,25 +1,25 @@
-ALTER TABLE flow_definition ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
-ALTER TABLE flow_definition ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_definition ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_definition ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_definition.create_by IS '创建人';
 COMMENT ON COLUMN flow_definition.update_by IS '更新人';
 
-ALTER TABLE flow_node ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
-ALTER TABLE flow_node ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_node ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_node ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_node.create_by IS '创建人';
 COMMENT ON COLUMN flow_node.update_by IS '更新人';
 
-ALTER TABLE flow_skip ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
-ALTER TABLE flow_skip ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_skip ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_skip ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_skip.create_by IS '创建人';
 COMMENT ON COLUMN flow_skip.update_by IS '更新人';
 
-ALTER TABLE flow_instance ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_instance ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_instance.update_by IS '更新人';
 
-ALTER TABLE flow_task ADD create_by VARCHAR2(64) DEFAULT '' NOT NULL;
-ALTER TABLE flow_task ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_task ADD create_by VARCHAR(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_task ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_task.create_by IS '创建人';
 COMMENT ON COLUMN flow_task.update_by IS '更新人';
 
-ALTER TABLE flow_user ADD update_by VARCHAR2(64) DEFAULT '' NOT NULL;
+ALTER TABLE flow_user ADD update_by VARCHAR(64) DEFAULT '' NOT NULL;
 COMMENT ON COLUMN flow_user.update_by IS '更新人';

+ 6 - 0
script/sql/update/postgres/update_5.5.1-5.5.2.sql

@@ -0,0 +1,6 @@
+ALTER TABLE flow_node
+    ALTER COLUMN node_ratio TYPE VARCHAR(200),
+    ALTER COLUMN node_ratio DROP NOT NULL;
+COMMENT ON COLUMN flow_node.node_ratio IS '流程签署比例值';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;

+ 5 - 5
script/sql/update/sqlserver/update_5.4.1-5.5.0.sql

@@ -123,15 +123,15 @@ GO
 
 INSERT sys_menu VALUES (11801, N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单');
 GO
-INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
-INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
+INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
 GO
 
 ALTER TABLE flow_definition ADD model_value VARCHAR(40) NOT NULL CONSTRAINT DF_flow_definition_model_value DEFAULT 'CLASSICS';

+ 22 - 0
script/sql/update/sqlserver/update_5.5.1-5.5.2.sql

@@ -0,0 +1,22 @@
+ALTER TABLE flow_node ALTER COLUMN node_ratio nvarchar(200) NULL;
+GO
+IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_his_task',
+'COLUMN', N'collaborator')) > 0)
+  EXEC sp_updateextendedproperty
+'MS_Description', N'流程签署比例值',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_ratio'
+ELSE
+  EXEC sp_addextendedproperty
+'MS_Description', N'流程签署比例值',
+'SCHEMA', N'dbo',
+'TABLE', N'flow_node',
+'COLUMN', N'node_ratio'
+GO
+ALTER TABLE flow_node DROP COLUMN handler_type;
+GO
+ALTER TABLE flow_node DROP COLUMN handler_path;
+GO

+ 5 - 5
script/sql/update/update_5.4.1-5.5.0.sql

@@ -22,11 +22,11 @@ INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'i
 INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, sysdate(), 1, sysdate());
 
 INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', '2', 'spel',    'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, sysdate(), 1, sysdate(), '流程达式定义菜单');
-INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
-INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, '');
+INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, sysdate(), NULL, NULL, '');
 
 ALTER TABLE `flow_definition`
     ADD COLUMN `model_value` varchar(40) NOT NULL DEFAULT 'CLASSICS' COMMENT '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)' AFTER `flow_name`;

+ 3 - 0
script/sql/update/update_5.5.1-5.5.2.sql

@@ -0,0 +1,3 @@
+ALTER TABLE flow_node MODIFY COLUMN node_ratio varchar(200) NULL COMMENT '流程签署比例值';
+ALTER TABLE flow_node DROP COLUMN handler_type;
+ALTER TABLE flow_node DROP COLUMN handler_path;