Prechádzať zdrojové kódy

Merge branch '3.1.1' of VberAdmin/VberAdminPlusV3 into dev

YueYunyun 6 mesiacov pred
rodič
commit
874f0ba652
92 zmenil súbory, kde vykonal 1523 pridanie a 684 odobranie
  1. BIN
      SERVER/VberAdminPlusV3/.script/bpmn/模型.zip
  2. 5 5
      SERVER/VberAdminPlusV3/pom.xml
  3. 2 2
      SERVER/VberAdminPlusV3/vber-admin/Dockerfile
  4. 3 0
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/AuthController.java
  5. 7 11
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application.yml
  6. 1 0
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/logback-plus.xml
  7. 0 52
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/AsyncConfig.java
  8. 0 14
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ThreadPoolConfig.java
  9. 2 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/TaskAssigneeDTO.java
  10. 5 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessEvent.java
  11. 5 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessTaskEvent.java
  12. 5 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/ServiceException.java
  13. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/SseException.java
  14. 10 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java
  15. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java
  16. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/NetUtils.java
  17. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ObjectUtils.java
  18. 2 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveStrategy.java
  19. 0 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  20. 9 8
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/config/ApiDecryptAutoConfiguration.java
  21. 15 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/CellMergeStrategy.java
  22. 2 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DropDownOptions.java
  23. 3 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java
  24. 21 9
      SERVER/VberAdminPlusV3/vber-common/vber-common-log/src/main/java/com/vber/common/log/aspect/LogAspect.java
  25. 54 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAdvice.java
  26. 0 51
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAspect.java
  27. 39 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionPointcut.java
  28. 33 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionPointcutAdvisor.java
  29. 8 6
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/config/MybatisPlusConfig.java
  30. 35 13
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/mapper/BaseMapperPlus.java
  31. 8 104
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusDataPermissionHandler.java
  32. 6 13
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
  33. 1 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/sharding/LongDateShardingAlgorithm.java
  34. 2 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/core/OssClient.java
  35. 4 7
      SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/utils/RedisUtils.java
  36. 3 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/utils/SequenceUtils.java
  37. 6 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-satoken/src/main/java/com/vber/common/satoken/core/dao/PlusSaTokenDao.java
  38. 24 6
      SERVER/VberAdminPlusV3/vber-common/vber-common-satoken/src/main/java/com/vber/common/satoken/core/service/SaPermissionImpl.java
  39. 12 15
      SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/config/FilterConfig.java
  40. 2 2
      SERVER/VberAdminPlusV3/vber-extend/vber-job-admin/Dockerfile
  41. 1 1
      SERVER/VberAdminPlusV3/vber-extend/vber-job-admin/src/main/java/com/vber/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
  42. 2 2
      SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/Dockerfile
  43. 2 0
      SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/MonitorAdminApplication.java
  44. 0 31
      SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/config/AdminServerConfig.java
  45. 5 13
      SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/config/SecurityConfig.java
  46. 3 0
      SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/resources/application.yml
  47. 20 14
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/reactor/core/ProjectReactor.java
  48. 2 0
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/reactor/domain/ProjectInfo.java
  49. 3 2
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/test/java/com/vber/reactor/ProjectReactorTest.java
  50. 5 2
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysProfileController.java
  51. 2 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysUserController.java
  52. 90 0
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/domain/vo/ProfileUserVo.java
  53. 15 6
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/mapper/SysMenuMapper.java
  54. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java
  55. 2 16
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysMenuServiceImpl.java
  56. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysPostServiceImpl.java
  57. 10 10
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysRoleServiceImpl.java
  58. 5 5
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysTaskAssigneeServiceImpl.java
  59. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java
  60. 4 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/constant/FlowConstant.java
  61. 25 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskAssigneeEnum.java
  62. 22 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java
  63. 17 5
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java
  64. 38 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowUrgeTaskBo.java
  65. 39 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowVariableBo.java
  66. 5 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TestLeaveBo.java
  67. 23 17
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java
  68. 14 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwCommonService.java
  69. 17 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java
  70. 27 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwTaskService.java
  71. 5 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/ITestLeaveService.java
  72. 15 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java
  73. 7 8
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java
  74. 93 14
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java
  75. 4 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java
  76. 75 49
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
  77. 136 47
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java
  78. 28 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java
  79. 31 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java
  80. 38 19
      UI/VAP_V3.VUE/src/api/workflow/_processInstance.ts
  81. 28 21
      UI/VAP_V3.VUE/src/api/workflow/_task.ts
  82. 94 0
      UI/VAP_V3.VUE/src/components/process/MessageType.vue
  83. 1 1
      UI/VAP_V3.VUE/src/components/process/SubmitVerify.vue
  84. 8 3
      UI/VAP_V3.VUE/src/core/services/PermissionService.ts
  85. 7 5
      UI/VAP_V3.VUE/src/core/services/RequestService.ts
  86. 3 2
      UI/VAP_V3.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue
  87. 3 3
      UI/VAP_V3.VUE/src/router/index.ts
  88. 10 1
      UI/VAP_V3.VUE/src/stores/_auth.ts
  89. 11 1
      UI/VAP_V3.VUE/src/views/tool/gen/index.vue
  90. 69 9
      UI/VAP_V3.VUE/src/views/workflow/processDefinition/index.vue
  91. 90 22
      UI/VAP_V3.VUE/src/views/workflow/processInstance/index.vue
  92. 28 1
      UI/VAP_V3.VUE/src/views/workflow/task/allTaskWaiting.vue

BIN
SERVER/VberAdminPlusV3/.script/bpmn/模型.zip


+ 5 - 5
SERVER/VberAdminPlusV3/pom.xml

@@ -22,9 +22,9 @@
         <java.version>17</java.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <spring-boot.version>3.4.7</spring-boot.version>
-        <spring-boot-admin.version>3.4.7</spring-boot-admin.version>
-        <springdoc.version>2.8.8</springdoc.version>
+        <spring-boot.version>3.5.4</spring-boot.version>
+        <spring-boot-admin.version>3.5.1</spring-boot-admin.version>
+        <springdoc.version>2.8.9</springdoc.version>
         <mybatis.version>3.5.16</mybatis.version>
         <mybatis-plus.version>3.5.12</mybatis-plus.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
@@ -41,7 +41,7 @@
         <lock4j.version>2.2.7</lock4j.version>
         <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
-        <lombok.version>1.18.36</lombok.version>
+        <lombok.version>1.18.38</lombok.version>
         <bouncycastle.version>1.80</bouncycastle.version>
         <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
@@ -57,7 +57,7 @@
         <anyline.version>8.7.2-20250603</anyline.version>
 
         <!-- 工作流配置 -->
-        <warm-flow.version>1.7.4</warm-flow.version>
+        <warm-flow.version>1.8.0-m1</warm-flow.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>

+ 2 - 2
SERVER/VberAdminPlusV3/vber-admin/Dockerfile

@@ -1,6 +1,6 @@
 # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
-FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
-#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
 #FROM openjdk:17.0.2-oraclelinux8
 
 LABEL maintainer="IwbY"

+ 3 - 0
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/AuthController.java

@@ -14,6 +14,8 @@ import com.vber.common.core.domain.model.SocialLoginBody;
 import com.vber.common.core.utils.*;
 import com.vber.common.encrypt.annotation.ApiEncrypt;
 import com.vber.common.json.utils.JsonUtils;
+import com.vber.common.ratelimiter.annotation.RateLimiter;
+import com.vber.common.ratelimiter.enums.LimitType;
 import com.vber.common.satoken.utils.LoginHelper;
 import com.vber.common.social.config.properties.SocialLoginConfigProperties;
 import com.vber.common.social.config.properties.SocialProperties;
@@ -198,6 +200,7 @@ public class AuthController {
      *
      * @return 租户列表
      */
+    @RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
     @GetMapping("/tenant/list")
     public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
         // 返回对象

+ 7 - 11
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application.yml

@@ -71,6 +71,13 @@ spring:
     # 开启虚拟线程 仅jdk21可用
     virtual:
       enabled: false
+  task:
+    execution:
+      # 从 springboot 3.5 开始 spring自带线程池
+      # 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
+      thread-name-prefix: async-
+      # 由spring自己初始化线程池
+      mode: force
   # 资源信息
   messages:
     # 国际化资源文件路径
@@ -229,17 +236,6 @@ xss:
   excludeUrls:
     - /system/notice
 
-
-# 全局线程池相关配置
-# 如使用JDK21请直接使用虚拟线程 不要开启此配置
-thread-pool:
-  # 是否开启线程池
-  enabled: false
-  # 队列最大长度
-  queueCapacity: 128
-  # 线程池维护线程所允许的空闲时间
-  keepAliveSeconds: 300
-
 --- # 分布式锁 lock4j 全局配置
 lock4j:
   # 获取分布式锁超时时间,默认为 3000 毫秒

+ 1 - 0
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/logback-plus.xml

@@ -120,6 +120,7 @@
     <!--系统操作日志-->
     <root level="info">
         <appender-ref ref="console"/>
+        <appender-ref ref="file_console"/>
         <appender-ref ref="async_info"/>
         <appender-ref ref="async_error"/>
         <appender-ref ref="file_console"/>

+ 0 - 52
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/AsyncConfig.java

@@ -1,52 +0,0 @@
-package com.vber.common.core.config;
-
-import cn.hutool.core.util.ArrayUtil;
-import com.vber.common.core.exception.ServiceException;
-import com.vber.common.core.utils.SpringUtils;
-import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.core.task.VirtualThreadTaskExecutor;
-import org.springframework.scheduling.annotation.AsyncConfigurer;
-
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-
-/**
- * 异步配置
- *
- * @author Iwb
- */
-@AutoConfiguration
-@ConditionalOnProperty(prefix = "spring.threads.virtual", name = "enabled", havingValue = "false")
-public class AsyncConfig implements AsyncConfigurer {
-
-    /**
-     * 自定义 @Async 注解使用系统线程池
-     */
-    @Override
-    public Executor getAsyncExecutor() {
-        if (SpringUtils.isVirtual()) {
-            return new VirtualThreadTaskExecutor("async-");
-        }
-        return SpringUtils.getBean("scheduledExecutorService");
-    }
-
-    /**
-     * 异步执行异常处理
-     */
-    @Override
-    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
-        return (throwable, method, objects) -> {
-            throwable.printStackTrace();
-            StringBuilder sb = new StringBuilder();
-            sb.append("Exception message - ").append(throwable.getMessage())
-                    .append(", Method name - ").append(method.getName());
-            if (ArrayUtil.isNotEmpty(objects)) {
-                sb.append(", Parameter value - ").append(Arrays.toString(objects));
-            }
-            throw new ServiceException(sb.toString());
-        };
-    }
-
-}

+ 0 - 14
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ThreadPoolConfig.java

@@ -7,11 +7,9 @@ import jakarta.annotation.PreDestroy;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.task.VirtualThreadTaskExecutor;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -34,18 +32,6 @@ public class ThreadPoolConfig {
 
     private ScheduledExecutorService scheduledExecutorService;
 
-    @Bean(name = "threadPoolTaskExecutor")
-    @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
-    public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
-        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        executor.setCorePoolSize(core);
-        executor.setMaxPoolSize(core * 2);
-        executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
-        executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
-        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
-        return executor;
-    }
-
     /**
      * 执行周期性或定时任务
      */

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/TaskAssigneeDTO.java

@@ -55,14 +55,14 @@ public class TaskAssigneeDTO implements Serializable {
             Function<T, String> storageId,
             Function<T, String> handlerCode,
             Function<T, String> handlerName,
-            Function<T, Long> groupName,
+            Function<T, String> groupName,
             Function<T, Date> createTimeMapper) {
         return sourceList.stream()
                 .map(item -> new TaskHandler(
                         storageId.apply(item),
                         handlerCode.apply(item),
                         handlerName.apply(item),
-                        groupName != null ? String.valueOf(groupName.apply(item)) : null,
+                        groupName.apply(item),
                         createTimeMapper.apply(item)
                 )).collect(Collectors.toList());
     }

+ 5 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessEvent.java

@@ -27,6 +27,11 @@ public class ProcessEvent implements Serializable {
      */
     private String flowCode;
 
+    /**
+     * 实例id
+     */
+    private Long instanceId;
+
     /**
      * 业务id
      */

+ 5 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessTaskEvent.java

@@ -47,6 +47,11 @@ public class ProcessTaskEvent implements Serializable {
      */
     private Long taskId;
 
+    /**
+     * 实例id
+     */
+    private Long instanceId;
+
     /**
      * 业务id
      */

+ 5 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/ServiceException.java

@@ -1,5 +1,6 @@
 package com.vber.common.core.exception;
 
+import cn.hutool.core.text.StrFormatter;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -45,6 +46,10 @@ public final class ServiceException extends RuntimeException {
         this.code = code;
     }
 
+    public ServiceException(String message, Object... args) {
+        this.message = StrFormatter.format(message, args);
+    }
+
     @Override
     public String getMessage() {
         return message;

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/SseException.java

@@ -11,7 +11,7 @@ import java.io.Serial;
 /**
  * sse 特制异常
  *
- * @author LionLi
+ * @author Iwb
  */
 @Data
 @EqualsAndHashCode(callSuper = true)

+ 10 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java

@@ -13,7 +13,6 @@ import java.util.Map;
  * @author Iwb
  */
 public interface WorkflowService {
-
     /**
      * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
      *
@@ -82,6 +81,7 @@ public interface WorkflowService {
      * completeTask.getVariables().put("ignore", true);
      *
      * @param completeTask 参数
+     * @return 结果
      */
     boolean completeTask(CompleteTaskDTO completeTask);
 
@@ -90,6 +90,15 @@ public interface WorkflowService {
      *
      * @param taskId  任务ID
      * @param message 办理意见
+     * @return 结果
      */
     boolean completeTask(Long taskId, String message);
+
+    /**
+     * 启动流程并办理第一个任务
+     *
+     * @param startProcess 参数
+     * @return 结果
+     */
+    boolean startCompleteTask(StartProcessDTO startProcess);
 }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java

@@ -293,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
 
         // 校验时间跨度不超过最大限制
         if (diff > maxValue) {
-            throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
+            throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
         }
     }
 

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/NetUtils.java

@@ -15,7 +15,7 @@ import java.net.UnknownHostException;
 /**
  * 增强网络相关工具类
  *
- * @author 秋辞未寒
+ * @author Iwb
  */
 @Slf4j
 @NoArgsConstructor(access = AccessLevel.PRIVATE)

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ObjectUtils.java

@@ -9,7 +9,7 @@ import java.util.function.Function;
 /**
  * 对象工具类
  *
- * @author 秋辞未寒
+ * @author Iwb
  */
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class ObjectUtils extends ObjectUtil {

+ 2 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveStrategy.java

@@ -1,5 +1,6 @@
 package com.vber.common.sensitive.core;
 
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.DesensitizedUtil;
 import lombok.AllArgsConstructor;
 
@@ -52,7 +53,7 @@ public enum SensitiveStrategy {
     /**
      * 用户ID
      */
-    USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
+    USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
 
     /**
      * 密码

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

@@ -1,5 +1,4 @@
 com.vber.common.core.config.ApplicationConfig
-com.vber.common.core.config.AsyncConfig
 com.vber.common.core.config.VbConfig
 com.vber.common.core.config.ThreadPoolConfig
 com.vber.common.core.config.ValidatorConfig

+ 9 - 8
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/config/ApiDecryptAutoConfiguration.java

@@ -6,6 +6,7 @@ import jakarta.servlet.DispatcherType;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistration;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 
@@ -20,13 +21,13 @@ import org.springframework.context.annotation.Bean;
 public class ApiDecryptAutoConfiguration {
 
     @Bean
-    public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
-        FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
-        registration.setDispatcherTypes(DispatcherType.REQUEST);
-        registration.setFilter(new CryptoFilter(properties));
-        registration.addUrlPatterns("/*");
-        registration.setName("cryptoFilter");
-        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
-        return registration;
+    @FilterRegistration(
+            name = "cryptoFilter",
+            urlPatterns = "/*",
+            order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
+            dispatcherTypes = DispatcherType.REQUEST
+    )
+    public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
+        return new CryptoFilter(properties);
     }
 }

+ 15 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/CellMergeStrategy.java

@@ -3,6 +3,8 @@ package com.vber.common.excel.core;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
 import cn.idev.excel.metadata.Head;
 import cn.idev.excel.write.handler.WorkbookWriteHandler;
@@ -70,8 +72,17 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
         if (CollUtil.isEmpty(list)) {
             return cellList;
         }
-        Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
-
+        Class<?> clazz = list.get(0).getClass();
+        boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
+        Field[] fields = ReflectUtils.getFields(clazz, field -> {
+            if ("serialVersionUID".equals(field.getName())) {
+                return false;
+            }
+            if (field.isAnnotationPresent(ExcelIgnore.class)) {
+                return false;
+            }
+            return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
+        });
         // 有注解的字段
         List<Field> mergeFields = new ArrayList<>();
         List<Integer> mergeFieldsIndex = new ArrayList<>();
@@ -91,9 +102,10 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
         Map<Field, RepeatCell> map = new HashMap<>();
         // 生成两两合并单元格
         for (int i = 0; i < list.size(); i++) {
+            Object rowObj = list.get(i);
             for (int j = 0; j < mergeFields.size(); j++) {
                 Field field = mergeFields.get(j);
-                Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
+                Object val = ReflectUtils.invokeGetter(rowObj, field.getName());
 
                 int colNum = mergeFieldsIndex.get(j);
                 if (!map.containsKey(field)) {

+ 2 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DropDownOptions.java

@@ -1,5 +1,6 @@
 package com.vber.common.excel.core;
 
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.StrUtil;
 import com.vber.common.core.exception.ServiceException;
 import lombok.AllArgsConstructor;
@@ -65,7 +66,7 @@ public class DropDownOptions {
         StringBuilder stringBuffer = new StringBuilder();
         String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
         for (int i = 0; i < vars.length; i++) {
-            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
+            String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
             if (!var.matches(regex)) {
                 throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
             }

+ 3 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java

@@ -1,6 +1,7 @@
 package com.vber.common.excel.core;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -103,7 +104,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 if (StringUtils.isNotBlank(dictType)) {
                     // 如果传递了字典名,则依据字典建立下拉
                     Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
-                            .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
+                            .orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
                             .values();
                     options = new ArrayList<>(values);
                 } else if (StringUtils.isNotBlank(converterExp)) {
@@ -115,7 +116,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
                 ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
                 List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
-                options = StreamUtils.toList(values, String::valueOf);
+                options = StreamUtils.toList(values, Convert::toStr);
             }
             if (ObjectUtil.isNotEmpty(options)) {
                 // 仅当下拉可选项不为空时执行

+ 21 - 9
SERVER/VberAdminPlusV3/vber-common/vber-common-log/src/main/java/com/vber/common/log/aspect/LogAspect.java

@@ -27,9 +27,7 @@ import org.springframework.http.HttpMethod;
 import org.springframework.validation.BindingResult;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.StringJoiner;
+import java.util.*;
 
 /**
  * 操作日志记录处理
@@ -176,14 +174,28 @@ public class LogAspect {
         if (ArrayUtil.isEmpty(paramsArray)) {
             return params.toString();
         }
+        String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
         for (Object o : paramsArray) {
             if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
-                String str = JsonUtils.toJsonString(o);
-                Dict dict = JsonUtils.parseMap(str);
-                if (MapUtil.isNotEmpty(dict)) {
-                    MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
-                    MapUtil.removeAny(dict, excludeParamNames);
-                    str = JsonUtils.toJsonString(dict);
+                String str = "";
+                if (o instanceof List<?> list) {
+                    List<Dict> list1 = new ArrayList<>();
+                    for (Object obj : list) {
+                        String str1 = JsonUtils.toJsonString(obj);
+                        Dict dict = JsonUtils.parseMap(str1);
+                        if (MapUtil.isNotEmpty(dict)) {
+                            MapUtil.removeAny(dict, exclude);
+                            list1.add(dict);
+                        }
+                    }
+                    str = JsonUtils.toJsonString(list1);
+                } else {
+                    str = JsonUtils.toJsonString(o);
+                    Dict dict = JsonUtils.parseMap(str);
+                    if (MapUtil.isNotEmpty(dict)) {
+                        MapUtil.removeAny(dict, exclude);
+                        str = JsonUtils.toJsonString(dict);
+                    }
                 }
                 params.add(str);
             }

+ 54 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAdvice.java

@@ -0,0 +1,54 @@
+package com.vber.common.mybatis.aspect;
+
+import com.vber.common.mybatis.annotation.DataPermission;
+import com.vber.common.mybatis.helper.DataPermissionHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * 数据权限注解Advice
+ *
+ * @author Iwb
+ */
+@Slf4j
+public class DataPermissionAdvice implements MethodInterceptor {
+
+    @Override
+    public Object invoke(MethodInvocation invocation) throws Throwable {
+        Object target = invocation.getThis();
+        Method method = invocation.getMethod();
+        Object[] args = invocation.getArguments();
+        // 设置权限注解
+        DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
+        try {
+            // 执行代理方法
+            return invocation.proceed();
+        } finally {
+            // 清除权限注解
+            DataPermissionHelper.removePermission();
+        }
+    }
+
+    /**
+     * 获取数据权限注解
+     */
+    private DataPermission getDataPermissionAnnotation(Object target, Method method, Object[] args) {
+        DataPermission dataPermission = method.getAnnotation(DataPermission.class);
+        // 优先获取方法上的注解
+        if (dataPermission != null) {
+            return dataPermission;
+        }
+        // 方法上没有注解,则获取类上的注解
+        Class<?> targetClass = target.getClass();
+        // 如果是 JDK 动态代理,则获取真实的Class实例
+        if (Proxy.isProxyClass(targetClass)) {
+            targetClass = targetClass.getInterfaces()[0];
+        }
+        dataPermission = targetClass.getAnnotation(DataPermission.class);
+        return dataPermission;
+    }
+}

+ 0 - 51
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAspect.java

@@ -1,51 +0,0 @@
-package com.vber.common.mybatis.aspect;
-
-
-import com.vber.common.mybatis.annotation.DataPermission;
-import com.vber.common.mybatis.helper.DataPermissionHelper;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.JoinPoint;
-import org.aspectj.lang.annotation.AfterReturning;
-import org.aspectj.lang.annotation.AfterThrowing;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Before;
-
-/**
- * 数据权限处理
- *
- * @author Iwb
- */
-@Slf4j
-@Aspect
-public class DataPermissionAspect {
-
-    /**
-     * 处理请求前执行
-     */
-    @Before(value = "@annotation(dataPermission)")
-    public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
-        DataPermissionHelper.setPermission(dataPermission);
-    }
-
-    /**
-     * 处理完请求后执行
-     *
-     * @param joinPoint 切点
-     */
-    @AfterReturning(pointcut = "@annotation(dataPermission)")
-    public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
-        DataPermissionHelper.removePermission();
-    }
-
-    /**
-     * 拦截异常操作
-     *
-     * @param joinPoint 切点
-     * @param e         异常
-     */
-    @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
-    public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
-        DataPermissionHelper.removePermission();
-    }
-
-}

+ 39 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionPointcut.java

@@ -0,0 +1,39 @@
+package com.vber.common.mybatis.aspect;
+
+import com.vber.common.mybatis.annotation.DataPermission;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.support.StaticMethodMatcherPointcut;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * 数据权限匹配切点
+ *
+ * @author Iwb
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
+
+    @Override
+    public boolean matches(Method method, Class<?> targetClass) {
+        // 优先匹配方法
+        // 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
+        if (method.isAnnotationPresent(DataPermission.class)) {
+            return true;
+        }
+
+        // MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
+        Class<?> targetClassRef = targetClass;
+        if (Proxy.isProxyClass(targetClassRef)) {
+            // 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
+            // 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
+            // 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
+            targetClassRef = targetClass.getInterfaces()[0];
+
+        }
+        return targetClassRef.isAnnotationPresent(DataPermission.class);
+    }
+
+}

+ 33 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionPointcutAdvisor.java

@@ -0,0 +1,33 @@
+package com.vber.common.mybatis.aspect;
+
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+
+/**
+ * 数据权限注解切面定义
+ *
+ * @author Iwb
+ */
+@SuppressWarnings("all")
+public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
+
+    private final Advice advice;
+    private final Pointcut pointcut;
+
+    public DataPermissionPointcutAdvisor() {
+        this.advice = new DataPermissionAdvice();
+        this.pointcut = new DataPermissionPointcut();
+    }
+
+    @Override
+    public Pointcut getPointcut() {
+        return this.pointcut;
+    }
+
+    @Override
+    public Advice getAdvice() {
+        return this.advice;
+    }
+
+}

+ 8 - 6
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/config/MybatisPlusConfig.java

@@ -11,15 +11,17 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
 import com.vber.common.core.factory.YmlPropertySourceFactory;
 import com.vber.common.core.utils.SpringUtils;
-import com.vber.common.mybatis.aspect.DataPermissionAspect;
+import com.vber.common.mybatis.aspect.DataPermissionPointcutAdvisor;
 import com.vber.common.mybatis.handler.InjectionMetaObjectHandler;
 import com.vber.common.mybatis.handler.MybatisExceptionHandler;
 import com.vber.common.mybatis.handler.PlusPostInitTableInfoHandler;
 import com.vber.common.mybatis.interceptor.PlusDataPermissionInterceptor;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.annotation.Role;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 /**
@@ -27,6 +29,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  *
  * @author Iwb
  */
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 @EnableTransactionManagement(proxyTargetClass = true)
 @MapperScan("${mybatis-plus.mapperPackage}")
 @PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
@@ -54,15 +57,16 @@ public class MybatisPlusConfig {
      * 数据权限拦截器
      */
     public PlusDataPermissionInterceptor dataPermissionInterceptor() {
-        return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
+        return new PlusDataPermissionInterceptor();
     }
 
     /**
      * 数据权限切面处理器
      */
     @Bean
-    public DataPermissionAspect dataPermissionAspect() {
-        return new DataPermissionAspect();
+    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+    public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
+        return new DataPermissionPointcutAdvisor();
     }
 
     /**
@@ -115,7 +119,6 @@ public class MybatisPlusConfig {
         return new PlusPostInitTableInfoHandler();
     }
 
-
     /**
      * PaginationInnerInterceptor 分页插件,自动识别数据库类型
      * https://baomidou.com/pages/97710a/
@@ -136,5 +139,4 @@ public class MybatisPlusConfig {
      * https://baomidou.com/pages/2a45ff/
      */
 
-
 }

+ 35 - 13
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/mapper/BaseMapperPlus.java

@@ -6,11 +6,13 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.toolkit.Db;
 import com.vber.common.core.utils.MapstructUtils;
 import com.vber.common.core.utils.StreamUtils;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.logging.Log;
 import org.apache.ibatis.logging.LogFactory;
 
@@ -26,11 +28,9 @@ import java.util.function.Function;
  * @param <T> table 泛型
  * @param <V> vo 泛型
  * @author Iwb
- * @since 2021-05-13
  */
 @SuppressWarnings("unchecked")
 public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
-
     Log log = LogFactory.getLog(BaseMapperPlus.class);
 
     /**
@@ -130,7 +130,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的单个VO对象
      */
     default V selectVoById(Serializable id) {
-        return selectVoById(id, this.currentVoClass());
+        return this.selectVoById(id, this.currentVoClass());
     }
 
     /**
@@ -156,7 +156,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的VO对象列表
      */
     default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
-        return selectVoByIds(idList, this.currentVoClass());
+        return this.selectVoByIds(idList, this.currentVoClass());
     }
 
     /**
@@ -168,7 +168,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
      */
     default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
-        List<T> list = this.selectBatchIds(idList);
+        List<T> list = this.selectByIds(idList);
         if (CollUtil.isEmpty(list)) {
             return CollUtil.newArrayList();
         }
@@ -182,7 +182,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的VO对象列表
      */
     default List<V> selectVoByMap(Map<String, Object> map) {
-        return selectVoByMap(map, this.currentVoClass());
+        return this.selectVoByMap(map, this.currentVoClass());
     }
 
     /**
@@ -208,7 +208,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的单个VO对象
      */
     default V selectVoOne(Wrapper<T> wrapper) {
-        return selectVoOne(wrapper, this.currentVoClass());
+        return this.selectVoOne(wrapper, this.currentVoClass());
     }
 
     /**
@@ -219,11 +219,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的单个VO对象
      */
     default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
-        return selectVoOne(wrapper, this.currentVoClass(), throwEx);
+        return this.selectVoOne(wrapper, this.currentVoClass(), throwEx);
     }
 
     /**
-     * 根据条件查询单个VO对象,并指定返回的VO对象的类型
+     * 根据条件查询单个VO对象,并指定返回的VO对象的类型(自动拼接 limit 1)
+     * 注意不要再自己添加 limit 1 做限制了
      *
      * @param wrapper 查询条件Wrapper
      * @param voClass 返回的VO对象的Class对象
@@ -231,11 +232,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
      */
     default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
-        return selectVoOne(wrapper, voClass, true);
+        return this.selectVoOne(wrapper, voClass, true);
     }
 
     /**
-     * 根据条件查询单个实体对象,并将其转换为指定的VO对象
+     * 根据条件查询单个实体对象,并将其转换为指定的VO对象(自动拼接 limit 1)
+     * 注意不要再自己添加 limit 1 做限制了
      *
      * @param wrapper 查询条件Wrapper
      * @param voClass 要转换的VO类的Class对象
@@ -251,13 +253,33 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
         return MapstructUtils.convert(obj, voClass);
     }
 
+    /**
+     * 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数)
+     * 注意不要再自己添加 limit 1 做限制了
+     * <p>
+     * <strong>注意:</strong>
+     * 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效
+     * 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录
+     * </p>
+     *
+     * @param queryWrapper 查询条件(可为 null)
+     * @param throwEx      <del>是否抛出异常(已弃用,此参数不再生效)</del>
+     * @return 单条记录或无数据时返回 null
+     */
+    @Override
+    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
+        // 强制分页查询(LIMIT 1),确保最多返回 1 条记录
+        List<T> list = this.selectList(new Page<>(1, 1), queryWrapper);
+        return CollUtil.isEmpty(list) ? null : list.get(0);
+    }
+
     /**
      * 查询所有VO对象列表
      *
      * @return 查询到的VO对象列表
      */
     default List<V> selectVoList() {
-        return selectVoList(new QueryWrapper<>(), this.currentVoClass());
+        return this.selectVoList(new QueryWrapper<>(), this.currentVoClass());
     }
 
     /**
@@ -294,7 +316,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
      * @return 查询到的VO对象分页列表
      */
     default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
-        return selectVoPage(page, wrapper, this.currentVoClass());
+        return this.selectVoPage(page, wrapper, this.currentVoClass());
     }
 
     /**

+ 8 - 104
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusDataPermissionHandler.java

@@ -1,6 +1,5 @@
 package com.vber.common.mybatis.handler;
 
-import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.dto.RoleDTO;
@@ -21,23 +20,13 @@ import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
 import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
 import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import org.apache.ibatis.io.Resources;
-import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.expression.BeanFactoryResolver;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-import org.springframework.core.io.support.ResourcePatternResolver;
-import org.springframework.core.type.ClassMetadata;
-import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
 import org.springframework.expression.*;
 import org.springframework.expression.common.TemplateParserContext;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
-import org.springframework.util.ClassUtils;
 
-import java.lang.reflect.Method;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
 /**
@@ -48,11 +37,6 @@ import java.util.function.Function;
 @Slf4j
 public class PlusDataPermissionHandler {
 
-    /**
-     * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
-     */
-    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
-
     /**
      * spel 解析器
      */
@@ -63,27 +47,17 @@ public class PlusDataPermissionHandler {
      */
     private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
 
-    /**
-     * 构造方法,扫描指定包下的 Mapper 类并初始化缓存
-     *
-     * @param mapperPackage Mapper 类所在的包路径
-     */
-    public PlusDataPermissionHandler(String mapperPackage) {
-        scanMapperClasses(mapperPackage);
-    }
-
     /**
      * 获取数据过滤条件的 SQL 片段
      *
-     * @param where             原始的查询条件表达式
-     * @param mappedStatementId Mapper 方法的 ID
-     * @param isSelect          是否为查询语句
+     * @param where    原始的查询条件表达式
+     * @param isSelect 是否为查询语句
      * @return 数据过滤条件的 SQL 片段
      */
-    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
+    public Expression getSqlSegment(Expression where, boolean isSelect) {
         try {
             // 获取数据权限配置
-            DataPermission dataPermission = getDataPermission(mappedStatementId);
+            DataPermission dataPermission = getDataPermission();
             // 获取当前登录用户信息
             LoginUser currentUser = DataPermissionHelper.getVariable("user");
             if (ObjectUtil.isNull(currentUser)) {
@@ -205,92 +179,22 @@ public class PlusDataPermissionHandler {
         return StringUtils.EMPTY;
     }
 
-    /**
-     * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
-     *
-     * @param mapperPackage Mapper 类所在的包路径
-     */
-    private void scanMapperClasses(String mapperPackage) {
-        // 创建资源解析器和元数据读取工厂
-        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
-        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
-        // 将 Mapper 包路径按分隔符拆分为数组
-        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
-        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
-        try {
-            for (String packagePattern : packagePatternArray) {
-                // 将包路径转换为资源路径
-                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
-                // 获取指定路径下的所有 .class 文件资源
-                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
-                for (Resource resource : resources) {
-                    // 获取资源的类元数据
-                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
-                    // 获取资源对应的类对象
-                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
-                    // 查找类中的特定注解
-                    findAnnotation(clazz);
-                }
-            }
-        } catch (Exception e) {
-            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
-        }
-    }
-
-    /**
-     * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
-     *
-     * @param clazz 要查找的类
-     */
-    private void findAnnotation(Class<?> clazz) {
-        DataPermission dataPermission;
-        for (Method method : clazz.getMethods()) {
-            if (method.isDefault() || method.isVarArgs()) {
-                continue;
-            }
-            String mappedStatementId = clazz.getName() + "." + method.getName();
-            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
-                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
-                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
-            }
-        }
-        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
-            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
-            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
-        }
-    }
-
     /**
      * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
      *
-     * @param mapperId 映射语句 ID
      * @return DataPermission 注解对象,如果不存在则返回 null
      */
-    public DataPermission getDataPermission(String mapperId) {
-        // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
-        if (DataPermissionHelper.getPermission() != null) {
-            return DataPermissionHelper.getPermission();
-        }
-        // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
-        if (dataPermissionCacheMap.containsKey(mapperId)) {
-            return dataPermissionCacheMap.get(mapperId);
-        }
-        // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
-        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
-        if (dataPermissionCacheMap.containsKey(clazzName)) {
-            return dataPermissionCacheMap.get(clazzName);
-        }
-        return null;
+    public DataPermission getDataPermission() {
+        return DataPermissionHelper.getPermission();
     }
 
     /**
      * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
      *
-     * @param mapperId 映射语句 ID
      * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
      */
-    public boolean invalid(String mapperId) {
-        return getDataPermission(mapperId) == null;
+    public boolean invalid() {
+        return getDataPermission() == null;
     }
 
     /**

+ 6 - 13
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/interceptor/PlusDataPermissionInterceptor.java

@@ -34,14 +34,7 @@ import java.util.List;
 @Slf4j
 public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
 
-    private final PlusDataPermissionHandler dataPermissionHandler;
-
-    /**
-     * 构造函数,初始化 PlusDataPermissionHandler 实例
-     */
-    public PlusDataPermissionInterceptor(String mapperPackage) {
-        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
-    }
+    private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
 
     /**
      * 在执行查询之前,检查并处理数据权限相关逻辑
@@ -61,7 +54,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
             return;
         }
         // 检查是否缺少有效的数据权限注解
-        if (dataPermissionHandler.invalid(ms.getId())) {
+        if (dataPermissionHandler.invalid()) {
             return;
         }
         // 解析 sql 分配对应方法
@@ -89,7 +82,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
                 return;
             }
             // 检查是否缺少有效的数据权限注解
-            if (dataPermissionHandler.invalid(ms.getId())) {
+            if (dataPermissionHandler.invalid()) {
                 return;
             }
             PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
@@ -125,7 +118,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
      */
     @Override
     protected void processUpdate(Update update, int index, String sql, Object obj) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
         if (null != sqlSegment) {
             update.setWhere(sqlSegment);
         }
@@ -141,7 +134,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
      */
     @Override
     protected void processDelete(Delete delete, int index, String sql, Object obj) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
         if (null != sqlSegment) {
             delete.setWhere(sqlSegment);
         }
@@ -154,7 +147,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
      * @param mappedStatementId 映射语句的 ID
      */
     protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
         if (null != sqlSegment) {
             plainSelect.setWhere(sqlSegment);
         }

+ 1 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/sharding/LongDateShardingAlgorithm.java

@@ -1,5 +1,6 @@
 package com.vber.common.sharding;
 
+import cn.hutool.core.convert.Convert;
 import com.google.common.collect.Range;
 import lombok.Getter;
 import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/core/OssClient.java

@@ -208,11 +208,11 @@ public class OssClient {
                 // 创建异步请求体(length如果为空会报错)
                 BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
                         .contentLength(length)
-                        .subscribeTimeout(Duration.ofSeconds(30))
+                        .subscribeTimeout(Duration.ofSeconds(120))
                         .build();
                 // 使用 transferManager 进行上传
                 Upload upload = transferManager.upload(
-                        x -> x.requestBody(body)
+                        x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
                                 .putObjectRequest(
                                         y -> y.bucket(properties.getBucketName())
                                                 .key(key)

+ 4 - 7
SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/utils/RedisUtils.java

@@ -129,9 +129,9 @@ public class RedisUtils {
             } catch (Exception e) {
                 long timeToLive = bucket.remainTimeToLive();
                 if (timeToLive == -1) {
-                    setCacheObject(key, value);
+                    bucket.set(value);
                 } else {
-                    setCacheObject(key, value, Duration.ofMillis(timeToLive));
+                    bucket.set(value, Duration.ofMillis(timeToLive));
                 }
             }
         } else {
@@ -147,11 +147,8 @@ public class RedisUtils {
      * @param duration 时间
      */
     public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
-        RBatch batch = CLIENT.createBatch();
-        RBucketAsync<T> bucket = batch.getBucket(key);
-        bucket.setAsync(value);
-        bucket.expireAsync(duration);
-        batch.execute();
+        RBucket<T> bucket = CLIENT.getBucket(key);
+        bucket.set(value, duration);
     }
 
     /**

+ 3 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/utils/SequenceUtils.java

@@ -1,5 +1,6 @@
 package com.vber.common.redis.utils;
 
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.date.DatePattern;
 import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.StringUtils;
@@ -118,7 +119,7 @@ public class SequenceUtils {
      * @return 唯一id
      */
     public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
-        return String.valueOf(getNextId(key, expireTime, initValue, stepValue));
+        return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
     }
 
     /**
@@ -129,7 +130,7 @@ public class SequenceUtils {
      * @return 唯一id
      */
     public static String getNextIdString(String key, Duration expireTime) {
-        return String.valueOf(getNextId(key, expireTime));
+        return Convert.toStr(getNextId(key, expireTime));
     }
 
     /**

+ 6 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-satoken/src/main/java/com/vber/common/satoken/core/dao/PlusSaTokenDao.java

@@ -72,7 +72,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
      */
     @Override
     public void delete(String key) {
-        RedisUtils.deleteObject(key);
+        if (RedisUtils.deleteObject(key)) {
+            CAFFEINE.invalidate(key);
+        }
     }
 
     /**
@@ -149,7 +151,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
      */
     @Override
     public void deleteObject(String key) {
-        RedisUtils.deleteObject(key);
+        if (RedisUtils.deleteObject(key)) {
+            CAFFEINE.invalidate(key);
+        }
     }
 
     /**

+ 24 - 6
SERVER/VberAdminPlusV3/vber-common/vber-common-satoken/src/main/java/com/vber/common/satoken/core/service/SaPermissionImpl.java

@@ -1,6 +1,7 @@
 package com.vber.common.satoken.core.service;
 
 import cn.dev33.satoken.stp.StpInterface;
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.enums.UserType;
@@ -26,7 +27,10 @@ public class SaPermissionImpl implements StpInterface {
     @Override
     public List<String> getPermissionList(Object loginId, String loginType) {
         LoginUser loginUser = LoginHelper.getLoginUser();
-        if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
+        if (ObjectUtil.isNull(loginUser)) {
+            return new ArrayList<>();
+        }
+        if (!loginUser.getLoginId().equals(loginId)) {
             PermissionService permissionService = getPermissionService();
             if (ObjectUtil.isNotNull(permissionService)) {
                 List<String> list = StringUtils.splitList(loginId.toString(), ":");
@@ -35,12 +39,21 @@ public class SaPermissionImpl implements StpInterface {
                 throw new ServiceException("PermissionService 实现类不存在");
             }
         }
+        // 判断是否为超级管理员
+        if (LoginHelper.isSuperAdmin(loginUser.getUserId())) {
+            // 返回通配符权限,代表拥有所有权限
+            return List.of("*");
+        }
         UserType userType = UserType.getUserType(loginUser.getUserType());
         if (userType == UserType.APP_USER) {
-            // 其他端 自行根据业务编写
+            // 其用户类型 自行根据业务编写
+        }
+        if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
+            // SYS_USER 默认返回权限
+            return new ArrayList<>(loginUser.getMenuPermission());
+        } else {
+            return new ArrayList<>();
         }
-        // SYS_USER 默认返回权限
-        return new ArrayList<>(loginUser.getMenuPermission());
     }
 
     /**
@@ -60,10 +73,15 @@ public class SaPermissionImpl implements StpInterface {
         }
         UserType userType = UserType.getUserType(loginUser.getUserType());
         if (userType == UserType.APP_USER) {
-            // 其他端 自行根据业务编写
+            // 其用户类型 自行根据业务编写
         }
         // SYS_USER 默认返回权限
-        return new ArrayList<>(loginUser.getRolePermission());
+        if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
+            // SYS_USER 默认返回权限
+            return new ArrayList<>(loginUser.getRolePermission());
+        } else {
+            return new ArrayList<>();
+        }
     }
 
     private PermissionService getPermissionService() {

+ 12 - 15
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/config/FilterConfig.java

@@ -7,6 +7,7 @@ import jakarta.servlet.DispatcherType;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistration;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 
@@ -21,24 +22,20 @@ public class FilterConfig {
 
     @Bean
     @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
-    public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
-        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
-        registration.setDispatcherTypes(DispatcherType.REQUEST);
-        registration.setFilter(new XssFilter());
-        registration.addUrlPatterns("/*");
-        registration.setName("xssFilter");
-        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
-        return registration;
+    @FilterRegistration(
+            name = "xssFilter",
+            urlPatterns = "/*",
+            order = FilterRegistrationBean.HIGHEST_PRECEDENCE + 1,
+            dispatcherTypes = DispatcherType.REQUEST
+    )
+    public XssFilter xssFilter() {
+        return new XssFilter();
     }
 
     @Bean
-    public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
-        FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
-        registration.setFilter(new RepeatableFilter());
-        registration.addUrlPatterns("/*");
-        registration.setName("repeatableFilter");
-        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
-        return registration;
+    @FilterRegistration(name = "repeatableFilter", urlPatterns = "/*")
+    public RepeatableFilter repeatableFilter() {
+        return new RepeatableFilter();
     }
 
 }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-extend/vber-job-admin/Dockerfile

@@ -1,6 +1,6 @@
 # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
-FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
-#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
 #FROM anapsix/alpine-java:8_server-jre_unlimited
 
 LABEL maintainer="IwbY"

+ 1 - 1
SERVER/VberAdminPlusV3/vber-extend/vber-job-admin/src/main/java/com/vber/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java

@@ -69,7 +69,7 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
             }
         }
 
-        long jobHash = hash(Convert.toStr(jobId));
+        long jobHash = hash(String.valueOf(jobId));
         SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
         if (!lastRing.isEmpty()) {
             return lastRing.get(lastRing.firstKey());

+ 2 - 2
SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/Dockerfile

@@ -1,6 +1,6 @@
 # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
-FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
-#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
 #FROM openjdk:17.0.2-oraclelinux8
 #FROM findepi/graalvm:java17-native
 

+ 2 - 0
SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/MonitorAdminApplication.java

@@ -1,5 +1,6 @@
 package com.vber.monitor.admin;
 
+import de.codecentric.boot.admin.server.config.EnableAdminServer;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@@ -8,6 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
  *
  * @author Iwb
  */
+@EnableAdminServer
 @SpringBootApplication
 public class MonitorAdminApplication {
 

+ 0 - 31
SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/config/AdminServerConfig.java

@@ -1,31 +0,0 @@
-package com.vber.monitor.admin.config;
-
-import de.codecentric.boot.admin.server.config.EnableAdminServer;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
-import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-
-import java.util.concurrent.Executor;
-
-/**
- * springboot-admin server配置类
- *
- * @author Iwb
- */
-@Configuration
-@EnableAdminServer
-public class AdminServerConfig {
-
-    @Lazy
-    @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
-    @ConditionalOnMissingBean(Executor.class)
-    public ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
-        return builder.build();
-    }
-
-
-}

+ 5 - 13
SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/java/com/vber/monitor/admin/config/SecurityConfig.java

@@ -3,7 +3,6 @@ package com.vber.monitor.admin.config;
 import de.codecentric.boot.admin.server.config.AdminServerProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -11,8 +10,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
 import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
-import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
-import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
 
 /**
  * admin 监控 安全配置
@@ -30,18 +28,18 @@ public class SecurityConfig {
     }
 
     @Bean
-    public SecurityFilterChain filterChain(HttpSecurity httpSecurity, MvcRequestMatcher.Builder mvc) throws Exception {
+    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
         SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
         successHandler.setTargetUrlParameter("redirectTo");
         successHandler.setDefaultTargetUrl(adminContextPath + "/");
-
+        PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults();
         return httpSecurity
                 .headers((header) ->
                         header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                 .authorizeHttpRequests((authorize) ->
                         authorize.requestMatchers(
-                                        mvc.pattern(adminContextPath + "/assets/**"),
-                                        mvc.pattern(adminContextPath + "/login")
+                                        mvc.matcher(adminContextPath + "/assets/**"),
+                                        mvc.matcher(adminContextPath + "/login")
                                 ).permitAll()
                                 .anyRequest().authenticated())
                 .formLogin((formLogin) ->
@@ -53,10 +51,4 @@ public class SecurityConfig {
                 .build();
     }
 
-    @Scope("prototype")
-    @Bean
-    public MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
-        return new MvcRequestMatcher.Builder(introspector);
-    }
-
 }

+ 3 - 0
SERVER/VberAdminPlusV3/vber-extend/vber-monitor-admin/src/main/resources/application.yml

@@ -20,6 +20,9 @@ spring:
       ui:
         title: VberAdminPlus服务监控中心
       context-path: /
+  # 忽略无用警告
+  thymeleaf:
+    check-template-location: false
 
 --- # Actuator 监控端点的配置项
 management:

+ 20 - 14
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/reactor/core/ProjectReactor.java

@@ -26,8 +26,9 @@ public class ProjectReactor {
     private static final String ARTIFACT_ID = "VberAdminPlus";
     private static final String VERSION = "V3";
     private static final String ARTIFACT_ID_VERSION = ARTIFACT_ID + VERSION;
+    private static final String ARTIFACT_ID_SHORT = "vap3";
     private static final String PACKAGE_NAME = "com.vber";
-    private static final String MODULE_PREFIX = "vber-";
+    private static final String MODULE_PREFIX = "vber";
     private static final String MODULE_ARTIFACT_ID_PREFIX = "<artifactId>" + MODULE_PREFIX;
     private static final String MODULE_ID_PREFIX = "<module>" + MODULE_PREFIX;
     private static final String TITLE = "VAP后台管理系统";
@@ -48,15 +49,15 @@ public class ProjectReactor {
      * @param packageName  项目的包名,用于Java项目的包结构定义。
      * @param title        项目的标题,可用于描述项目的名称或目的。
      * @param projectDir   项目目录的路径,指定项目文件和资源所在的物理位置。
-     * @param modulePrefix 项目模块前缀,如果为空则使用默认值"vber-"。用于构建模块名称。
+     * @param modulePrefix 项目模块前缀,如果为空则使用默认值"vber"。用于构建模块名称。
      */
-    public static void build(@NotNull String groupId, @NotNull String artifactId, @NotNull String packageName, @NotNull String title, @NotNull String projectDir, String modulePrefix) {
+    public static void build(@NotNull String groupId, @NotNull String artifactId, @NotNull String packageName, @NotNull String title, @NotNull String projectDir, String modulePrefix, @NotNull String artifactIdShort) {
         // 如果modulePrefix为空,则使用默认前缀
-        modulePrefix = StrUtil.isEmpty(modulePrefix) ? "vber-" : modulePrefix;
-        modulePrefix = modulePrefix.endsWith("-") ? modulePrefix : modulePrefix + "-";
+        modulePrefix = StrUtil.isEmpty(modulePrefix) ? "vber" : modulePrefix;
+        modulePrefix = modulePrefix.endsWith("-") ? modulePrefix.substring(0, modulePrefix.length() - 1) : modulePrefix;
         // 确保projectDir以反斜杠结尾。
         projectDir = projectDir.endsWith("\\") ? projectDir : projectDir + "\\";
-        build(new ProjectInfo(groupId, artifactId, packageName, title, projectDir, modulePrefix));
+        build(new ProjectInfo(groupId, artifactId, artifactIdShort, packageName, title, projectDir, modulePrefix));
     }
 
     /**
@@ -76,6 +77,7 @@ public class ProjectReactor {
         // 使用项目信息中的值,如果为空则使用默认值
         String groupId = StrUtil.isEmpty(projectInfo.getGroupId()) ? GROUP_ID : projectInfo.getGroupId();
         String artifactId = StrUtil.isEmpty(projectInfo.getArtifactId()) ? ARTIFACT_ID : projectInfo.getArtifactId();
+        String artifactIdShort = StrUtil.isEmpty(projectInfo.getArtifactIdShort()) ? ARTIFACT_ID_SHORT : projectInfo.getArtifactIdShort();
         String packageName = StrUtil.isEmpty(projectInfo.getPackageName()) ? PACKAGE_NAME : projectInfo.getPackageName();
         String modulePrefix = StrUtil.isEmpty(projectInfo.getModulePrefix()) ? MODULE_PREFIX : projectInfo.getModulePrefix();
         String moduleArtifactIdPrefix = "<artifactId>" + modulePrefix;
@@ -124,7 +126,7 @@ public class ProjectReactor {
                 return;
             }
             // 如果非白名单的文件类型,重写内容,在生成文件
-            String content = replaceFileContent(file, groupId, artifactId, packageName, title, moduleArtifactIdPrefix, moduleIdPrefix);
+            String content = replaceFileContent(file, groupId, artifactId, artifactIdShort, packageName, title, moduleArtifactIdPrefix, moduleIdPrefix);
             writeFile(file, content, oldProjectDir, projectDir, packageName, artifactId, modulePrefix, uiFile);
         });
         log.info("[重写完成]共耗时:{} 秒", (System.currentTimeMillis() - start) / 1000);
@@ -137,10 +139,12 @@ public class ProjectReactor {
                 .filter(file -> !file.getPath().contains(separator + "target" + separator)
                         && !file.getPath().contains(separator + "node_modules" + separator)
                         && !file.getPath().contains(separator + ".idea" + separator)
-                        && !file.getPath().contains(separator + ".git" + separator)
-                        && !file.getPath().contains(separator + "UI/dist" + separator)
-                        && !file.getPath().contains(separator + ".vscode" + separator)
                         && !file.getPath().contains(separator + ".vs" + separator)
+                        && !file.getPath().contains(separator + ".vscode" + separator)
+                        && !file.getPath().contains(separator + ".visualstudio" + separator)
+                        && !file.getPath().contains(separator + ".git" + separator)
+                        && !file.getPath().contains(separator + ".data" + separator)
+                        && !file.getPath().contains(separator + "UI" + separator + "dist" + separator)
                         && !file.getPath().contains(separator + "vber" + separator + "logs" + separator)
                         && !file.getPath().contains(separator + "vber" + separator + "profile" + separator)
                         && !file.getPath().contains(separator + "com" + separator + "vber" + separator + "reactor" + separator)
@@ -161,7 +165,7 @@ public class ProjectReactor {
     }
 
     private static String replaceFileContent(File file, String groupIdNew,
-                                             String artifactIdNew, String packageNameNew,
+                                             String artifactIdNew, String artifactIdShortNew, String packageNameNew,
                                              String titleNew, String moduleArtifactIdPrefix, String moduleIdSuffix) {
         String content = FileUtil.readString(file, StandardCharsets.UTF_8);
         // 如果是白名单的文件类型,不进行重写
@@ -172,12 +176,14 @@ public class ProjectReactor {
         // 执行文件内容都重写
         return content.replaceAll(GROUP_ID, groupIdNew)
                 .replaceAll(PACKAGE_NAME, packageNameNew)
-                .replaceAll(MODULE_ARTIFACT_ID_PREFIX, moduleArtifactIdPrefix)
+                .replaceAll(MODULE_ARTIFACT_ID_PREFIX + "-", moduleArtifactIdPrefix + "-")
+                .replaceAll(MODULE_ARTIFACT_ID_PREFIX + "_", moduleArtifactIdPrefix + "_")
+                .replaceAll(MODULE_ID_PREFIX, moduleIdSuffix)
+                .replaceAll(ARTIFACT_ID_SHORT, artifactIdShortNew)
                 .replaceAll(ARTIFACT_ID_VERSION, artifactIdNew) // 带版本的ARTIFACT_ID也替换成无版本的!
                 .replaceAll(ARTIFACT_ID, artifactIdNew) // 必须放在最后替换,因为 ARTIFACT_ID 太短!
                 .replaceAll(StrUtil.upperFirst(ARTIFACT_ID), StrUtil.upperFirst(artifactIdNew))
-                .replaceAll(StrUtil.upperFirst(MODULE_ID_PREFIX), StrUtil.upperFirst(moduleIdSuffix))
-                .replaceAll("permission: \"no_auth\" //NEW PROJECT", "permission: \"\"") //隐藏生成新项目按钮
+                .replaceAll("permission: \"no_auth\" //NEW PROJECT", "permission: \"---\"") //隐藏生成新项目按钮
                 .replaceAll(TITLE, titleNew);
     }
 

+ 2 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/reactor/domain/ProjectInfo.java

@@ -23,6 +23,8 @@ public class ProjectInfo implements Serializable {
     private String groupId;
     @NotBlank(message = "新项目ArtifactId不能为空")
     private String artifactId;
+    @NotBlank(message = "新项目短名称不能为空")
+    private String artifactIdShort;
     @NotBlank(message = "新项目包名称不能为空")
     private String packageName;
     @NotBlank(message = "新项目系统标题不能为空")

+ 3 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/test/java/com/vber/reactor/ProjectReactorTest.java

@@ -10,12 +10,13 @@ public class ProjectReactorTest {
         // ========== 手动修改配置后运行 ==========
         String groupId = "com.va";
         String artifactId = "VberAdmin";
+        String artifactIdShort = "iwb";
         String packageName = "cn.vber";
         String title = "VberAdmin管理平台";
-        String modulePrefix = "vb-";
+        String modulePrefix = "vb";
         String projectDir = "D:\\Project\\VberPlus\\";
         // ========== 手动修改配置后运行 ==========
-        ProjectReactor.build(groupId, artifactId, packageName, title, projectDir, modulePrefix);
+        ProjectReactor.build(groupId, artifactId, artifactIdShort, packageName, title, projectDir, modulePrefix);
 
     }
 }

+ 5 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysProfileController.java

@@ -16,6 +16,7 @@ import com.vber.common.web.core.BaseController;
 import com.vber.system.domain.bo.SysUserBo;
 import com.vber.system.domain.bo.SysUserPasswordBo;
 import com.vber.system.domain.bo.SysUserProfileBo;
+import com.vber.system.domain.vo.ProfileUserVo;
 import com.vber.system.domain.vo.SysOssVo;
 import com.vber.system.domain.vo.SysUserVo;
 import com.vber.system.service.ISysOssService;
@@ -50,7 +51,9 @@ public class SysProfileController extends BaseController {
         SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
         String roleGroup = userService.selectUserRoleGroup(user.getUserId());
         String postGroup = userService.selectUserPostGroup(user.getUserId());
-        ProfileVo profileVo = new ProfileVo(user, roleGroup, postGroup);
+        // 单独做一个vo专门给个人中心用 避免数据被脱敏
+        ProfileUserVo profileUser = BeanUtil.toBean(user, ProfileUserVo.class);
+        ProfileVo profileVo = new ProfileVo(profileUser, roleGroup, postGroup);
         return R.ok(profileVo);
     }
 
@@ -130,6 +133,6 @@ public class SysProfileController extends BaseController {
     public record AvatarVo(String imgUrl) {
     }
 
-    public record ProfileVo(SysUserVo user, String roleGroup, String postGroup) {
+    public record ProfileVo(ProfileUserVo user, String roleGroup, String postGroup) {
     }
 }

+ 2 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/controller/system/SysUserController.java

@@ -18,6 +18,7 @@ import com.vber.common.log.annotation.Log;
 import com.vber.common.log.enums.BusinessType;
 import com.vber.common.mybatis.core.page.PageQuery;
 import com.vber.common.mybatis.core.page.TableDataInfo;
+import com.vber.common.mybatis.helper.DataPermissionHelper;
 import com.vber.common.satoken.utils.LoginHelper;
 import com.vber.common.tenant.helper.TenantHelper;
 import com.vber.common.web.core.BaseController;
@@ -119,7 +120,7 @@ public class SysUserController extends BaseController {
             // 超级管理员 如果重新加载用户信息需清除动态租户
             TenantHelper.clearDynamic();
         }
-        SysUserVo user = userService.selectUserById(loginUser.getUserId());
+        SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId()));
         if (ObjectUtil.isNull(user)) {
             return R.fail("没有权限访问用户数据!");
         }

+ 90 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/domain/vo/ProfileUserVo.java

@@ -0,0 +1,90 @@
+package com.vber.system.domain.vo;
+
+import com.vber.common.translation.annotation.Translation;
+import com.vber.common.translation.constant.TransConstant;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 用户信息视图对象 sys_user
+ *
+ * @author Iwb
+ */
+@Data
+public class ProfileUserVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 部门ID
+     */
+    private Long orgId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户类型(sys_user系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 头像地址
+     */
+    @Translation(type = TransConstant.OSS_ID_TO_URL)
+    private Long avatar;
+
+    /**
+     * 最后登录IP
+     */
+    private String loginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private Date loginDate;
+
+    /**
+     * 部门名
+     */
+    @Translation(type = TransConstant.ORG_ID_TO_NAME, mapper = "orgId")
+    private String deptName;
+
+}

+ 15 - 6
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/mapper/SysMenuMapper.java

@@ -2,12 +2,16 @@ package com.vber.system.mapper;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.vber.common.core.constant.SystemConstants;
+import com.vber.common.core.utils.StreamUtils;
+import com.vber.common.core.utils.StringUtils;
 import com.vber.common.mybatis.core.mapper.BaseMapperPlus;
 import com.vber.system.domain.SysMenu;
 import com.vber.system.domain.vo.SysMenuVo;
 import org.springframework.stereotype.Repository;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * 菜单表 数据层
@@ -111,11 +115,12 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
      *
      * @return 权限列表
      */
-    default List<String> selectMenuPerms() {
-        return this.selectObjs(
+    default Set<String> selectMenuPerms() {
+        List<String> list = this.selectObjs(
                 new LambdaQueryWrapper<SysMenu>()
                         .select(SysMenu::getPerms)
         );
+        return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
     }
 
 
@@ -125,12 +130,14 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
      * @param userId 用户ID
      * @return 权限列表
      */
-    default List<String> selectMenuPermsByUserId(Long userId) {
-        return this.selectObjs(
+    default Set<String> selectMenuPermsByUserId(Long userId) {
+        List<String> list = this.selectObjs(
                 new LambdaQueryWrapper<SysMenu>()
                         .select(SysMenu::getPerms)
                         .inSql(SysMenu::getMenuId, this.buildMenuByUserSql(userId))
+                        .isNotNull(SysMenu::getPerms)
         );
+        return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
     }
 
     /**
@@ -139,12 +146,14 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
      * @param roleId 角色ID
      * @return 权限列表
      */
-    default List<String> selectMenuPermsByRoleId(Long roleId) {
-        return this.selectObjs(
+    default Set<String> selectMenuPermsByRoleId(Long roleId) {
+        List<String> list = this.selectObjs(
                 new LambdaQueryWrapper<SysMenu>()
                         .select(SysMenu::getPerms)
                         .inSql(SysMenu::getMenuId, this.buildMenuByRoleSql(roleId))
+                        .isNotNull(SysMenu::getPerms)
         );
+        return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
     }
 
     /**

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java

@@ -144,7 +144,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
             boolean assigned = dictDataMapper.exists(new LambdaQueryWrapper<SysDictData>()
                     .eq(SysDictData::getDictType, x.getDictType()));
             if (assigned) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除", x.getDictName()));
+                throw new ServiceException("{}已分配,不能删除", x.getDictName());
             }
         });
         baseMapper.deleteByIds(dictIds);

+ 2 - 16
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysMenuServiceImpl.java

@@ -107,14 +107,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
      */
     @Override
     public Set<String> selectMenuPermsByUserId(Long userId) {
-        List<String> perms = LoginHelper.isSuperAdmin(userId) ? baseMapper.selectMenuPerms() : baseMapper.selectMenuPermsByUserId(userId);
-        Set<String> permsSet = new HashSet<>();
-        for (String perm : perms) {
-            if (StringUtils.isNotEmpty(perm)) {
-                permsSet.addAll(StringUtils.splitList(perm.trim()));
-            }
-        }
-        return permsSet;
+        return baseMapper.selectMenuPermsByUserId(userId);
     }
 
     /**
@@ -125,14 +118,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
      */
     @Override
     public Set<String> selectMenuPermsByRoleId(Long roleId) {
-        List<String> perms = baseMapper.selectMenuPermsByRoleId(roleId);
-        Set<String> permsSet = new HashSet<>();
-        for (String perm : perms) {
-            if (StringUtils.isNotEmpty(perm)) {
-                permsSet.addAll(StringUtils.splitList(perm.trim()));
-            }
-        }
-        return permsSet;
+        return baseMapper.selectMenuPermsByRoleId(roleId);
     }
 
     /**

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysPostServiceImpl.java

@@ -219,7 +219,7 @@ public class SysPostServiceImpl implements ISysPostService, PostService {
         List<SysPost> list = baseMapper.selectByIds(postIds);
         for (SysPost post : list) {
             if (this.countUserPostById(post.getPostId()) > 0) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除!", post.getPostName()));
+                throw new ServiceException("{}已分配,不能删除!", post.getPostName());
             }
         }
         return baseMapper.deleteByIds(postIds);

+ 10 - 10
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysRoleServiceImpl.java

@@ -367,16 +367,16 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
      */
     private int insertRoleMenu(SysRoleBo role) {
         int rows = 1;
-        // 新增角色与组织机构(数据权限)管理
-        List<SysRoleOrg> list = new ArrayList<>();
-        for (Long orgId : role.getOrgIds()) {
-            SysRoleOrg rd = new SysRoleOrg();
-            rd.setRoleId(role.getRoleId());
-            rd.setOrgId(orgId);
-            list.add(rd);
+        // 新增角色与菜单管理
+        List<SysRoleMenu> list = new ArrayList<>();
+        for (Long menuId : role.getMenuIds()) {
+            SysRoleMenu rm = new SysRoleMenu();
+            rm.setRoleId(role.getRoleId());
+            rm.setMenuId(menuId);
+            list.add(rm);
         }
         if (CollUtil.isNotEmpty(list)) {
-            rows = roleOrgMapper.insertBatch(list) ? list.size() : 0;
+            rows = roleMenuMapper.insertBatch(list) ? list.size() : 0;
         }
         return rows;
     }
@@ -389,7 +389,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
     private int insertRoleOrg(SysRoleBo role) {
         Long[] orgIds = role.getOrgIds();
         if (ArrayUtil.isEmpty(orgIds)) {
-            return 0;
+            return 1;
         }
         List<SysRoleOrg> roleOrgList = Arrays.stream(orgIds)
                 .map(orgId -> {
@@ -433,7 +433,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
         for (SysRole role : roles) {
             checkRoleAllowed(BeanUtil.toBean(role, SysRoleBo.class));
             if (countUserRoleByRoleId(role.getRoleId()) > 0) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除!", role.getRoleName()));
+                throw new ServiceException("{}已分配,不能删除!", role.getRoleName());
             }
         }
         // 删除角色与菜单关联

+ 5 - 5
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysTaskAssigneeServiceImpl.java

@@ -59,7 +59,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysRoleVo> page = roleService.selectPageRoleList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                item -> Convert.toStr(item.getRoleId()), SysRoleVo::getRoleKey, SysRoleVo::getRoleName, null, SysRoleVo::getCreateTime);
+                item -> Convert.toStr(item.getRoleId()), SysRoleVo::getRoleKey, SysRoleVo::getRoleName, item -> "", SysRoleVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -83,12 +83,12 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysPostVo> page = postService.selectPagePostList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                p -> Convert.toStr(p.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, SysPostVo::getOrgId, SysPostVo::getCreateTime);
+                item -> Convert.toStr(item.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, item -> Convert.toStr(item.getOrgId()), SysPostVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
     /**
-     * 查询组织机构并返回任务指派的列表,支持分页
+     * 查询部门并返回任务指派的列表,支持分页
      *
      * @param taskQuery 查询条件
      * @return 办理人
@@ -107,7 +107,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysOrgVo> page = orgService.selectPageOrgList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                d -> Convert.toStr(d.getOrgId()), SysOrgVo::getOrgCategory, SysOrgVo::getOrgName, SysOrgVo::getParentId, SysOrgVo::getCreateTime);
+                item -> Convert.toStr(item.getOrgId()), SysOrgVo::getOrgCategory, SysOrgVo::getOrgName, item -> Convert.toStr(item.getParentId()), SysOrgVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -131,7 +131,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysUserVo> page = userService.selectPageUserList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                u -> Convert.toStr(u.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, SysUserVo::getOrgId, SysUserVo::getCreateTime);
+                item -> Convert.toStr(item.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, item -> Convert.toStr(item.getOrgId()), SysUserVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java

@@ -367,7 +367,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
         // 防止错误更新后导致的数据误删除
         int flag = baseMapper.updateById(sysUser);
         if (flag < 1) {
-            throw new ServiceException("修改用户" + user.getUserName() + "信息失败");
+            throw new ServiceException("修改用户{}信息失败", user.getUserName());
         }
         return flag;
     }

+ 4 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/constant/FlowConstant.java

@@ -84,5 +84,9 @@ public interface FlowConstant {
      */
     String WF_TASK_STATUS = "wf_task_status";
 
+    /**
+     * 自动通过
+     */
+    String AUTO_PASS = "autoPass";
 
 }

+ 25 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskAssigneeEnum.java

@@ -1,6 +1,7 @@
 package com.vber.workflow.common.enums;
 
 import com.vber.common.core.exception.ServiceException;
+import com.vber.common.core.utils.StringUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -110,5 +111,29 @@ public enum TaskAssigneeEnum {
                 .map(TaskAssigneeEnum::getCode)
                 .collect(Collectors.toList());
     }
+
+    /**
+     * 判断当前办理人类型是否需要调用部门服务(deptService)
+     *
+     * @return 如果类型是 USER、ORG 或 POST,则返回 true;否则返回 false
+     */
+    public boolean needsOrgService() {
+        return this == USER || this == ORG || this == POST;
+    }
+
+    /**
+     * 判断给定字符串是否符合 SPEL 表达式格式(以 $ 或 # 开头)
+     *
+     * @param value 待判断字符串
+     * @return 是否为 SPEL 表达式
+     */
+    public static boolean isSpelExpression(String value) {
+        if (value == null) {
+            return false;
+        }
+        // $前缀表示默认办理人变量策略
+        // #前缀表示spel办理人变量策略
+        return StringUtils.startsWith(value, "$") || StringUtils.startsWith(value, "#");
+    }
 }
 

+ 22 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java

@@ -11,6 +11,7 @@ import com.vber.workflow.common.ConditionalOnEnable;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import com.vber.workflow.service.IFlwInstanceService;
 import lombok.RequiredArgsConstructor;
@@ -88,6 +89,16 @@ public class FlwInstanceController extends BaseController {
         return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
     }
 
+    /**
+     * 按照实例id删除已完成得流程实例
+     *
+     * @param instanceIds 实例id
+     */
+    @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
+    public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
+        return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
+    }
+
     /**
      * 撤销流程
      *
@@ -142,6 +153,17 @@ public class FlwInstanceController extends BaseController {
         return R.ok(flwInstanceService.instanceVariable(instanceId));
     }
 
+    /**
+     * 修改流程变量
+     *
+     * @param bo 参数
+     */
+    @RepeatSubmit()
+    @PutMapping("/updateVariable")
+    public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
+        return toAjax(flwInstanceService.updateVariable(bo));
+    }
+
     /**
      * 作废流程
      *

+ 17 - 5
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java

@@ -191,12 +191,12 @@ public class FlwTaskController extends BaseController {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      */
-    @GetMapping("/getBackTaskNode/{definitionId}/{nowNodeCode}")
-    public R<List<Node>> getBackTaskNode(@PathVariable Long definitionId, @PathVariable String nowNodeCode) {
-        return R.ok(flwTaskService.getBackTaskNode(definitionId, nowNodeCode));
+    @GetMapping("/getBackTaskNode/{taskId}/{nowNodeCode}")
+    public R<List<Node>> getBackTaskNode(@PathVariable Long taskId, @PathVariable String nowNodeCode) {
+        return R.ok(flwTaskService.getBackTaskNode(taskId, nowNodeCode));
     }
 
     /**
@@ -209,4 +209,16 @@ public class FlwTaskController extends BaseController {
         return R.ok(flwTaskService.currentTaskAllUser(List.of(taskId)));
     }
 
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    @PostMapping("/urgeTask")
+    public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
+        return toAjax(flwTaskService.urgeTask(bo));
+    }
+
+
 }

+ 38 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowUrgeTaskBo.java

@@ -0,0 +1,38 @@
+package com.vber.workflow.domain.bo;
+
+import com.vber.common.core.validate.AddGroup;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 流程变量参数
+ *
+ * @author Iwb
+ */
+@Data
+public class FlowUrgeTaskBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务id
+     */
+    @NotNull(message = "任务id为空", groups = AddGroup.class)
+    private List<Long> taskIdList;
+
+    /**
+     * 消息类型
+     */
+    private List<String> messageType;
+
+    /**
+     * 催办内容
+     */
+    @NotNull(message = "催办内容为空", groups = AddGroup.class)
+    private String message;
+}

+ 39 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowVariableBo.java

@@ -0,0 +1,39 @@
+package com.vber.workflow.domain.bo;
+
+import com.vber.common.core.validate.AddGroup;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 流程变量参数
+ *
+ * @author Iwb
+ */
+@Data
+public class FlowVariableBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 流程实例id
+     */
+    @NotNull(message = "流程实例id为空", groups = AddGroup.class)
+    private Long instanceId;
+
+    /**
+     * 流程变量key
+     */
+    @NotNull(message = "流程变量key为空", groups = AddGroup.class)
+    private String key;
+
+    /**
+     * 流程变量value
+     */
+    @NotNull(message = "流程变量value为空", groups = AddGroup.class)
+    private String value;
+
+}

+ 5 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TestLeaveBo.java

@@ -31,6 +31,11 @@ public class TestLeaveBo extends BaseEntity {
     @NotNull(message = "主键不能为空", groups = {EditGroup.class})
     private Long id;
 
+    /**
+     * 流程code
+     */
+    private String flowCode;
+
     /**
      * 请假类型
      */

+ 23 - 17
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java

@@ -16,7 +16,6 @@ import com.vber.workflow.service.IFlwInstanceService;
 import com.vber.workflow.service.IFlwTaskService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.warm.flow.core.FlowEngine;
 import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.Definition;
 import org.dromara.warm.flow.core.entity.Instance;
@@ -24,8 +23,6 @@ import org.dromara.warm.flow.core.entity.Task;
 import org.dromara.warm.flow.core.listener.GlobalListener;
 import org.dromara.warm.flow.core.listener.ListenerVariable;
 import org.dromara.warm.flow.core.service.InsService;
-import org.dromara.warm.flow.orm.entity.FlowInstance;
-import org.dromara.warm.flow.orm.entity.FlowTask;
 import org.springframework.stereotype.Component;
 
 import java.util.HashMap;
@@ -105,6 +102,7 @@ public class WorkflowGlobalListener implements GlobalListener {
         Instance instance = listenerVariable.getInstance();
         Definition definition = listenerVariable.getDefinition();
         Task task = listenerVariable.getTask();
+        List<Task> nextTasks = listenerVariable.getNextTasks();
         Map<String, Object> params = new HashMap<>();
         FlowParams flowParams = listenerVariable.getFlowParams();
         Map<String, Object> variable = new HashMap<>();
@@ -127,10 +125,22 @@ public class WorkflowGlobalListener implements GlobalListener {
             if (StringUtils.isNotBlank(status)) {
                 flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false);
             }
+            if (!BusinessStatusEnum.initialState(instance.getFlowStatus())) {
+                if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1
+                        && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) {
+                    // 如果为画线指定驳回 线条指定为驳回 驳回得节点为申请人节点 则修改流程状态为退回
+                    flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false);
+                    // 修改流程实例状态
+                    instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus());
+                    insService.updateById(instance);
+                }
+            }
         }
         //发布任务事件
-        if (task != null) {
-            flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId(), params);
+        if (CollUtil.isNotEmpty(nextTasks)) {
+            for (Task nextTask : nextTasks) {
+                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(), params);
+            }
         }
         if (ObjectUtil.isNull(flowParams)) {
             return;
@@ -150,7 +160,7 @@ public class WorkflowGlobalListener implements GlobalListener {
             flwTaskService.setCopy(task, flowCopyList);
         }
         if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
-            List<String> messageType = MapUtil.get(variable, FlowConstant.FLOW_COPY_LIST, new TypeReference<>() {
+            List<String> messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {
             });
             String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE);
             // 消息通知
@@ -158,15 +168,12 @@ public class WorkflowGlobalListener implements GlobalListener {
                 flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
             }
         }
-        FlowInstance ins = new FlowInstance();
-        Map<String, Object> variableMap = instance.getVariableMap();
-        variableMap.remove(FlowConstant.FLOW_COPY_LIST);
-        variableMap.remove(FlowConstant.MESSAGE_TYPE);
-        variableMap.remove(FlowConstant.MESSAGE_NOTICE);
-        variableMap.remove(FlowConstant.SUBMIT);
-        ins.setId(instance.getId());
-        ins.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
-        insService.updateById(ins);
+        insService.removeVariables(instance.getId(),
+                FlowConstant.FLOW_COPY_LIST,
+                FlowConstant.MESSAGE_TYPE,
+                FlowConstant.MESSAGE_NOTICE,
+                FlowConstant.SUBMIT
+        );
     }
 
     /**
@@ -182,8 +189,7 @@ public class WorkflowGlobalListener implements GlobalListener {
             return flowStatus;
         } else {
             Long instanceId = instance.getId();
-            List<FlowTask> flowTasks = flwTaskService.selectByInstId(instanceId);
-            if (CollUtil.isEmpty(flowTasks)) {
+            if (flwTaskService.isTaskEnd(instanceId)) {
                 String status = BusinessStatusEnum.FINISH.getStatus();
                 // 更新流程状态为已完成
                 instanceService.updateStatus(instanceId, status);

+ 14 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwCommonService.java

@@ -1,15 +1,16 @@
 package com.vber.workflow.service;
 
+import com.vber.common.core.domain.dto.UserDTO;
+
 import java.util.List;
 
 /**
  * 通用 工作流服务
  *
- * @author LionLi
+ * @author Iwb
  */
 public interface IFlwCommonService {
 
-
     /**
      * 发送消息
      *
@@ -20,6 +21,17 @@ public interface IFlwCommonService {
      */
     void sendMessage(String flowName, Long instId, List<String> messageType, String message);
 
+    /**
+     * 发送消息
+     *
+     * @param messageType 消息类型
+     * @param message     消息内容
+     * @param subject     邮件标题
+     * @param userList    接收用户
+     */
+    void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList);
+
+
     /**
      * 申请人节点编码
      *

+ 17 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java

@@ -5,6 +5,7 @@ import com.vber.common.mybatis.core.page.TableDataInfo;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import org.dromara.warm.flow.orm.entity.FlowInstance;
 
@@ -84,6 +85,14 @@ public interface IFlwInstanceService {
      */
     boolean deleteByInstanceIds(List<Long> instanceIds);
 
+    /**
+     * 按照实例id删除已完成得流程实例
+     *
+     * @param instanceIds 删除的实例id
+     * @return 删除结果
+     */
+    boolean deleteHisByInstanceIds(List<Long> instanceIds);
+
     /**
      * 撤销流程
      *
@@ -125,6 +134,14 @@ public interface IFlwInstanceService {
      */
     Map<String, Object> instanceVariable(Long instanceId);
 
+    /**
+     * 更新流程变量
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    boolean updateVariable(FlowVariableBo bo);
+
     /**
      * 设置流程变量
      *
@@ -141,7 +158,6 @@ public interface IFlwInstanceService {
      */
     FlowInstance selectByTaskId(Long taskId);
 
-
     /**
      * 作废流程
      *

+ 27 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwTaskService.java

@@ -111,11 +111,11 @@ public interface IFlwTaskService {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      * @return 结果
      */
-    List<Node> getBackTaskNode(Long definitionId, String nowNodeCode);
+    List<Node> getBackTaskNode(Long taskId, String nowNodeCode);
 
     /**
      * 终止任务
@@ -165,6 +165,22 @@ public interface IFlwTaskService {
      */
     List<FlowTask> selectByInstId(Long instanceId);
 
+    /**
+     * 按照实例id查询任务
+     *
+     * @param instanceIds 列表
+     * @return 结果
+     */
+    List<FlowTask> selectByInstIds(List<Long> instanceIds);
+
+    /**
+     * 判断流程是否已结束(即该流程实例下是否还有未完成的任务)
+     *
+     * @param instanceId 流程实例ID
+     * @return true 表示任务已全部结束;false 表示仍有任务存在
+     */
+    boolean isTaskEnd(Long instanceId);
+
     /**
      * 任务操作
      *
@@ -190,4 +206,12 @@ public interface IFlwTaskService {
      * @return 节点
      */
     FlowNode getByNodeCode(String nodeCode, Long definitionId);
+
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    boolean urgeTask(FlowUrgeTaskBo bo);
 }

+ 5 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/ITestLeaveService.java

@@ -35,6 +35,11 @@ public interface ITestLeaveService {
      */
     TestLeaveVo insertByBo(TestLeaveBo bo);
 
+    /**
+     * 提交请假并发起流程
+     */
+    TestLeaveVo submitAndFlowStart(TestLeaveBo bo);
+
     /**
      * 修改请假
      */

+ 15 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java

@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
 /**
  * 工作流工具
  *
- * @author LionLi
+ * @author Iwb
  */
 @ConditionalOnEnable
 @Slf4j
@@ -55,6 +55,19 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
         if (CollUtil.isEmpty(userList)) {
             return;
         }
+        sendMessage(messageType, message, "单据审批提醒", userList);
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param messageType 消息类型
+     * @param message     消息内容
+     * @param subject     邮件标题
+     * @param userList    接收用户
+     */
+    @Override
+    public void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList) {
         for (String code : messageType) {
             MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
             if (ObjectUtil.isEmpty(messageTypeEnum)) {
@@ -68,7 +81,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
                     SseMessageUtils.publishMessage(dto);
                 }
                 case EMAIL_MESSAGE -> {
-                    MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message);
+                    MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), subject, message);
                 }
                 case SMS_MESSAGE -> {
                     //todo 短信发送
@@ -76,7 +89,6 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
                 default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
             }
         }
-
     }
 
 

+ 7 - 8
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java

@@ -8,7 +8,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vber.common.core.exception.ServiceException;
-import com.vber.common.core.utils.ObjectUtils;
 import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.json.utils.JsonUtils;
@@ -26,6 +25,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.warm.flow.core.dto.DefJson;
 import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.enums.PublishStatus;
 import org.dromara.warm.flow.core.service.DefService;
 import org.dromara.warm.flow.orm.entity.FlowDefinition;
 import org.dromara.warm.flow.orm.entity.FlowHisTask;
@@ -42,6 +42,7 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import static com.vber.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
@@ -56,7 +57,6 @@ import static com.vber.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
 @RequiredArgsConstructor
 @Service
 public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
-
     private final DefService defService;
     private final FlowDefinitionMapper flowDefinitionMapper;
     private final FlowHisTaskMapper flowHisTaskMapper;
@@ -75,7 +75,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     public TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
-//        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
+        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
         Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
         List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
         return new TableDataInfo<>(list, page.getTotal());
@@ -91,7 +91,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
-//        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
+        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
         Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
         List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
         return new TableDataInfo<>(list, page.getTotal());
@@ -105,7 +105,6 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
             wrapper.in(FlowDefinition::getCategory, StreamUtils.toList(categoryIds, Convert::toStr));
         }
-        wrapper.eq(ObjectUtils.isNotEmpty(flowDefinition.getIsPublish()), FlowDefinition::getIsPublish, flowDefinition.getIsPublish());
         wrapper.orderByDesc(FlowDefinition::getCreateTime);
         return wrapper;
     }
@@ -128,7 +127,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
                 }
             }
             if (CollUtil.isNotEmpty(errorMsg)) {
-                throw new ServiceException("节点【" + StringUtils.join(errorMsg, ",") + "】未配置办理人!");
+                throw new ServiceException("节点【{}】未配置办理人!", String.join(",", errorMsg));
             }
         }
         return defService.publish(id);
@@ -188,7 +187,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             if (CollUtil.isNotEmpty(flowDefinitions)) {
                 String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
                 log.info("流程定义【{}】已被使用不可被删除!", join);
-                throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!");
+                throw new ServiceException("流程定义【{}】已被使用不可被删除!", join);
             }
         }
         try {
@@ -231,7 +230,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             flowDefinition.setId(null);
             flowDefinition.setTenantId(tenantId);
             flowDefinition.setIsPublish(0);
-            flowDefinition.setCategory(String.valueOf(flowCategory.getCategoryId()));
+            flowDefinition.setCategory(Convert.toStr(flowCategory.getCategoryId()));
             int insert = flowDefinitionMapper.insert(flowDefinition);
             if (insert <= 0) {
                 log.info("同步流程定义【{}】失败!", definition.getFlowCode());

+ 93 - 14
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java

@@ -21,6 +21,7 @@ import com.vber.workflow.common.enums.TaskStatusEnum;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowHisTaskVo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import com.vber.workflow.handler.FlowProcessEventHandler;
@@ -109,7 +110,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     @Override
     public FlowInstanceVo queryByBusinessId(Long businessId) {
-        FlowInstance instance = this.selectInstByBusinessId(String.valueOf(businessId));
+        FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
         FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
         Definition definition = defService.getById(instanceVo.getDefinitionId());
         instanceVo.setFlowName(definition.getFlowName());
@@ -210,18 +211,71 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
                 Function.identity()
         );
 
-        // 逐一触发删除事件
-        instances.forEach(instance -> {
-            Definition definition = definitionMap.get(instance.getDefinitionId());
-            if (ObjectUtil.isNull(definition)) {
-                log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                return;
+        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("删除流程实例失败");
             }
-            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-        });
+        } catch (Exception e) {
+            log.warn("操作失败!{}", e.getMessage());
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
 
-        // 删除实例
-        return insService.remove(instanceIds);
+    /**
+     * 按照实例id删除已完成的流程实例
+     *
+     * @param instanceIds 实例id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteHisByInstanceIds(List<Long> instanceIds) {
+        // 获取实例信息
+        List<Instance> instances = insService.getByIds(instanceIds);
+        if (CollUtil.isEmpty(instances)) {
+            log.warn("未找到对应的流程实例信息,无法执行删除操作。");
+            return false;
+        }
+        // 获取定义信息
+        Map<Long, Definition> definitionMap = StreamUtils.toMap(
+                defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+                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));
+            }
+            FlowEngine.taskService().deleteByInsIds(instanceIds);
+            FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+            FlowEngine.insService().removeByIds(instanceIds);
+        } catch (Exception e) {
+            log.warn("操作失败!{}", e.getMessage());
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
     }
 
     /**
@@ -304,9 +358,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
                 if (CollUtil.isNotEmpty(users)) {
                     vo.setApprover(StreamUtils.join(users, User::getProcessedBy));
                 }
-                if (BusinessStatusEnum.isDraftOrCancelOrBack(flowInstance.getFlowStatus())) {
-                    vo.setApprover(LoginHelper.getUserIdStr());
-                }
             }
         }
 
@@ -359,6 +410,34 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
         return Map.of("variableList", variableList, "variable", flowInstance.getVariable());
     }
 
+    /**
+     * 设置流程变量
+     *
+     * @param bo 参数
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateVariable(FlowVariableBo bo) {
+        FlowInstance flowInstance = flowInstanceMapper.selectById(bo.getInstanceId());
+        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());
+        }
+        return true;
+    }
+
     /**
      * 设置流程变量
      *

+ 4 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java

@@ -161,7 +161,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
         TableDataInfo<FlowSpelVo> page = this.queryPageList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                FlowSpelVo::getViewSpel, c -> "", FlowSpelVo::getRemark, null, FlowSpelVo::getCreateTime);
+                FlowSpelVo::getViewSpel, item -> "", FlowSpelVo::getRemark, item -> "", FlowSpelVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -181,7 +181,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
                         .select(FlowSpel::getViewSpel, FlowSpel::getRemark)
                         .in(FlowSpel::getViewSpel, viewSpels)
         );
-        return StreamUtils.toMap(list, FlowSpel::getViewSpel, FlowSpel::getRemark);
+        return StreamUtils.toMap(list, FlowSpel::getViewSpel, x ->
+                StringUtils.isEmpty(x.getRemark()) ? "" : x.getRemark()
+        );
     }
 
 }

+ 75 - 49
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskAssigneeServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Pair;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import com.vber.common.core.domain.dto.OrgDTO;
 import com.vber.common.core.domain.dto.TaskAssigneeDTO;
@@ -12,6 +13,7 @@ import com.vber.common.core.domain.model.TaskAssigneeBody;
 import com.vber.common.core.enums.FormatsType;
 import com.vber.common.core.service.*;
 import com.vber.common.core.utils.DateUtils;
+import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.workflow.common.ConditionalOnEnable;
 import com.vber.workflow.common.enums.TaskAssigneeEnum;
@@ -28,6 +30,7 @@ import org.dromara.warm.flow.ui.vo.HandlerSelectVo;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 流程设计器-获取办理人权限设置列表
@@ -59,7 +62,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     }
 
     /**
-     * 获取办理列表, 同时构建左侧组织机构树状结构
+     * 获取办理列表, 同时构建左侧部门树状结构
      *
      * @param query 查询条件
      * @return HandlerSelectVo
@@ -90,30 +93,25 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
             return Collections.emptyList();
         }
         // 解析并归类 ID,同时记录原始顺序和对应解析结果
-        Map<TaskAssigneeEnum, List<Long>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
-        Map<String, Pair<TaskAssigneeEnum, Long>> parsedMap = new LinkedHashMap<>();
-        List<String> spelList = new ArrayList<>();
+        Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<String, Pair<TaskAssigneeEnum, String>> parsedMap = new LinkedHashMap<>();
         for (String storageId : storageIds) {
-            Pair<TaskAssigneeEnum, Long> parsed = this.parseStorageId(storageId);
+            Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
             parsedMap.put(storageId, parsed);
             if (parsed != null) {
                 typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
-            } else if (StringUtils.startsWith(storageId, "#")) {
-                // #前缀表示SpEL办理人变量策略
-                spelList.add(storageId);
             }
         }
 
         // 查询所有类型对应的 ID 名称映射
-        Map<TaskAssigneeEnum, Map<Long, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<TaskAssigneeEnum, Map<String, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
         typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids)));
-        Map<String, String> spelMap = spelService.selectRemarksBySpels(spelList);
         // 组装返回结果,保持原始顺序
         return parsedMap.entrySet().stream()
                 .map(entry -> {
                     String storageId = entry.getKey();
-                    Pair<TaskAssigneeEnum, Long> parsed = entry.getValue();
-                    String handlerName = (parsed == null) ? spelMap.get(storageId)
+                    Pair<TaskAssigneeEnum, String> parsed = entry.getValue();
+                    String handlerName = (parsed == null) ? null
                             : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap())
                             .get(parsed.getValue());
                     return new HandlerFeedBackVo(storageId, handlerName);
@@ -134,23 +132,40 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     }
 
     /**
-     * 根据任务办理类型获取组织机构数据
+     * 根据任务办理类型获取部门数据
      */
     private List<OrgDTO> fetchOrgData(TaskAssigneeEnum type) {
-        if (type == TaskAssigneeEnum.USER || type == TaskAssigneeEnum.ORG || type == TaskAssigneeEnum.POST) {
+        if (type.needsOrgService()) {
             return orgService.selectOrgsByList();
         }
         return new ArrayList<>();
     }
 
     /**
-     * 构建组织机构树状结构
+     * 获取权限分组名称
+     *
+     * @param type      任务分配人枚举
+     * @param groupName 权限分组
+     * @return 权限分组名称
+     */
+    private String getGroupName(TaskAssigneeEnum type, String groupName) {
+        if (StringUtils.isEmpty(groupName)) {
+            return DEFAULT_GROUP_NAME;
+        }
+        if (type.needsOrgService()) {
+            return orgService.selectOrgNameByIds(groupName);
+        }
+        return DEFAULT_GROUP_NAME;
+    }
+
+    /**
+     * 构建部门树状结构
      */
     private TreeFunDto<OrgDTO> buildOrgTree(List<OrgDTO> orgs) {
         return new TreeFunDto<>(orgs)
-                .setId(org -> String.valueOf(org.getOrgId()))
+                .setId(org -> Convert.toStr(org.getOrgId()))
                 .setName(OrgDTO::getOrgName)
-                .setParentId(org -> String.valueOf(org.getParentId()));
+                .setParentId(org -> Convert.toStr(org.getParentId()));
     }
 
     /**
@@ -161,10 +176,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
                 .setStorageId(assignee -> type.getCode() + assignee.getStorageId())
                 .setHandlerCode(assignee -> StringUtils.blankToDefault(assignee.getHandlerCode(), "无"))
                 .setHandlerName(assignee -> StringUtils.blankToDefault(assignee.getHandlerName(), "无"))
-                .setGroupName(assignee -> StringUtils.defaultIfBlank(
-                        Optional.ofNullable(assignee.getGroupName())
-                                .map(orgService::selectOrgNameByIds)
-                                .orElse(DEFAULT_GROUP_NAME), DEFAULT_GROUP_NAME))
+                .setGroupName(assignee -> this.getGroupName(type, assignee.getGroupName()))
                 .setCreateTime(assignee -> DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, assignee.getCreateTime()));
     }
 
@@ -181,9 +193,9 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
         if (StringUtils.isEmpty(storageIds)) {
             return List.of();
         }
-        Map<TaskAssigneeEnum, List<Long>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
         for (String storageId : storageIds.split(StringUtils.SEPARATOR)) {
-            Pair<TaskAssigneeEnum, Long> parsed = this.parseStorageId(storageId);
+            Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
             if (parsed != null) {
                 typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
             }
@@ -197,38 +209,56 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     /**
      * 根据指定的任务分配类型(TaskAssigneeEnum)和 ID 列表,获取对应的用户信息列表
      *
-     * @param type 任务分配类型,表示用户、角色、组织机构或其他(TaskAssigneeEnum 枚举值)
-     * @param ids  与指定分配类型关联的 ID 列表(例如用户ID、角色ID、组织机构ID等)
+     * @param type 任务分配类型,表示用户、角色、部门或其他(TaskAssigneeEnum 枚举值)
+     * @param ids  与指定分配类型关联的 ID 列表(例如用户ID、角色ID、部门ID等)
      * @return 返回包含用户信息的列表。如果类型为用户(USER),则通过用户ID列表查询;
      * 如果类型为角色(ROLE),则通过角色ID列表查询;
-     * 如果类型为组织机构(DEPT),则通过组织机构ID列表查询;
+     * 如果类型为部门(DEPT),则通过部门ID列表查询;
      * 如果类型为岗位(POST)或无法识别的类型,则返回空列表
      */
-    private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<Long> ids) {
+    private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<String> ids) {
+        if (type == TaskAssigneeEnum.SPEL) {
+            return new ArrayList<>();
+        }
+        List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
         return switch (type) {
-            case USER -> userService.selectListByIds(ids);
-            case ROLE -> userService.selectUsersByRoleIds(ids);
-            case ORG -> userService.selectUsersByOrgIds(ids);
-            case POST -> userService.selectUsersByPostIds(ids);
-            case SPEL -> new ArrayList<>();
+            case USER -> userService.selectListByIds(longIds);
+            case ROLE -> userService.selectUsersByRoleIds(longIds);
+            case ORG -> userService.selectUsersByOrgIds(longIds);
+            case POST -> userService.selectUsersByPostIds(longIds);
+            default -> new ArrayList<>();
         };
     }
 
     /**
      * 根据任务分配类型和对应 ID 列表,批量查询名称映射关系
      *
-     * @param type 分配类型(用户、角色、组织机构、岗位)
+     * @param type 分配类型(用户、角色、部门、岗位)
      * @param ids  ID 列表(如用户ID、角色ID等)
      * @return 返回 Map,其中 key 为 ID,value 为对应的名称
      */
-    private Map<Long, String> getNamesByType(TaskAssigneeEnum type, List<Long> ids) {
-        return switch (type) {
-            case USER -> userService.selectUserNamesByIds(ids);
-            case ROLE -> roleService.selectRoleNamesByIds(ids);
-            case ORG -> orgService.selectOrgNamesByIds(ids);
-            case POST -> postService.selectPostNamesByIds(ids);
-            case SPEL -> new HashMap<>();
+    private Map<String, String> getNamesByType(TaskAssigneeEnum type, List<String> ids) {
+        if (type == TaskAssigneeEnum.SPEL) {
+            return spelService.selectRemarksBySpels(ids);
+        }
+
+        List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
+        Map<Long, String> rawMap = switch (type) {
+            case USER -> userService.selectUserNamesByIds(longIds);
+            case ROLE -> roleService.selectRoleNamesByIds(longIds);
+            case ORG -> orgService.selectOrgNamesByIds(longIds);
+            case POST -> postService.selectPostNamesByIds(longIds);
+            default -> Collections.emptyMap();
         };
+        if (MapUtil.isEmpty(rawMap)) {
+            return Collections.emptyMap();
+        }
+        return rawMap.entrySet()
+                .stream()
+                .collect(Collectors.toMap(
+                        e -> Convert.toStr(e.getKey()),
+                        Map.Entry::getValue
+                ));
     }
 
     /**
@@ -237,24 +267,20 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
      * @param storageId 例如 "user:123" 或 "456"
      * @return Pair(TaskAssigneeEnum, Long),如果格式非法返回 null
      */
-    private Pair<TaskAssigneeEnum, Long> parseStorageId(String storageId) {
+    private Pair<TaskAssigneeEnum, String> parseStorageId(String storageId) {
         if (StringUtils.isBlank(storageId)) {
             return null;
         }
-        // 跳过以 $ 或 # 开头的字符串
-        if (StringUtils.startsWith(storageId, "$") || StringUtils.startsWith(storageId, "#")) {
-            // $前缀表示默认办理人变量策略
-            // #前缀表示spel办理人变量策略
-            log.debug("跳过 storageId 解析,检测到内置变量表达式:{}", storageId);
-            return null;
+        if (TaskAssigneeEnum.isSpelExpression(storageId)) {
+            return Pair.of(TaskAssigneeEnum.SPEL, storageId);
         }
         try {
             String[] parts = storageId.split(StrUtil.COLON, 2);
             if (parts.length < 2) {
-                return Pair.of(TaskAssigneeEnum.USER, Convert.toLong(parts[0]));
+                return Pair.of(TaskAssigneeEnum.USER, parts[0]);
             } else {
                 TaskAssigneeEnum type = TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON);
-                return Pair.of(type, Convert.toLong(parts[1]));
+                return Pair.of(type, parts[1]);
             }
         } catch (Exception e) {
             log.warn("解析 storageId 失败,格式非法:{},错误信息:{}", storageId, e.getMessage());

+ 136 - 47
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java

@@ -3,6 +3,7 @@ package com.vber.workflow.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -19,6 +20,7 @@ import com.vber.common.core.utils.StringUtils;
 import com.vber.common.core.utils.ValidatorUtils;
 import com.vber.common.core.validate.AddGroup;
 import com.vber.common.core.validate.EditGroup;
+import com.vber.common.json.utils.JsonUtils;
 import com.vber.common.mybatis.core.page.PageQuery;
 import com.vber.common.mybatis.core.page.TableDataInfo;
 import com.vber.common.satoken.utils.LoginHelper;
@@ -42,14 +44,12 @@ import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.*;
 import org.dromara.warm.flow.core.enums.NodeType;
 import org.dromara.warm.flow.core.enums.SkipType;
+import org.dromara.warm.flow.core.enums.UserType;
 import org.dromara.warm.flow.core.service.*;
 import org.dromara.warm.flow.core.utils.ExpressionUtil;
 import org.dromara.warm.flow.core.utils.MapUtil;
 import org.dromara.warm.flow.orm.entity.*;
-import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
-import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
-import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
-import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.warm.flow.orm.mapper.*;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -61,7 +61,7 @@ import static com.vber.workflow.common.constant.FlowConstant.*;
 /**
  * 任务 服务层实现
  *
- * @author may
+ * @author Iwb
  */
 @ConditionalOnEnable
 @Slf4j
@@ -85,6 +85,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     private final IFlwTaskAssigneeService flwTaskAssigneeService;
     private final IFlwCommonService flwCommonService;
     private final IFlwNodeExtService flwNodeExtService;
+    private final FlowDefinitionMapper flowDefinitionMapper;
 
     /**
      * 启动任务
@@ -102,7 +103,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         Map<String, Object> variables = startProcessBo.getVariables();
         // 流程发起人
         variables.put(INITIATOR, LoginHelper.getUserIdStr());
-        // 发起人组织机构id
+        // 发起人部门id
         variables.put(INITIATOR_ORG_ID, LoginHelper.getOrgId());
         // 业务id
         variables.put(BUSINESS_ID, businessId);
@@ -118,6 +119,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             dto.setTaskId(taskList.get(0).getId());
             return dto;
         }
+        // 将流程定义内的扩展参数设置到变量中
+        Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
+        Dict dict = JsonUtils.parseMap(definition.getExt());
+        boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
+        variables.put(FlowConstant.AUTO_PASS, autoPass);
+
         FlowParams flowParams = FlowParams.build()
                 .flowCode(startProcessBo.getFlowCode())
                 .variable(startProcessBo.getVariables())
@@ -155,11 +162,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             // 获取抄送人
             List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
             // 设置抄送人
-            completeTaskBo.getVariables().put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
+            Map<String, Object> variables = completeTaskBo.getVariables();
+            variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
             // 消息类型
-            completeTaskBo.getVariables().put(FlowConstant.MESSAGE_TYPE, messageType);
+            variables.put(FlowConstant.MESSAGE_TYPE, messageType);
             // 消息通知
-            completeTaskBo.getVariables().put(FlowConstant.MESSAGE_NOTICE, notice);
+            variables.put(FlowConstant.MESSAGE_NOTICE, notice);
 
 
             FlowTask flowTask = flowTaskMapper.selectById(taskId);
@@ -169,23 +177,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             Instance ins = insService.getById(flowTask.getInstanceId());
             // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
             if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
-                completeTaskBo.getVariables().put(FlowConstant.SUBMIT, true);
+                variables.put(FlowConstant.SUBMIT, true);
             }
             // 设置弹窗处理人
             Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
             if (CollUtil.isNotEmpty(assigneeMap)) {
-                completeTaskBo.getVariables().putAll(assigneeMap);
+                variables.putAll(assigneeMap);
             }
             // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
             FlowParams flowParams = FlowParams.build()
-                    .variable(completeTaskBo.getVariables())
+                    .variable(variables)
                     .skipType(SkipType.PASS.getKey())
                     .message(completeTaskBo.getMessage())
                     .flowStatus(BusinessStatusEnum.WAITING.getStatus())
                     .hisStatus(TaskStatusEnum.PASS.getStatus())
                     .hisTaskExt(completeTaskBo.getFileId());
-            // 执行任务跳转,并根据返回的处理人设置下一步处理人
-            taskService.skip(taskId, flowParams);
+            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);
@@ -193,6 +201,43 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
     }
 
+    /**
+     * 流程办理
+     *
+     * @param taskId     任务ID
+     * @param flowParams 参数
+     * @param instanceId 实例ID
+     * @param autoPass   自动审批
+     */
+    private void skipTask(Long taskId, FlowParams flowParams, Long instanceId, Boolean autoPass) {
+        // 执行任务跳转,并根据返回的处理人设置下一步处理人
+        taskService.skip(taskId, flowParams);
+        List<FlowTask> flowTaskList = selectByInstId(instanceId);
+        if (CollUtil.isEmpty(flowTaskList)) {
+            return;
+        }
+        List<User> userList = FlowEngine.userService()
+                .getByAssociateds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+        if (CollUtil.isEmpty(userList)) {
+            return;
+        }
+        for (FlowTask task : flowTaskList) {
+            if (!task.getId().equals(taskId) && autoPass) {
+                List<User> users = StreamUtils.filter(userList, e -> ObjectUtil.equals(task.getId(), e.getAssociated()) && ObjectUtil.equal(e.getProcessedBy(), LoginHelper.getUserIdStr()));
+                if (CollUtil.isEmpty(users)) {
+                    continue;
+                }
+                flowParams.
+                        message("流程引擎自动审批!").
+                        variable(Map.of(
+                                FlowConstant.SUBMIT, false,
+                                FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
+                                FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
+                skipTask(task.getId(), flowParams, instanceId, true);
+            }
+        }
+    }
+
     /**
      * 设置弹窗处理人
      *
@@ -215,7 +260,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
                     hashSet.addAll(popUserIds);
                     hashSet.addAll(variableUserIds);
-                    map.put(entry.getKey(), String.join(StringUtils.SEPARATOR, hashSet));
+                    map.put(entry.getKey(), StringUtils.joinComma(hashSet));
                 }
             } else {
                 map.put(entry.getKey(), entry.getValue());
@@ -236,7 +281,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             return;
         }
         // 添加抄送人记录
-        FlowHisTask flowHisTask = flowHisTaskMapper.selectList(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getTaskId, task.getId())).get(0);
+        FlowHisTask flowHisTask = flowHisTaskMapper.selectList(
+                new LambdaQueryWrapper<>(FlowHisTask.class)
+                        .eq(FlowHisTask::getTaskId, task.getId())).get(0);
         FlowNode flowNode = new FlowNode();
         flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
         flowNode.setNodeName(flowHisTask.getTargetNodeName());
@@ -256,9 +303,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         List<User> userList = StreamUtils.toList(flowCopyList, x ->
                 new FlowUser()
                         .setType(TaskAssigneeType.COPY.getCode())
-                        .setProcessedBy(String.valueOf(x.getUserId()))
-                        .setAssociated(taskId)
-        );
+                        .setProcessedBy(Convert.toStr(x.getUserId()))
+                        .setAssociated(taskId));
         // 批量保存抄送人员
         FlowEngine.userService().saveBatch(userList);
     }
@@ -320,8 +366,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (CollUtil.isEmpty(taskList)) {
             return;
         }
-        List<User> associatedUsers = FlowEngine.userService()
-                .getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId));
+        List<User> associatedUsers = FlowEngine.userService().getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId));
         Map<Long, List<User>> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
         // 组装用户数据回任务列表
         for (FlowTaskVo task : taskList) {
@@ -418,22 +463,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      */
     @Override
-    public List<Node> getBackTaskNode(Long definitionId, String nowNodeCode) {
-        List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), definitionId);
+    public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
+        FlowTask task = flowTaskMapper.selectById(taskId);
+        List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
         if (!CollUtil.isNotEmpty(nodeCodes)) {
             return nodeCodes;
         }
+        List<User> userList = FlowEngine.userService()
+                .getByAssociateds(Collections.singletonList(task.getId()), UserType.DEPUTE.getKey());
+        if (CollUtil.isNotEmpty(userList)) {
+            return nodeCodes;
+        }
         //判断是否配置了固定驳回节点
         Node node = nodeCodes.get(0);
         if (StringUtils.isNotBlank(node.getAnyNodeSkip())) {
-            return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), definitionId);
+            return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), task.getDefinitionId());
         }
         //获取可驳回的前置节点
-        List<Node> nodes = nodeService.previousNodeList(definitionId, nowNodeCode);
+        List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
         if (CollUtil.isNotEmpty(nodes)) {
             return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
         }
@@ -477,8 +528,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public List<FlowTask> selectByIdList(List<Long> taskIdList) {
-        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
-                .in(FlowTask::getId, taskIdList));
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getId, taskIdList));
     }
 
     /**
@@ -535,17 +585,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             //办理人变量替换
             ExpressionUtil.evalVariable(buildNextTaskList, FlowParams.build().variable(mergeVariable));
             for (FlowNode flowNode : nextFlowNodes) {
-                Optional<Task> first = buildNextTaskList.stream()
-                        .filter(t -> t.getNodeCode().equals(flowNode.getNodeCode()))
-                        .findFirst();
-                first.ifPresent(t -> {
-                    if (CollUtil.isNotEmpty(t.getPermissionList())) {
-                        List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageIds(String.join(StringUtils.SEPARATOR, t.getPermissionList()));
-                        if (CollUtil.isNotEmpty(users)) {
-                            flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId())));
-                        }
+                Task first = StreamUtils.findFirst(buildNextTaskList, t -> t.getNodeCode().equals(flowNode.getNodeCode()));
+                if (ObjectUtil.isNotNull(first) && CollUtil.isNotEmpty(first.getPermissionList())) {
+                    List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageIds(StringUtils.joinComma(first.getPermissionList()));
+                    if (CollUtil.isNotEmpty(users)) {
+                        flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId())));
                     }
-                });
+                }
+
             }
         }
         return nextFlowNodes;
@@ -559,8 +606,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public FlowHisTask selectHisTaskById(Long taskId) {
-        return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class)
-                .eq(FlowHisTask::getId, taskId));
+        return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getId, taskId));
     }
 
     /**
@@ -570,8 +616,29 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public List<FlowTask> selectByInstId(Long instanceId) {
-        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
-                .eq(FlowTask::getInstanceId, instanceId));
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).eq(FlowTask::getInstanceId, instanceId));
+    }
+
+    /**
+     * 按照实例id查询任务
+     *
+     * @param instanceIds 流程实例id
+     */
+    @Override
+    public List<FlowTask> selectByInstIds(List<Long> instanceIds) {
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getInstanceId, instanceIds));
+    }
+
+    /**
+     * 判断流程是否已结束(即该流程实例下是否还有未完成的任务)
+     *
+     * @param instanceId 流程实例ID
+     * @return true 表示任务已全部结束;false 表示仍有任务存在
+     */
+    @Override
+    public boolean isTaskEnd(Long instanceId) {
+        boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper<FlowTask>().eq(FlowTask::getInstanceId, instanceId));
+        return !exists;
     }
 
     /**
@@ -583,8 +650,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
-        FlowParams flowParams = FlowParams.build()
-                .message(bo.getMessage());
+        FlowParams flowParams = FlowParams.build().message(bo.getMessage());
         if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
             flowParams.ignore(true);
         }
@@ -667,8 +733,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                         new FlowUser()
                                 .setType(TaskAssigneeType.APPROVER.getCode())
                                 .setProcessedBy(userId)
-                                .setAssociated(flowTask.getId())
-                );
+                                .setAssociated(flowTask.getId()));
                 if (CollUtil.isNotEmpty(userList)) {
                     FlowEngine.userService().saveBatch(userList);
                 }
@@ -708,4 +773,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                 .eq(FlowNode::getDefinitionId, definitionId));
     }
 
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     */
+    @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());
+        }
+        return true;
+    }
 }

+ 28 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java

@@ -7,10 +7,12 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.vber.common.core.domain.dto.StartProcessDTO;
 import com.vber.common.core.domain.event.ProcessDeleteEvent;
 import com.vber.common.core.domain.event.ProcessEvent;
 import com.vber.common.core.domain.event.ProcessTaskEvent;
 import com.vber.common.core.enums.BusinessStatusEnum;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.MapstructUtils;
 import com.vber.common.core.utils.StringUtils;
@@ -114,6 +116,32 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
         return MapstructUtils.convert(add, TestLeaveVo.class);
     }
 
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public TestLeaveVo submitAndFlowStart(TestLeaveBo bo) {
+        long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true);
+        // 截止日期也算一天
+        bo.setLeaveDays((int) day + 1);
+        TestLeave leave = MapstructUtils.convert(bo, TestLeave.class);
+        boolean flag = baseMapper.insertOrUpdate(leave);
+        if (flag) {
+            bo.setId(leave.getId());
+            // 后端发起需要忽略权限
+            bo.getParams().put("ignore", true);
+
+
+            boolean flag1 = workflowService.startCompleteTask(new StartProcessDTO() {{
+                setBusinessId(leave.getId().toString());
+                setFlowCode(StringUtils.isEmpty(bo.getFlowCode()) ? "leave1" : bo.getFlowCode());
+                setVariables(bo.getParams());
+            }});
+            if (!flag1) {
+                throw new ServiceException("流程发起异常");
+            }
+        }
+        return MapstructUtils.convert(leave, TestLeaveVo.class);
+    }
+
     /**
      * 修改请假
      */

+ 31 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java

@@ -5,9 +5,11 @@ import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.dto.CompleteTaskDTO;
 import com.vber.common.core.domain.dto.StartProcessDTO;
 import com.vber.common.core.domain.dto.StartProcessReturnDTO;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.workflow.common.ConditionalOnEnable;
+import com.vber.workflow.common.enums.MessageTypeEnum;
 import com.vber.workflow.domain.bo.CompleteTaskBo;
 import com.vber.workflow.domain.bo.StartProcessBo;
 import com.vber.workflow.service.IFlwDefinitionService;
@@ -16,7 +18,9 @@ import com.vber.workflow.service.IFlwTaskService;
 import lombok.RequiredArgsConstructor;
 import org.dromara.warm.flow.orm.entity.FlowInstance;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -148,4 +152,31 @@ public class WorkflowServiceImpl implements WorkflowService {
         return flwTaskService.completeTask(completeTask);
     }
 
+    /**
+     * 启动流程并办理第一个任务
+     *
+     * @param startProcess 参数
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean startCompleteTask(StartProcessDTO startProcess) {
+        try {
+            StartProcessReturnDTO result = flwTaskService.startWorkFlow(new StartProcessBo() {{
+                setBusinessId(startProcess.getBusinessId());
+                setFlowCode(startProcess.getFlowCode());
+                setVariables(startProcess.getVariables());
+            }});
+            boolean flag = flwTaskService.completeTask(new CompleteTaskBo() {{
+                setTaskId(result.getTaskId());
+                setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
+                setVariables(startProcess.getVariables());
+            }});
+            if (!flag) {
+                throw new ServiceException("流程发起异常");
+            }
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
 }

+ 38 - 19
UI/VAP_V3.VUE/src/api/workflow/_processInstance.ts

@@ -16,14 +16,14 @@ class processInstanceApi {
 		})
 	}
 
-		/**
+	/**
 	 * 通过业务id获取历史流程图
 	 */
 	flowHisTaskList = (businessId: string | number) => {
 		return Rs.get({
-			url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
-		});
-	};
+			url: `/workflow/instance/flowHisTaskList/${businessId}` + "?t" + Math.random()
+		})
+	}
 
 	/**
 	 * 分页查询当前登录人单据
@@ -32,11 +32,10 @@ class processInstanceApi {
 	 */
 	getPageByCurrent = (query: any) => {
 		return Rs.get({
-			url: '/workflow/instance/pageByCurrent',
+			url: "/workflow/instance/pageByCurrent",
 			params: query
-		});
-	};
-
+		})
+	}
 
 	/**
 	 * 撤销流程
@@ -47,8 +46,8 @@ class processInstanceApi {
 		return Rs.put({
 			url: `/workflow/instance/cancelProcessApply`,
 			data: data
-		});
-	};
+		})
+	}
 
 	/**
 	 * 获取流程变量
@@ -57,9 +56,9 @@ class processInstanceApi {
 	 */
 	instanceVariable = (instanceId: string | number) => {
 		return Rs.get({
-			url: `/workflow/instance/instanceVariable/${instanceId}`,
-		});
-	};
+			url: `/workflow/instance/instanceVariable/${instanceId}`
+		})
+	}
 
 	/**
 	 * 删除
@@ -68,9 +67,19 @@ class processInstanceApi {
 	 */
 	deleteByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
 		return Rs.del({
-			url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
-		});
-	};
+			url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`
+		})
+	}
+
+	/**
+	 * 删除历史流程实例
+	 * @param instanceIds
+	 */
+	deleteHisByInstanceIds(instanceIds: Array<string | number> | string | number) {
+		return Rs.del({
+			url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`
+		})
+	}
 	/**
 	 * 作废流程
 	 * @param data 参数
@@ -80,8 +89,8 @@ class processInstanceApi {
 		return Rs.post({
 			url: `/workflow/instance/invalid`,
 			data: data
-		});
-	};
+		})
+	}
 
 	//-----------------
 	//通过业务id获取历史流程图
@@ -121,6 +130,16 @@ class processInstanceApi {
 			url: `/workflow/instance/deleteFinishAndHisInstance/${businessKeys}`
 		})
 	}
-
+	/**
+	 * 修改流程变量
+	 * @param data 参数
+	 * @returns
+	 */
+	updateVariable(data: any) {
+		return Rs.put({
+			url: `/workflow/instance/updateVariable`,
+			data: data
+		})
+	}
 }
 export default processInstanceApi

+ 28 - 21
UI/VAP_V3.VUE/src/api/workflow/_task.ts

@@ -54,9 +54,6 @@ class taskApi {
 		})
 	}
 
-
-	
-	
 	//任务驳回
 	backProcess = (data: any) => {
 		return Rs.post({
@@ -70,13 +67,12 @@ class taskApi {
 	 * @param taskId
 	 * @returns
 	 */
-  getTask = (taskId: string) => {
+	getTask = (taskId: string) => {
 		return Rs.get({
-			url: '/workflow/task/getTask/' + taskId,
-		});
-	};
+			url: "/workflow/task/getTask/" + taskId
+		})
+	}
 
-	
 	//修改任务办理人
 	updateAssignee = (taskIds: string[], userId: string) => {
 		return Rs.put({
@@ -103,11 +99,11 @@ class taskApi {
 	 * 获取可驳回得任务节点
 	 * @returns
 	 */
-	getBackTaskNode = (definitionId: string, nodeCode: string) => {
+	getBackTaskNode = (taskId: string | number, nodeCode: string) => {
 		return Rs.get({
-			url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
-		});
-	};
+			url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`
+		})
+	}
 
 	/**
 	 * 任务操作 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
@@ -117,8 +113,8 @@ class taskApi {
 		return Rs.post({
 			url: `/workflow/task/taskOperation/${operation}`,
 			data: data
-		});
-	};
+		})
+	}
 
 	/**
 	 * 获取当前任务办理人
@@ -127,21 +123,32 @@ class taskApi {
 	 */
 	currentTaskAllUser = (taskId: string | number) => {
 		return Rs.get({
-			url: `/workflow/task/currentTaskAllUser/${taskId}`,
-		});
-	};
+			url: `/workflow/task/currentTaskAllUser/${taskId}`
+		})
+	}
 
 	/**
 	 * 获取下一节点写
 	 * @param data参数
 	 * @returns
 	 */
-  getNextNodeList = (data: any): any => {
+	getNextNodeList = (data: any): any => {
 		return Rs.post({
-			url: '/workflow/task/getNextNodeList',
+			url: "/workflow/task/getNextNodeList",
 			data: data
-		});
-	};
+		})
+	}
 
+	/**
+	 * 催办任务
+	 * @param data参数
+	 * @returns
+	 */
+	urgeTask = (data: any): any => {
+		return Rs.post({
+			url: "/workflow/task/urgeTask",
+			data: data
+		})
+	}
 }
 export default taskApi

+ 94 - 0
UI/VAP_V3.VUE/src/components/process/MessageType.vue

@@ -0,0 +1,94 @@
+<script setup lang="ts">
+const props = withDefaults(defineProps<{ title: string }>(), { title: "提示" })
+const emits = defineEmits<{
+	(e: "submit", v: any): void
+	(e: "cancel"): void
+}>()
+const modalRef = ref()
+const formRef = ref()
+const form = ref<Record<string, any>>({
+	message: undefined,
+	messageType: ["1"]
+})
+const rules = reactive<Record<string, any>>({
+	messageType: [
+		{
+			required: true,
+			message: "请选择消息提醒",
+			trigger: "change"
+		}
+	],
+	message: [
+		{
+			required: true,
+			message: "请输入消息内容",
+			trigger: "blur"
+		}
+	]
+})
+
+function onSubmit() {
+	if (!formRef.value) return
+	formRef.value.validate().then((valid) => {
+		if (valid) {
+			emits("submit", form.value)
+			close()
+		}
+	})
+}
+//取消
+function onCancel() {
+	modalRef.value!.hide()
+	emits("cancel")
+}
+
+function handleReset() {
+	form.value.taskIdList = []
+	form.value.message = ""
+	form.value.messageType = ["1"]
+}
+function open(ids: string[]) {
+	handleReset()
+	form.value.taskIdList = ids
+	modalRef.value!.show()
+}
+function close() {
+	handleReset()
+	onCancel()
+}
+
+function init() {
+	//
+}
+
+onMounted(init)
+defineExpose({
+	open,
+	close
+})
+</script>
+<template>
+	<div class="app-container">
+		<VbModal
+			v-model:modal="modalRef"
+			:save-auto-close="false"
+			:title="title"
+			@cancel="onCancel"
+			@confirm="onSubmit">
+			<template #body>
+				<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+					<el-form-item label="消息提醒" prop="messageType">
+						<el-checkbox-group v-model="form.messageType">
+							<el-checkbox value="1" name="type" disabled>站内信</el-checkbox>
+							<el-checkbox value="2" name="type">邮件</el-checkbox>
+							<el-checkbox value="3" name="type">短信</el-checkbox>
+						</el-checkbox-group>
+					</el-form-item>
+					<el-form-item label="消息内容" prop="message">
+						<el-input v-model="form.message" type="textarea" resize="none" />
+					</el-form-item>
+				</el-form>
+			</template>
+		</VbModal>
+	</div>
+</template>

+ 1 - 1
UI/VAP_V3.VUE/src/components/process/SubmitVerify.vue

@@ -228,7 +228,7 @@ function handleBackProcessOpen() {
 	backModalRef.value.show()
 	backButtonDisabled.value = true
 	apis.workflow.taskApi
-		.getNextNodeList(task.value.processInstanceId)
+		.getBackTaskNode(task.value.id, task.value.nodeCode)
 		.then((res: any) => {
 			taskNodeList.value = res.data
 			backForm.value.nodeCode = taskNodeList.value[0].nodeId

+ 8 - 3
UI/VAP_V3.VUE/src/core/services/PermissionService.ts

@@ -13,9 +13,12 @@ export function checkPermission(value: string | string[]) {
 		value = [value]
 	}
 	if (value && value instanceof Array && value.length > 0) {
+		if (appStore.authStore.isSuperAdmin()) {
+			return true
+		}
 		const permissions = appStore.authStore.user.permissions
 		const permissionDatas = value
-		const all_permission = "*:*:*"
+		const all_permission = "*"
 		const hasPermission = permissions.some((permission) => {
 			return all_permission === permission || permissionDatas.includes(permission)
 		})
@@ -36,16 +39,18 @@ export function checkPermission(value: string | string[]) {
  * @returns {Boolean}
  */
 export function checkRole(value: string | string[]) {
+	if (appStore.authStore.isSuperAdmin()) {
+		return true
+	}
 	if (typeof value == "string") {
 		value = [value]
 	}
 	if (value && value instanceof Array && value.length > 0) {
 		const roles = appStore.authStore.user.roles
 		const permissionRoles = value
-		const super_admin = "admin"
 
 		const hasRole = roles.some((role) => {
-			return super_admin === role || permissionRoles.includes(role)
+			return permissionRoles.includes(role)
 		})
 
 		if (!hasRole) {

+ 7 - 5
UI/VAP_V3.VUE/src/core/services/RequestService.ts

@@ -143,7 +143,8 @@ Rs.interceptors.response.use(
 		const code = res.data.code || HttpStatus.SUCCESS
 		const data = res.data
 		// 获取错误信息
-		const msg = errorCode[code] || res.data.msg || errorCode.default
+		let msg = errorCode[code] || res.data.msg || errorCode.default
+
 		// 二进制数据则直接返回
 		if (res.request.responseType === "blob" || res.request.responseType === "arraybuffer") {
 			return data
@@ -159,9 +160,7 @@ Rs.interceptors.response.use(
 					})
 					.then(() => {
 						isReLogin.show = false
-						appStore.authStore.logout().then(() => {
-							location.href = import.meta.env.VITE_APP_CONTEXT_PATH + "home"
-						})
+						appStore.authStore.logout()
 					})
 					.catch(() => {
 						isReLogin.show = false
@@ -172,8 +171,11 @@ Rs.interceptors.response.use(
 			if (config.errorAlert) {
 				msgUtil.msgError("没有权限,请联系管理员!")
 			}
-			return Promise.reject(msg)
+			return Promise.reject("没有权限,请联系管理员!")
 		} else if (code === HttpStatus.SERVER_ERROR) {
+			if (!import.meta.env.DEV) {
+				msg = "服务器异常,请联系管理员!"
+			}
 			if (config.errorAlert) {
 				msgUtil.msgError(msg)
 			}

+ 3 - 2
UI/VAP_V3.VUE/src/layouts/main/header/navbar/UserAccountMenu.vue

@@ -2,6 +2,7 @@
 import appStore from "@/stores"
 import apis from "@a"
 import VbWorkflow from "@/layouts/main/header/navbar/UserSubMenuWorkflow.vue"
+import router from "@r"
 
 const user = appStore.authStore.user
 const dynamic = computed(() => {
@@ -19,8 +20,8 @@ function refreshConfig() {
 	})
 }
 function signOut() {
-	appStore.authStore.logout().then(() => {
-		location.href = import.meta.env.VITE_APP_CONTEXT_PATH + "home"
+	appStore.authStore.logout(false).then(() => {
+		router.push("/login")
 	})
 }
 </script>

+ 3 - 3
UI/VAP_V3.VUE/src/router/index.ts

@@ -145,9 +145,9 @@ router.beforeEach((to, _from, next) => {
 					})
 					.catch((err) => {
 						isReLogin.show = false
-						authStore.logout().then(() => {
+						authStore.logout(false).then(() => {
 							message.msgError(err)
-							next({ path: import.meta.env.VITE_APP_CONTEXT_PATH + "home" })
+							router.push("/login")
 						})
 					})
 				appStore.appConfigStore.loadConfig()
@@ -161,7 +161,7 @@ router.beforeEach((to, _from, next) => {
 			// 在免登录白名单,直接进入
 			next()
 		} else {
-			next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
+			next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
 			NProgressClose()
 		}
 	}

+ 10 - 1
UI/VAP_V3.VUE/src/stores/_auth.ts

@@ -3,6 +3,8 @@ import appStore from "@s"
 import apis from "@/api"
 import type { User } from "@@/types/User"
 import type { LoginData } from "@@/types/Account"
+import { use } from "echarts"
+import router from "@r"
 
 export const useAuthStore = defineStore("auth", () => {
 	const errors = ref({})
@@ -38,6 +40,9 @@ export const useAuthStore = defineStore("auth", () => {
 			user.value.roles = ["ROLE_DEFAULT"]
 		}
 	}
+	function isSuperAdmin() {
+		return user.value.userName === "admin" || user.value.roles.includes("super_admin")
+	}
 
 	function changeAvatar(avatar: any) {
 		user.value.avatar = avatar
@@ -101,12 +106,15 @@ export const useAuthStore = defineStore("auth", () => {
 		})
 	}
 	// 退出系统
-	function logout() {
+	function logout(isJump = true) {
 		return new Promise((resolve, reject) => {
 			apis.loginApi
 				.logout()
 				.then(() => {
 					purgeAuth()
+					if (isJump) {
+						router.push(`/login?redirect=${encodeURIComponent(window.location.pathname)}`)
+					}
 					resolve(true)
 				})
 				.catch((error) => {
@@ -119,6 +127,7 @@ export const useAuthStore = defineStore("auth", () => {
 		errors,
 		user,
 		isAuthenticated,
+		isSuperAdmin,
 		login,
 		socialLogin,
 		callback,

+ 11 - 1
UI/VAP_V3.VUE/src/views/tool/gen/index.vue

@@ -70,6 +70,9 @@ const opts = reactive<any>({
 			name: "生成新项目",
 			btnClass: "btn btn-light-info",
 			clickFun: handleProject,
+			disabledFun: () => {
+				return false
+			},
 			iconType: "class",
 			icon: "bi bi-bookmark-plus",
 			permission: "no_auth" //NEW PROJECT
@@ -235,7 +238,8 @@ const newProjectForm = ref<any>({
 	groupId: "com.vap",
 	artifactId: "VberAdminPlus",
 	packageName: "com.vber",
-	modulePrefix: "vber-",
+	modulePrefix: "vber",
+	artifactIdShort: "iwb",
 	title: "VAP后台管理系统",
 	projectDir: "D:\\Project\\" + dayjs().format("YYYYMMDDHHmmss") + "\\"
 })
@@ -252,6 +256,12 @@ const newProjectFormItem: any = [
 		placeholder: "请输入ARTIFACT ID",
 		required: true
 	},
+	{
+		field: "artifactIdShort",
+		label: "ARTIFACT ID SHORT",
+		placeholder: "请输入ARTIFACT ID SHORT",
+		required: true
+	},
 	{
 		field: "packageName",
 		label: "PACKAGE NAME",

+ 69 - 9
UI/VAP_V3.VUE/src/views/workflow/processDefinition/index.vue

@@ -6,6 +6,7 @@ const previewRef = ref()
 const tableRef = ref()
 const modalRef = ref()
 const uploadModalRef = ref()
+const autoPass = ref(false)
 const opts = reactive({
 	columns: [
 		{ field: "id", name: "主键", width: 100, isSort: true, visible: false, tooltip: true },
@@ -106,7 +107,7 @@ const opts = reactive({
 			field: "category",
 			label: "流程分类",
 			class: "w-100",
-			required: false,
+			required: true,
 			placeholder: "请选择流程分类",
 			component: "VST",
 			data: () => categoryOptions.value,
@@ -122,7 +123,7 @@ const opts = reactive({
 			field: "flowName",
 			label: "流程名称",
 			class: "w-100",
-			required: false,
+			required: true,
 			placeholder: "请输入流程定义名称",
 			component: "I"
 		},
@@ -130,10 +131,49 @@ const opts = reactive({
 			field: "flowCode",
 			label: "流程编码",
 			class: "w-100",
-			required: false,
+			required: true,
 			placeholder: "请输入流程定义编码",
 			component: "I"
 		},
+		{
+			field: "modelValue",
+			label: "设计器模式",
+			class: "w-100",
+			required: true,
+			placeholder: "请选择设计器模式",
+			component: "VS",
+			data: [
+				{ label: "经典模式", value: "CLASSICS" },
+				{ label: "仿钉钉模式", value: "MIMIC" }
+			],
+			props: {
+				type: "radio"
+			}
+		},
+		{
+			field: "autoPass",
+			label: "流程配置",
+			class: "w-100",
+			required: false,
+			placeholder: "请选择流程配置",
+			component: "slot"
+		},
+		{
+			field: "formCustom",
+			label: "动态表单",
+			class: "w-100",
+			required: true,
+			placeholder: "请选择是否择动态表单",
+			component: "VS",
+			data: [
+				{ label: "是", value: "Y" },
+				{ label: "否", value: "N" }
+			],
+			props: {
+				type: "radio"
+			}
+		},
+
 		{
 			field: "formPath",
 			label: "表单路径",
@@ -151,9 +191,12 @@ const opts = reactive({
 		category: undefined,
 		flowName: undefined,
 		flowCode: undefined,
-		formPath: undefined
+		formPath: undefined,
+		formCustom: "N",
+		modelValue: "CLASSICS",
+		ext: ""
 	},
-	labelWidth: "80px"
+	labelWidth: "120px"
 })
 const { queryParams, emptyFormData } = toRefs(opts)
 const form = ref<any>(emptyFormData.value)
@@ -214,7 +257,15 @@ function handleCreate() {
 function handleUpdate(row: any) {
 	tableRef.value.defaultHandleFuns.handleUpdate("", row)
 }
-
+function onFormEdit(data: any) {
+	autoPass.value = false
+	if (data.ext != null && data.ext != "") {
+		const extJson = JSON.parse(data.ext)
+		if (extJson.autoPass != null && extJson.autoPass != "") {
+			autoPass.value = extJson.autoPass
+		}
+	}
+}
 /** 部署按钮操作 */
 function handleUpload() {
 	uploadForm.value.category = "0"
@@ -236,6 +287,10 @@ const handleFileChange = (uploadFile: any) => {
 	file.value = uploadFile
 }
 function handleSubmit() {
+	const ext = {
+		autoPass: autoPass.value
+	}
+	form.value.ext = JSON.stringify(ext)
 	apis.workflow.processDefinitionApi.addOrUpdate(form.value).then(() => {
 		handleQuery()
 	})
@@ -332,7 +387,7 @@ function handleDesignView(row) {
 	designModalRef.value.show()
 }
 function getDesignUrl(id, disabled: boolean) {
-	let url = `${import.meta.env.VITE_APP_BASE_API}/.html?id=${id}&disabled=${disabled}`
+	let url = `${import.meta.env.VITE_APP_BASE_API}/.html?id=${id}&onlyDesignShow=${disabled}`
 	url += `&Authorization=Bearer ${getToken()}&clientid=${import.meta.env.VITE_APP_CLIENT_ID}`
 	return url
 }
@@ -405,7 +460,8 @@ onMounted(init)
 					:reset-form-fun="opts.resetForm"
 					v-model:form-data="form"
 					:reset-search-form-fun="resetQuery"
-					:custom-search-fun="handleQuery">
+					:custom-search-fun="handleQuery"
+					@form-edit="onFormEdit">
 					<template #version="{ row }">v{{ row.version }}.0</template>
 					<template #activityStatus="{ row }">
 						<vb-tooltip
@@ -496,7 +552,11 @@ onMounted(init)
 			:form-items="opts.formItems"
 			:label-width="opts.labelWidth"
 			append-to-body
-			@confirm="handleSubmit"></VbModal>
+			@confirm="handleSubmit">
+			<template #autoPass_form>
+				<el-checkbox v-model="autoPass" label="下一节点执行人是当前任务处理人自动审批" />
+			</template>
+		</VbModal>
 		<VbModal
 			v-model:modal="uploadModalRef"
 			title="部署流程"

+ 90 - 22
UI/VAP_V3.VUE/src/views/workflow/processInstance/index.vue

@@ -165,8 +165,20 @@ function handleUpdate(row: any) {
 /** 删除按钮操作 */
 function handleDelete(rows: any[]) {
 	const ids = rows.map((item) => item.id)
-	if (queryParams.value.runStatus == "1") {
-	}
+	message.confirm("是否确认删除流程定义?", "确认删除").then(() => {
+		if (queryParams.value.runStatus == "1") {
+			apis.workflow.processInstanceApi.deleteByInstanceIds(ids).then(() => {
+				message.msgSuccess("删除成功")
+				handleQuery()
+			})
+		} else {
+			apis.workflow.processInstanceApi.deleteHisByInstanceIds(ids).then(() => {
+				message.msgSuccess("删除成功")
+				handleQuery()
+			})
+		}
+	})
+
 	tableRef.value.defaultHandleFuns.handleDelete("", rows)
 }
 function handleExport(rows: any) {
@@ -211,10 +223,10 @@ function handleConfirmInvalid(row: any) {
 }
 function formatToJsonObject(data: string) {
 	try {
-    return JSON.parse(data);
-  } catch (error) {
-    return data;
-  }
+		return JSON.parse(data)
+	} catch (error) {
+		return data
+	}
 }
 
 const pdTableRef = ref()
@@ -251,16 +263,52 @@ const pdVersion = ref("")
 function handleView(row: any) {
 	wfTaskJump(row)
 }
+const instanceId = ref()
 const variableModalRef = ref()
-const variableModalTitle = ref("")
+const variableFormRef = ref()
+const variableForm = ref({
+	instanceId: undefined,
+	key: undefined,
+	value: undefined
+})
+const variableFormRules = reactive<Record<string, any>>({
+	key: [
+		{
+			required: true,
+			message: "请输入KEY",
+			trigger: "blur"
+		}
+	],
+	value: [
+		{
+			required: true,
+			message: "请输入变量值",
+			trigger: "blur"
+		}
+	]
+})
 const variables = ref<any>()
 const flowName = ref("")
 function handleVariable(row: any) {
 	flowName.value = row.flowName
+	instanceId.value = row.id
 	apis.workflow.processInstanceApi.instanceVariable(row.id).then((res: any) => {
 		variables.value = res.data.variable
 		variableModalRef.value.show()
-})
+	})
+}
+function onVariableSubmit() {
+	form.value.instanceId = instanceId.value
+	message.confirm("是否确认保存变量?", "确认保存").then(() => {
+		variableFormRef.value.validate().then(() => {
+			apis.workflow.processInstanceApi.updateVariable(variableForm.value).then((res: any) => {
+				message.msgSuccess("保存成功")
+				apis.workflow.processInstanceApi.instanceVariable(instanceId.value).then((res: any) => {
+					variables.value = res.data.variable
+				})
+			})
+		})
+	})
 }
 function onCategoryChange(data: any) {
 	queryParams.value.category = data.categoryId
@@ -285,10 +333,10 @@ function getTableListFun(query: any) {
 		queryParams.value.runStatus == "1"
 			? apis.workflow.processInstanceApi.getPageByRunning(query).then((res: any) => {
 					resolve(res)
-			  })
+				})
 			: apis.workflow.processInstanceApi.getPageByFinish(query).then((res: any) => {
 					resolve(res)
-			  })
+				})
 	})
 }
 function init() {
@@ -322,9 +370,7 @@ onMounted(init)
 					:check-multiple="true"
 					:reset-search-form-fun="resetQuery"
 					:custom-search-fun="handleQuery">
-					<template #flowName="{ row }">
-						{{ row.flowName }}_V{{ row.version }}
-					</template>
+					<template #flowName="{ row }">{{ row.flowName }}_V{{ row.version }}</template>
 					<template #flowStatus="{ row }">
 						<DictTag type="wf_business_status" :value="row.flowStatus"></DictTag>
 					</template>
@@ -409,16 +455,38 @@ onMounted(init)
 				<VueJsonPretty :data="formatToJsonObject(variables)" />
 				</template>	
 			</VbModal> -->
-			<VbModal v-model:modal="variableModalRef" title="流程变量" :confirm-btn="false" append-to-body>
+		<VbModal
+			v-model:modal="variableModalRef"
+			title="流程变量"
+			:save-auto-close="false"
+			@confirm="onVariableSubmit"
+			append-to-body>
 			<template #body>
-				<span>
-					流程定义名称:
-					<el-tag>{{ flowName }}</el-tag>
-				</span>
-				<dl v-for="(v, index) in formatToJsonObject(variables)" :key="index">
-					<dt>{{ v.key }}:</dt>
-					<dd>{{ v.value }}</dd>
-				</dl>
+				<el-card>
+					<span>
+						流程定义名称:
+						<el-tag>{{ flowName }}</el-tag>
+					</span>
+					<dl v-for="(v, index) in formatToJsonObject(variables)" :key="index">
+						<dt>{{ v.key }}:</dt>
+						<dd>{{ v.value }}</dd>
+					</dl>
+				</el-card>
+				<el-card>
+					<el-form
+						ref="variableFormRef"
+						:model="variableForm"
+						:inline="true"
+						:rules="variableFormRules"
+						label-width="120px">
+						<el-form-item label="变量KEY" prop="key">
+							<el-input v-model="form.key" placeholder="请输入变量KEY" />
+						</el-form-item>
+						<el-form-item label="变量值" prop="value">
+							<el-input v-model="form.value" placeholder="请输入变量值" />
+						</el-form-item>
+					</el-form>
+				</el-card>
 			</template>
 		</VbModal>
 		<VbModal

+ 28 - 1
UI/VAP_V3.VUE/src/views/workflow/task/allTaskWaiting.vue

@@ -138,7 +138,7 @@ function onSubmitUpdateAssignee(data: any) {
 	}
 }
 function handleView(row: any) {
-	wfTaskJump(row)
+	wfTaskJump(row, "view")
 }
 function handleMeddle(row: any) {
 	processMeddleRef.value.open(row.id)
@@ -175,6 +175,20 @@ function getTableListFun(query: any) {
 				})
 	})
 }
+//消息组件
+const messageTypeRef = ref()
+function handleUrgeTaskOpen(row: any) {
+	messageTypeRef.value.open()
+}
+function onUrgeTaskSubmit(data: any) {
+	message.confirm(`是否催办任务`).then(() => {
+		apis.workflow.taskApi.urgeTask(data).then(() => {
+			messageTypeRef.value.close()
+			message.msgSuccess("催办成功")
+			handleQuery()
+		})
+	})
+}
 </script>
 
 <template>
@@ -253,6 +267,17 @@ function getTableListFun(query: any) {
 						</template>
 					</el-button>
 				</vb-tooltip>
+				<vb-tooltip content="催办" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleUrgeTaskOpen(row)"
+						v-hasPermission="'workflow:formManage:edit'">
+						<template #icon>
+							<VbIcon icon-name="notepad-edit" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
 			</template>
 		</VbDataTable>
 		<!-- 选人组件 -->
@@ -268,5 +293,7 @@ function getTableListFun(query: any) {
 			@confirm="onSelectUserCallback"></UserSelect>
 		<!-- 流程干预组件 -->
 		<ProcessMeddle ref="processMeddleRef" @submit="onGetWaitingList"></ProcessMeddle>
+		<!-- 流程催办组件 -->
+		<MessageType ref="messageTypeRef" @submit="onUrgeTaskSubmit"></MessageType>
 	</div>
 </template>