diff --git a/README.md b/README.md index 143710d90..447a2bd49 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ > 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)
-> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网) +> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top) ## 赞助商 @@ -38,8 +38,9 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow
**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/**
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong
+Ruoyi-Plus-Uniapp - https://ruoyi.plus
-[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group) +[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group) # 本框架与RuoYi的功能差异 diff --git a/pom.xml b/pom.xml index e152b0a25..871c53ecb 100644 --- a/pom.xml +++ b/pom.xml @@ -14,27 +14,27 @@ 5.4.1 - 3.4.7 + 3.5.4 UTF-8 UTF-8 17 3.5.16 - 2.8.8 + 2.8.11 0.15.0 - 1.2.0 + 1.3.0 2.3 1.44.0 3.5.12 3.9.1 - 5.8.38 - 3.4.7 - 3.50.0 + 5.8.40 + 3.5.1 + 3.51.0 2.2.7 4.3.1 - 1.6.0 + 1.7.2 1.4.8 0.2.0 - 1.18.36 + 1.18.38 1.80 1.16.7 @@ -48,7 +48,7 @@ 8.7.2-20250603 - 1.7.4 + 1.8.1 3.4.2 @@ -118,25 +118,6 @@ import - - - org.dromara.warm - warm-flow-mybatis-plus-sb3-starter - ${warm-flow.version} - - - org.dromara.warm - warm-flow-plugin-ui-sb-web - ${warm-flow.version} - - - - - me.zhyd.oauth - JustAuth - ${justauth.version} - - org.dromara @@ -313,6 +294,25 @@ ${mapstruct-plus.version} + + + org.dromara.warm + warm-flow-mybatis-plus-sb3-starter + ${warm-flow.version} + + + org.dromara.warm + warm-flow-plugin-ui-sb-web + ${warm-flow.version} + + + + + me.zhyd.oauth + JustAuth + ${justauth.version} + + org.lionsoul diff --git a/ruoyi-admin/Dockerfile b/ruoyi-admin/Dockerfile index fc13f4ff7..0394ccb97 100644 --- a/ruoyi-admin/Dockerfile +++ b/ruoyi-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 findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java index 0848170af..dcd04c5e1 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java @@ -131,15 +131,18 @@ public class CaptchaController { String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; // 生成验证码 CaptchaType captchaType = captchaProperties.getType(); - boolean isMath = CaptchaType.MATH == captchaType; - Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength(); - CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length); + CodeGenerator codeGenerator; + if (CaptchaType.MATH == captchaType) { + codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false); + } else { + codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength()); + } AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); captcha.setGenerator(codeGenerator); captcha.createCode(); // 如果是数学验证码,使用SpEL表达式处理验证码结果 String code = captcha.getCode(); - if (isMath) { + if (CaptchaType.MATH == captchaType) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(StringUtils.remove(code, "=")); code = exp.getValue(String.class); diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java b/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java index 567906e42..5a3351d05 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/service/SysRegisterService.java @@ -87,7 +87,7 @@ public class SysRegisterService { recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } - if (!code.equalsIgnoreCase(captcha)) { + if (!StringUtils.equalsIgnoreCase(code, captcha)) { recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java index e579f9969..abf590b36 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java @@ -102,7 +102,7 @@ public class PasswordAuthStrategy implements IAuthStrategy { loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } - if (!code.equalsIgnoreCase(captcha)) { + if (!StringUtils.equalsIgnoreCase(code, captcha)) { loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 5dd2e00fe..1b52fab96 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -27,8 +27,6 @@ snail-job: port: 2${server.port} # 客户端ip指定 host: - # RPC类型: netty, grpc - rpc-type: grpc --- # 数据源配置 spring: diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index 435a9e856..d77ddf57c 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -30,8 +30,6 @@ snail-job: port: 2${server.port} # 客户端ip指定 host: - # RPC类型: netty, grpc - rpc-type: grpc --- # 数据源配置 spring: diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index e89324145..ff0b88f3b 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -57,6 +57,13 @@ spring: # 开启虚拟线程 仅jdk21可用 virtual: enabled: false + task: + execution: + # 从 springboot 3.5 开始 spring自带线程池 + # 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用 + thread-name-prefix: async- + # 由spring自己初始化线程池 + mode: force # 资源信息 messages: # 国际化资源文件路径 @@ -211,16 +218,6 @@ xss: excludeUrls: - /system/notice -# 全局线程池相关配置 -# 如使用JDK21请直接使用虚拟线程 不要开启此配置 -thread-pool: - # 是否开启线程池 - enabled: false - # 队列最大长度 - queueCapacity: 128 - # 线程池维护线程所允许的空闲时间 - keepAliveSeconds: 300 - --- # 分布式锁 lock4j 全局配置 lock4j: # 获取分布式锁超时时间,默认为 3000 毫秒 @@ -260,13 +257,7 @@ warm-flow: enabled: true # 是否开启设计器ui ui: true + # 是否显示流程图顶部文字 + top-text-show: true # 默认Authorization,如果有多个token,用逗号分隔 token-name: ${sa-token.token-name},clientid - # 流程状态对应的三元色 - chart-status-color: - ## 未办理 - - 62,62,62 - ## 待办理 - - 255,205,23 - ## 已办理 - - 157,255,0 diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java deleted file mode 100644 index cd01e33d5..000000000 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.dromara.common.core.config; - -import cn.hutool.core.util.ArrayUtil; -import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.utils.SpringUtils; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.core.task.VirtualThreadTaskExecutor; -import org.springframework.scheduling.annotation.AsyncConfigurer; - -import java.util.Arrays; -import java.util.concurrent.Executor; - -/** - * 异步配置 - *

- * 如果未使用虚拟线程则生效 - * - * @author Lion Li - */ -@AutoConfiguration -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()); - }; - } - -} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java index 2630485a4..16f309d8f 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java @@ -7,11 +7,9 @@ import org.dromara.common.core.config.properties.ThreadPoolProperties; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.Threads; 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; - } - /** * 执行周期性或定时任务 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java index 2e63f8aca..e011874a2 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/CompleteTaskDTO.java @@ -50,6 +50,11 @@ public class CompleteTaskDTO implements Serializable { */ private String notice; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java index 3934ada55..a6d4cb3a8 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java @@ -30,6 +30,11 @@ public class StartProcessDTO implements Serializable { */ private String flowCode; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java index 73281c5a3..78fb40ec9 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/TaskAssigneeDTO.java @@ -55,14 +55,14 @@ public class TaskAssigneeDTO implements Serializable { Function storageId, Function handlerCode, Function handlerName, - Function groupName, + Function groupName, Function 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()); } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java index e9dc6ec9b..90f5752b1 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java @@ -1,11 +1,15 @@ package org.dromara.common.core.exception; -import lombok.*; +import cn.hutool.core.text.StrFormatter; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import java.io.Serial; /** - * 业务异常 + * 业务异常(支持占位符 {} ) * * @author ruoyi */ @@ -42,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; diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java index 9d1a90195..706d357e7 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java @@ -82,6 +82,7 @@ public interface WorkflowService { * completeTask.getVariables().put("ignore", true); * * @param completeTask 参数 + * @return 结果 */ boolean completeTask(CompleteTaskDTO completeTask); @@ -90,6 +91,15 @@ public interface WorkflowService { * * @param taskId 任务ID * @param message 办理意见 + * @return 结果 */ boolean completeTask(Long taskId, String message); + + /** + * 启动流程并办理第一个任务 + * + * @param startProcess 参数 + * @return 结果 + */ + boolean startCompleteTask(StartProcessDTO startProcess); } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java index b52d95e16..b4d146242 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/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()); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java index bd1aab808..509026f72 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java @@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil { public static Map getParamMap(ServletRequest request) { Map params = new HashMap<>(); for (Map.Entry entry : getParams(request).entrySet()) { - params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR)); + params.put(entry.getKey(), StringUtils.joinComma(entry.getValue())); } return params; } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java index 1342deb71..5172f3225 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java @@ -1,13 +1,11 @@ package org.dromara.common.core.utils; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import java.util.*; import java.util.function.BiFunction; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -29,10 +27,12 @@ public class StreamUtils { */ public static List filter(Collection collection, Predicate function) { if (CollUtil.isEmpty(collection)) { - return CollUtil.newArrayList(); + return Collections.emptyList(); } - // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 - return collection.stream().filter(function).collect(Collectors.toList()); + return collection.stream() + .filter(function) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); } /** @@ -40,13 +40,26 @@ public class StreamUtils { * * @param collection 需要查询的集合 * @param function 过滤方法 - * @return 找到符合条件的第一个元素,没有则返回null + * @return 找到符合条件的第一个元素,没有则返回 Optional.empty() */ - public static E findFirst(Collection collection, Predicate function) { + public static Optional findFirst(Collection collection, Predicate function) { if (CollUtil.isEmpty(collection)) { - return null; + return Optional.empty(); } - return collection.stream().filter(function).findFirst().orElse(null); + return collection.stream() + .filter(function) + .findFirst(); + } + + /** + * 找到流中满足条件的第一个元素值 + * + * @param collection 需要查询的集合 + * @param function 过滤方法 + * @return 找到符合条件的第一个元素,没有则返回 null + */ + public static E findFirstValue(Collection collection, Predicate function) { + return findFirst(collection,function).orElse(null); } /** @@ -54,13 +67,26 @@ public class StreamUtils { * * @param collection 需要查询的集合 * @param function 过滤方法 - * @return 找到符合条件的任意一个元素,没有则返回null + * @return 找到符合条件的任意一个元素,没有则返回 Optional.empty() */ public static Optional findAny(Collection collection, Predicate function) { if (CollUtil.isEmpty(collection)) { return Optional.empty(); } - return collection.stream().filter(function).findAny(); + return collection.stream() + .filter(function) + .findAny(); + } + + /** + * 找到流中任意一个满足条件的元素值 + * + * @param collection 需要查询的集合 + * @param function 过滤方法 + * @return 找到符合条件的任意一个元素,没有则返回null + */ + public static E findAnyValue(Collection collection, Predicate function) { + return findAny(collection,function).orElse(null); } /** @@ -86,7 +112,10 @@ public class StreamUtils { if (CollUtil.isEmpty(collection)) { return StringUtils.EMPTY; } - return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter)); + return collection.stream() + .map(function) + .filter(Objects::nonNull) + .collect(Collectors.joining(delimiter)); } /** @@ -98,10 +127,13 @@ public class StreamUtils { */ public static List sorted(Collection collection, Comparator comparing) { if (CollUtil.isEmpty(collection)) { - return CollUtil.newArrayList(); + return Collections.emptyList(); } - // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 - return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList()); + return collection.stream() + .filter(Objects::nonNull) + .sorted(comparing) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); } /** @@ -116,9 +148,11 @@ public class StreamUtils { */ public static Map toIdentityMap(Collection collection, Function key) { if (CollUtil.isEmpty(collection)) { - return MapUtil.newHashMap(); + return Collections.emptyMap(); } - return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); + return collection.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); } /** @@ -135,9 +169,27 @@ public class StreamUtils { */ public static Map toMap(Collection collection, Function key, Function value) { if (CollUtil.isEmpty(collection)) { - return MapUtil.newHashMap(); + return Collections.emptyMap(); } - return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l)); + return collection.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(key, value, (l, r) -> l)); + } + + /** + * 获取 map 中的数据作为新 Map 的 value ,key 不变 + * @param map 需要处理的map + * @param take 取值函数 + * @param map中的key类型 + * @param map中的value类型 + * @param 新map中的value类型 + * @return 新的map + */ + public static Map toMap(Map map, BiFunction take) { + if (CollUtil.isEmpty(map)) { + return Collections.emptyMap(); + } + return toMap(map.entrySet(), Map.Entry::getKey, entry -> take.apply(entry.getKey(), entry.getValue())); } /** @@ -152,10 +204,10 @@ public class StreamUtils { */ public static Map> groupByKey(Collection collection, Function key) { if (CollUtil.isEmpty(collection)) { - return MapUtil.newHashMap(); + return Collections.emptyMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); } @@ -173,10 +225,10 @@ public class StreamUtils { */ public static Map>> groupBy2Key(Collection collection, Function key1, Function key2) { if (CollUtil.isEmpty(collection)) { - return MapUtil.newHashMap(); + return Collections.emptyMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList()))); } @@ -193,11 +245,11 @@ public class StreamUtils { * @return 分类后的map */ public static Map> group2Map(Collection collection, Function key1, Function key2) { - if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { - return MapUtil.newHashMap(); + if (CollUtil.isEmpty(collection)) { + return Collections.emptyMap(); } - return collection - .stream().filter(Objects::nonNull) + return collection.stream() + .filter(Objects::nonNull) .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l))); } @@ -213,10 +265,9 @@ public class StreamUtils { */ public static List toList(Collection collection, Function function) { if (CollUtil.isEmpty(collection)) { - return CollUtil.newArrayList(); + return Collections.emptyList(); } - return collection - .stream() + return collection.stream() .map(function) .filter(Objects::nonNull) // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 @@ -234,11 +285,10 @@ public class StreamUtils { * @return 转化后的Set */ public static Set toSet(Collection collection, Function function) { - if (CollUtil.isEmpty(collection) || function == null) { - return CollUtil.newHashSet(); + if (CollUtil.isEmpty(collection)) { + return Collections.emptySet(); } - return collection - .stream() + return collection.stream() .map(function) .filter(Objects::nonNull) .collect(Collectors.toSet()); @@ -258,26 +308,20 @@ public class StreamUtils { * @return 合并后的map */ public static Map merge(Map map1, Map map2, BiFunction merge) { - if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) { - return MapUtil.newHashMap(); - } else if (MapUtil.isEmpty(map1)) { - map1 = MapUtil.newHashMap(); - } else if (MapUtil.isEmpty(map2)) { - map2 = MapUtil.newHashMap(); + if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) { + // 如果两个 map 都为空,则直接返回空的 map + return Collections.emptyMap(); + } else if (CollUtil.isEmpty(map1)) { + // 如果 map1 为空,则直接处理返回 map2 + return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue())); + } else if (CollUtil.isEmpty(map2)) { + // 如果 map2 为空,则直接处理返回 map1 + return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null)); } - Set key = new HashSet<>(); - key.addAll(map1.keySet()); - key.addAll(map2.keySet()); - Map map = new HashMap<>(); - for (K t : key) { - X x = map1.get(t); - Y y = map2.get(t); - V z = merge.apply(x, y); - if (z != null) { - map.put(t, z); - } - } - return map; + Set keySet = new HashSet<>(); + keySet.addAll(map1.keySet()); + keySet.addAll(map2.keySet()); + return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key))); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java index 716573431..6eac2fcbe 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java @@ -260,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { if (s != null) { final int len = s.length(); if (s.length() <= size) { - sb.append(String.valueOf(c).repeat(size - len)); + sb.append(Convert.toStr(c).repeat(size - len)); sb.append(s); } else { return s.substring(len - size, len); } } else { - sb.append(String.valueOf(c).repeat(Math.max(0, size))); + sb.append(Convert.toStr(c).repeat(Math.max(0, size))); } return sb.toString(); } @@ -361,5 +361,24 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { return input; } } + /** + * 将可迭代对象中的元素使用逗号拼接成字符串 + * + * @param iterable 可迭代对象,如 List、Set 等 + * @return 拼接后的字符串 + */ + public static String joinComma(Iterable iterable) { + return StringUtils.join(iterable, SEPARATOR); + } + + /** + * 将数组中的元素使用逗号拼接成字符串 + * + * @param array 任意类型的数组 + * @return 拼接后的字符串 + */ + public static String joinComma(Object[] array) { + return StringUtils.join(array, SEPARATOR); + } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 43c7fcfe0..5c3db08a5 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,4 @@ org.dromara.common.core.config.ApplicationConfig -org.dromara.common.core.config.AsyncConfig org.dromara.common.core.config.ThreadPoolConfig org.dromara.common.core.config.ValidatorConfig org.dromara.common.core.utils.SpringUtils diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java index 098f6bc8d..38b22f388 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java @@ -6,6 +6,7 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties; 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,14 @@ import org.springframework.context.annotation.Bean; public class ApiDecryptAutoConfiguration { @Bean - public FilterRegistrationBean cryptoFilterRegistration(ApiDecryptProperties properties) { - FilterRegistrationBean 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); } + } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java new file mode 100644 index 000000000..204a88e25 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java @@ -0,0 +1,200 @@ +package org.dromara.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 lombok.SneakyThrows; +import org.apache.poi.ss.util.CellRangeAddress; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.excel.annotation.CellMerge; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * 单元格合并处理器 + * + * @author Lion Li + */ +public class CellMergeHandler { + + private final boolean hasTitle; + private int rowIndex; + + private CellMergeHandler(final boolean hasTitle) { + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + } + + @SneakyThrows + public List handle(List rows) { + // 如果入参为空集合则返回空集 + if (CollUtil.isEmpty(rows)) { + return Collections.emptyList(); + } + + // 获取有合并注解的字段 + Map mergeFields = getFieldColumnIndexMap(rows.get(0).getClass()); + // 如果没有需要合并的字段则返回空集 + if (CollUtil.isEmpty(mergeFields)) { + return Collections.emptyList(); + } + + // 结果集 + List result = new ArrayList<>(); + + // 生成两两合并单元格 + Map rowRepeatCellMap = new HashMap<>(); + for (Map.Entry item : mergeFields.entrySet()) { + Field field = item.getKey(); + FieldColumnIndex itemValue = item.getValue(); + int colNum = itemValue.colIndex(); + CellMerge cellMerge = itemValue.cellMerge(); + + for (int i = 0; i < rows.size(); i++) { + // 当前行数据 + Object currentRowObj = rows.get(i); + // 当前行数据字段值 + Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName()); + + // 空值跳过不处理 + if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) { + continue; + } + + // 单元格合并Map是否存在数据,如果不存在则添加当前行的字段值 + if (!rowRepeatCellMap.containsKey(field)) { + rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i)); + continue; + } + + // 获取 单元格合并Map 中字段值 + RepeatCell repeatCell = rowRepeatCellMap.get(field); + Object cellValue = repeatCell.value(); + int current = repeatCell.current(); + + // 检查是否满足合并条件 + // currentRowObj 当前行数据 + // rows.get(i - 1) 上一行数据 注:由于 if (!rowRepeatCellMap.containsKey(field)) 条件的存在,所以该 i 必不可能小于1 + // cellMerge 当前行字段合并注解 + boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge); + + // 是否添加到结果集 + boolean isAddResult = false; + // 最新行 + int lastRow = i + rowIndex - 1; + + // 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换 + if (!currentRowObjFieldVal.equals(cellValue) || !merge) { + rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i)); + isAddResult = true; + } + + // 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后 + if (i == rows.size() - 1) { + isAddResult = true; + if (i > current) { + lastRow = i + rowIndex; + } + } + + if (isAddResult && i > current) { + result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum)); + } + } + } + return result; + } + + /** + * 获取带有合并注解的字段列索引和合并注解信息Map集 + */ + private Map getFieldColumnIndexMap(Class clazz) { + 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); + }); + + // 有注解的字段 + Map mergeFields = new HashMap<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (!field.isAnnotationPresent(CellMerge.class)) { + continue; + } + CellMerge cm = field.getAnnotation(CellMerge.class); + int index = cm.index() == -1 ? i : cm.index(); + mergeFields.put(field, FieldColumnIndex.of(index, cm)); + + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + return mergeFields; + } + + private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) { + final String[] mergeBy = cellMerge.mergeBy(); + if (StrUtil.isAllNotBlank(mergeBy)) { + //比对当前行和上一行的各个属性值一一比对 如果全为真 则为真 + for (String fieldName : mergeBy) { + final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName); + final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName); + if (!Objects.equals(valPre, valCurrent)) { + //依赖字段如有任一不等值,则标记为不可合并 + return false; + } + } + } + return true; + } + + /** + * 单元格合并 + */ + record RepeatCell(Object value, int current) { + static RepeatCell of(Object value, int current) { + return new RepeatCell(value, current); + } + } + + /** + * 字段列索引和合并注解信息 + */ + record FieldColumnIndex(int colIndex, CellMerge cellMerge) { + static FieldColumnIndex of(int colIndex, CellMerge cellMerge) { + return new FieldColumnIndex(colIndex, cellMerge); + } + } + + /** + * 创建一个单元格合并处理器实例 + * + * @param hasTitle 是否合并标题 + * @return 单元格合并处理器 + */ + public static CellMergeHandler of(final boolean hasTitle) { + return new CellMergeHandler(hasTitle); + } + + /** + * 创建一个单元格合并处理器实例(默认不合并标题) + * + * @return 单元格合并处理器 + */ + public static CellMergeHandler of() { + return new CellMergeHandler(false); + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java index 515f68e1b..34bf4a42f 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java @@ -1,24 +1,15 @@ package org.dromara.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.ExcelProperty; import cn.idev.excel.metadata.Head; import cn.idev.excel.write.handler.WorkbookWriteHandler; import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext; import cn.idev.excel.write.merge.AbstractMergeStrategy; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; -import org.dromara.common.core.utils.reflect.ReflectUtils; -import org.dromara.common.excel.annotation.CellMerge; -import java.lang.reflect.Field; import java.util.*; /** @@ -30,134 +21,39 @@ import java.util.*; public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler { private final List cellList; - private final boolean hasTitle; - private int rowIndex; + + public CellMergeStrategy(List cellList) { + this.cellList = cellList; + } public CellMergeStrategy(List list, boolean hasTitle) { - this.hasTitle = hasTitle; - // 行合并开始下标 - this.rowIndex = hasTitle ? 1 : 0; - this.cellList = handle(list, hasTitle); + this.cellList = CellMergeHandler.of(hasTitle).handle(list); } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + if (CollUtil.isEmpty(cellList)){ + return; + } //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空 final int rowIndex = cell.getRowIndex(); - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress cellAddresses : cellList) { - final int firstRow = cellAddresses.getFirstRow(); - if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ - cell.setBlank(); - } + for (CellRangeAddress cellAddresses : cellList) { + final int firstRow = cellAddresses.getFirstRow(); + if (cellAddresses.isInRange(cell) && rowIndex != firstRow){ + cell.setBlank(); } } } @Override public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) { + if (CollUtil.isEmpty(cellList)){ + return; + } //当前表格写完后,统一写入 - if (CollUtil.isNotEmpty(cellList)){ - for (CellRangeAddress item : cellList) { - context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); - } + for (CellRangeAddress item : cellList) { + context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item); } } - @SneakyThrows - private List handle(List list, boolean hasTitle) { - List cellList = new ArrayList<>(); - if (CollUtil.isEmpty(list)) { - return cellList; - } - Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName())); - - // 有注解的字段 - List mergeFields = new ArrayList<>(); - List mergeFieldsIndex = new ArrayList<>(); - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - if (field.isAnnotationPresent(CellMerge.class)) { - CellMerge cm = field.getAnnotation(CellMerge.class); - mergeFields.add(field); - mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); - if (hasTitle) { - ExcelProperty property = field.getAnnotation(ExcelProperty.class); - rowIndex = Math.max(rowIndex, property.value().length); - } - } - } - - Map map = new HashMap<>(); - // 生成两两合并单元格 - for (int i = 0; i < list.size(); i++) { - for (int j = 0; j < mergeFields.size(); j++) { - Field field = mergeFields.get(j); - Object val = ReflectUtils.invokeGetter(list.get(i), field.getName()); - - int colNum = mergeFieldsIndex.get(j); - if (!map.containsKey(field)) { - map.put(field, new RepeatCell(val, i)); - } else { - RepeatCell repeatCell = map.get(field); - Object cellValue = repeatCell.getValue(); - if (cellValue == null || "".equals(cellValue)) { - // 空值跳过不合并 - continue; - } - - if (!cellValue.equals(val)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } else if (i == list.size() - 1) { - if (!isMerge(list, i, field)) { - // 如果最后一行不能合并,检查之前的数据是否需要合并 - if (i - repeatCell.getCurrent() > 1) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - } else if (i > repeatCell.getCurrent()) { - // 如果最后一行可以合并,则直接合并到最后 - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); - } - } else if (!isMerge(list, i, field)) { - if ((i - repeatCell.getCurrent() > 1)) { - cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); - } - map.put(field, new RepeatCell(val, i)); - } - } - } - } - return cellList; - } - - private boolean isMerge(List list, int i, Field field) { - boolean isMerge = true; - CellMerge cm = field.getAnnotation(CellMerge.class); - final String[] mergeBy = cm.mergeBy(); - if (StrUtil.isAllNotBlank(mergeBy)) { - //比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真 - for (String fieldName : mergeBy) { - final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName); - final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName); - if (!Objects.equals(valPre, valCurrent)) { - //依赖字段如有任一不等值,则标记为不可合并 - isMerge = false; - } - } - } - return isMerge; - } - - @Data - @AllArgsConstructor - static class RepeatCell { - - private Object value; - - private int current; - - } } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java index 8b53a0ce0..7cdb5c5c0 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java @@ -1,5 +1,6 @@ package org.dromara.common.excel.core; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Data; @@ -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("选项数据不符合规则,仅允许使用中英文字符以及数字"); } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java index f3b641545..139728390 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java @@ -1,6 +1,7 @@ package org.dromara.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 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 values = EnumUtil.getFieldValues(format.enumClass(), format.textField()); - options = StreamUtils.toList(values, String::valueOf); + options = StreamUtils.toList(values, Convert::toStr); } if (ObjectUtil.isNotEmpty(options)) { // 仅当下拉可选项不为空时执行 diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java index 1b35e559a..74dbccbac 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java @@ -27,6 +27,7 @@ import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * Excel相关处理 @@ -203,6 +204,44 @@ public class ExcelUtil { builder.doWrite(list); } + /** + * 导出excel + * + * @param headType 带Excel注解的类型 + * @param os 输出流 + * @param options Excel下拉可选项 + * @param consumer 导出助手消费函数 + */ + public static void exportExcel(Class headType, OutputStream os, List options, Consumer> consumer) { + try (ExcelWriter writer = FastExcel.write(os, headType) + .autoCloseStream(false) + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + // 批注必填项处理 + .registerWriteHandler(new DataWriteHandler(headType)) + // 添加下拉框操作 + .registerWriteHandler(new ExcelDownHandler(options)) + .build()) { + // 执行消费函数 + consumer.accept(ExcelWriterWrapper.of(writer)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出excel + * + * @param headType 带Excel注解的类型 + * @param os 输出流 + * @param consumer 导出助手消费函数 + */ + public static void exportExcel(Class headType, OutputStream os, Consumer> consumer) { + exportExcel(headType, os, null, consumer); + } + /** * 单表多数据模板导出 模板格式为 {.属性} * diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java new file mode 100644 index 000000000..396f3713e --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelWriterWrapper.java @@ -0,0 +1,127 @@ +package org.dromara.common.excel.utils; + +import cn.idev.excel.ExcelWriter; +import cn.idev.excel.FastExcel; +import cn.idev.excel.context.WriteContext; +import cn.idev.excel.write.builder.ExcelWriterSheetBuilder; +import cn.idev.excel.write.builder.ExcelWriterTableBuilder; +import cn.idev.excel.write.metadata.WriteSheet; +import cn.idev.excel.write.metadata.WriteTable; +import cn.idev.excel.write.metadata.fill.FillConfig; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * ExcelWriterWrapper Excel写出包装器 + *
+ * 提供了一组与 ExcelWriter 一一对应的写出方法,避免直接提供 ExcelWriter 而导致的一些不可控问题(比如提前关闭了IO流等) + * + * @author 秋辞未寒 + * @see ExcelWriter + */ +public record ExcelWriterWrapper(ExcelWriter excelWriter) { + + public void write(Collection data, WriteSheet writeSheet) { + excelWriter.write(data, writeSheet); + } + + public void write(Supplier> supplier, WriteSheet writeSheet) { + excelWriter.write(supplier.get(), writeSheet); + } + + public void write(Collection data, WriteSheet writeSheet, WriteTable writeTable) { + excelWriter.write(data, writeSheet, writeTable); + } + + public void write(Supplier> supplier, WriteSheet writeSheet, WriteTable writeTable) { + excelWriter.write(supplier.get(), writeSheet, writeTable); + } + + public void fill(Object data, WriteSheet writeSheet) { + excelWriter.fill(data, writeSheet); + } + + public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) { + excelWriter.fill(data, fillConfig, writeSheet); + } + + public void fill(Supplier supplier, WriteSheet writeSheet) { + excelWriter.fill(supplier, writeSheet); + } + + public void fill(Supplier supplier, FillConfig fillConfig, WriteSheet writeSheet) { + excelWriter.fill(supplier, fillConfig, writeSheet); + } + + public WriteContext writeContext() { + return excelWriter.writeContext(); + } + + /** + * 创建一个 ExcelWriterWrapper + * + * @param excelWriter ExcelWriter + * @return ExcelWriterWrapper + */ + public static ExcelWriterWrapper of(ExcelWriter excelWriter) { + return new ExcelWriterWrapper<>(excelWriter); + } + + // -------------------------------- sheet start + + public static WriteSheet buildSheet(Integer sheetNo, String sheetName) { + return sheetBuilder(sheetNo, sheetName).build(); + } + + public static WriteSheet buildSheet(Integer sheetNo) { + return sheetBuilder(sheetNo).build(); + } + + public static WriteSheet buildSheet(String sheetName) { + return sheetBuilder(sheetName).build(); + } + + public static WriteSheet buildSheet() { + return sheetBuilder().build(); + } + + public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo, String sheetName) { + return FastExcel.writerSheet(sheetNo, sheetName); + } + + public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo) { + return FastExcel.writerSheet(sheetNo); + } + + public static ExcelWriterSheetBuilder sheetBuilder(String sheetName) { + return FastExcel.writerSheet(sheetName); + } + + public static ExcelWriterSheetBuilder sheetBuilder() { + return FastExcel.writerSheet(); + } + + // -------------------------------- sheet end + + // -------------------------------- table start + + public static WriteTable buildTable(Integer tableNo) { + return tableBuilder(tableNo).build(); + } + + public static WriteTable buildTable() { + return tableBuilder().build(); + } + + public static ExcelWriterTableBuilder tableBuilder(Integer tableNo) { + return FastExcel.writerTable(tableNo); + } + + public static ExcelWriterTableBuilder tableBuilder() { + return FastExcel.writerTable(); + } + + // -------------------------------- table end + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java index 77cf83381..039257387 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java @@ -1,5 +1,6 @@ package org.dromara.common.json.config; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; @@ -28,20 +29,24 @@ import java.util.TimeZone; @AutoConfiguration(before = JacksonAutoConfiguration.class) public class JacksonConfig { + @Bean + public Module registerJavaTimeModule() { + // 全局配置序列化返回 JSON 处理 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer()); + return javaTimeModule; + } + @Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { - // 全局配置序列化返回 JSON 处理 - JavaTimeModule javaTimeModule = new JavaTimeModule(); - javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); - javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); - javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); - javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer()); - builder.modules(javaTimeModule); builder.timeZone(TimeZone.getDefault()); log.info("初始化 jackson 配置"); }; diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java index 069b924f0..21c6a6aa6 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/CustomDateDeserializer.java @@ -1,9 +1,11 @@ package org.dromara.common.json.handler; +import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import org.dromara.common.core.utils.ObjectUtils; import java.io.IOException; import java.util.Date; @@ -25,7 +27,11 @@ public class CustomDateDeserializer extends JsonDeserializer { */ @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return DateUtil.parse(p.getText()); + DateTime parse = DateUtil.parse(p.getText()); + if (ObjectUtils.isNull(parse)) { + return null; + } + return parse.toJdkDate(); } } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java new file mode 100644 index 000000000..54d5ad41a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java @@ -0,0 +1,54 @@ +package org.dromara.common.mybatis.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.helper.DataPermissionHelper; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限注解Advice + * + * @author 秋辞未寒 + */ +@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; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java deleted file mode 100644 index 1c83cc392..000000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.dromara.common.mybatis.aspect; - -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; -import org.dromara.common.mybatis.annotation.DataPermission; -import org.dromara.common.mybatis.helper.DataPermissionHelper; - -/** - * 数据权限处理 - * - * @author Lion Li - */ -@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(); - } - -} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java new file mode 100644 index 000000000..4b7d945a3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java @@ -0,0 +1,39 @@ +package org.dromara.common.mybatis.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.springframework.aop.support.StaticMethodMatcherPointcut; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限匹配切点 + * + * @author 秋辞未寒 + */ +@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); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java new file mode 100644 index 000000000..351288ce4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java @@ -0,0 +1,33 @@ +package org.dromara.common.mybatis.aspect; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; + +/** + * 数据权限注解切面定义 + * + * @author 秋辞未寒 + */ +@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; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java index 00c26912e..b08825731 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/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 org.dromara.common.core.factory.YmlPropertySourceFactory; import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.mybatis.aspect.DataPermissionAspect; +import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.handler.MybatisExceptionHandler; import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler; import org.dromara.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 Lion Li */ +@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(); } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java index 5084424eb..2d5244bcf 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java @@ -42,17 +42,46 @@ public enum DataBaseType { * 根据数据库产品名称查找对应的数据库类型 * * @param databaseProductName 数据库产品名称 - * @return 对应的数据库类型枚举值,如果未找到则返回 null + * @return 对应的数据库类型枚举值 */ public static DataBaseType find(String databaseProductName) { if (StringUtils.isBlank(databaseProductName)) { - return null; + return MY_SQL; } for (DataBaseType type : values()) { if (type.getType().equals(databaseProductName)) { return type; } } - return null; + return MY_SQL; } + + /** + * 判断是否为 MySQL 类型 + */ + public boolean isMySql() { + return this == MY_SQL; + } + + /** + * 判断是否为 Oracle 类型 + */ + public boolean isOracle() { + return this == ORACLE; + } + + /** + * 判断是否为 PostgreSQL 类型 + */ + public boolean isPostgreSql() { + return this == POSTGRE_SQL; + } + + /** + * 判断是否为 SQL Server 类型 + */ + public boolean isSqlServer() { + return this == SQL_SERVER; + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java index 6dee1214a..5c2ca983b 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.handler; -import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; @@ -10,7 +9,6 @@ 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.dromara.common.core.domain.dto.RoleDTO; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.exception.ServiceException; @@ -22,22 +20,13 @@ import org.dromara.common.mybatis.annotation.DataPermission; import org.dromara.common.mybatis.enums.DataScopeType; import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.satoken.utils.LoginHelper; -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; /** @@ -49,11 +38,6 @@ import java.util.function.Function; @Slf4j public class PlusDataPermissionHandler { - /** - * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行) - */ - private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); - /** * spel 解析器 */ @@ -64,27 +48,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 是否为查询语句 * @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)) { @@ -206,92 +180,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; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java index cd43c6831..487ffd375 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java @@ -26,7 +26,14 @@ public class DataBaseHelper { private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); /** - * 获取当前数据库类型 + * 获取当前数据源对应的数据库类型 + *

+ * 通过 DynamicRoutingDataSource 获取当前线程绑定的数据源, + * 然后从数据源获取数据库连接,利用连接的元数据获取数据库产品名称, + * 最后调用 DataBaseType.find 方法将数据库名称转换为对应的枚举类型 + * + * @return 当前数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL + * @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常 */ public static DataBaseType getDataBaseType() { DataSource dataSource = DS.determineDataSource(); @@ -39,37 +46,31 @@ public class DataBaseHelper { } } - public static boolean isMySql() { - return DataBaseType.MY_SQL == getDataBaseType(); - } - - public static boolean isOracle() { - return DataBaseType.ORACLE == getDataBaseType(); - } - - public static boolean isPostgerSql() { - return DataBaseType.POSTGRE_SQL == getDataBaseType(); - } - - public static boolean isSqlServer() { - return DataBaseType.SQL_SERVER == getDataBaseType(); - } - + /** + * 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段 + *

+ * 用于判断指定值是否存在于逗号分隔的字符串列中,SQL写法根据不同数据库方言自动切换: + * - Oracle 使用 instr 函数 + * - PostgreSQL 使用 strpos 函数 + * - SQL Server 使用 charindex 函数 + * - 其他默认使用 MySQL 的 find_in_set 函数 + * + * @param var1 要查找的值(支持任意类型,内部会转换成字符串) + * @param var2 存储逗号分隔值的数据库列名 + * @return 适用于当前数据库的 SQL 条件字符串,通常用于 where 或 apply 中拼接 + */ public static String findInSet(Object var1, String var2) { - DataBaseType dataBasyType = getDataBaseType(); String var = Convert.toStr(var1); - if (dataBasyType == DataBaseType.SQL_SERVER) { - // charindex(',100,' , ',0,100,101,') <> 0 - return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); - } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { - // (select strpos(',0,100,101,' , ',100,')) <> 0 - return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); - } else if (dataBasyType == DataBaseType.ORACLE) { + return switch (getDataBaseType()) { // instr(',0,100,101,' , ',100,') <> 0 - return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); - } - // find_in_set(100 , '0,100,101') - return "find_in_set('%s' , %s) <> 0".formatted(var, var2); + case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); + // (select strpos(',0,100,101,' , ',100,')) <> 0 + case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); + // charindex(',100,' , ',0,100,101,') <> 0 + case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); + // find_in_set(100 , '0,100,101') + default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2); + }; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java index 85a4d0abc..b37d96ed1 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java @@ -35,16 +35,7 @@ import java.util.List; @Slf4j public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor { - private final PlusDataPermissionHandler dataPermissionHandler; - - /** - * 构造函数,初始化 PlusDataPermissionHandler 实例 - * - * @param mapperPackage 扫描的映射器包 - */ - public PlusDataPermissionInterceptor(String mapperPackage) { - this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); - } + private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); /** * 在执行查询之前,检查并处理数据权限相关逻辑 @@ -64,7 +55,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } // 解析 sql 分配对应方法 @@ -92,7 +83,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); @@ -128,7 +119,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); } @@ -144,7 +135,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); } @@ -157,7 +148,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); } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java index 7eab05a3b..b9a90dc75 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java @@ -2,6 +2,7 @@ package org.dromara.common.oss.core; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; +import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.constant.Constants; import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.StringUtils; @@ -13,9 +14,7 @@ import org.dromara.common.oss.exception.OssException; import org.dromara.common.oss.properties.OssProperties; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.async.AsyncResponseTransformer; -import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; +import software.amazon.awssdk.core.async.*; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; @@ -29,9 +28,12 @@ import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener; import java.io.*; import java.net.URI; import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Optional; import java.util.function.Consumer; /** @@ -40,6 +42,7 @@ import java.util.function.Consumer; * * @author AprilWind */ +@Slf4j public class OssClient { /** @@ -237,30 +240,61 @@ public class OssClient { * @param key 文件在 Amazon S3 中的对象键 * @param out 输出流 * @param consumer 自定义处理逻辑 - * @return 输出流中写入的字节数(长度) * @throws OssException 如果下载失败,抛出自定义异常 */ public void download(String key, OutputStream out, Consumer consumer) { + try { + this.download(key, consumer).writeTo(out); + } catch (Exception e) { + throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); + } + } + + /** + * 下载文件从 Amazon S3 到 输出流 + * + * @param key 文件在 Amazon S3 中的对象键 + * @param contentLengthConsumer 文件大小消费者函数 + * @return 写出订阅器 + * @throws OssException 如果下载失败,抛出自定义异常 + */ + public WriteOutSubscriber download(String key, Consumer contentLengthConsumer) { try { // 构建下载请求 - DownloadRequest> downloadRequest = DownloadRequest.builder() + DownloadRequest> publisherDownloadRequest = DownloadRequest.builder() // 文件对象 .getObjectRequest(y -> y.bucket(properties.getBucketName()) .key(key) .build()) .addTransferListener(LoggingTransferListener.create()) - // 使用订阅转换器 - .responseTransformer(AsyncResponseTransformer.toBlockingInputStream()) + // 使用发布订阅转换器 + .responseTransformer(AsyncResponseTransformer.toPublisher()) .build(); + // 使用 S3TransferManager 下载文件 - Download> responseFuture = transferManager.download(downloadRequest); - // 输出到流中 - try (ResponseInputStream responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream - if (consumer != null) { - consumer.accept(responseStream.response().contentLength()); + Download> publisherDownload = transferManager.download(publisherDownloadRequest); + // 获取下载发布订阅转换器 + ResponsePublisher publisher = publisherDownload.completionFuture().join().result(); + // 执行文件大小消费者函数 + Optional.ofNullable(contentLengthConsumer) + .ifPresent(lengthConsumer -> lengthConsumer.accept(publisher.response().contentLength())); + + // 构建写出订阅器对象 + return out -> { + // 创建可写入的字节通道 + try(WritableByteChannel channel = Channels.newChannel(out)){ + // 订阅数据 + publisher.subscribe(byteBuffer -> { + while (byteBuffer.hasRemaining()) { + try { + channel.write(byteBuffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }).join(); } - responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread - } + }; } catch (Exception e) { throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java new file mode 100644 index 000000000..d3a9841a1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/WriteOutSubscriber.java @@ -0,0 +1,15 @@ +package org.dromara.common.oss.core; + +import java.io.IOException; + +/** + * 写出订阅器 + * + * @author 秋辞未寒 + */ +@FunctionalInterface +public interface WriteOutSubscriber { + + void writeTo(T out) throws IOException; + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java index 355cd2931..c433bffad 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/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 void setCacheObject(final String key, final T value, final Duration duration) { - RBatch batch = CLIENT.createBatch(); - RBucketAsync bucket = batch.getBucket(key); - bucket.setAsync(value); - bucket.expireAsync(duration); - batch.execute(); + RBucket bucket = CLIENT.getBucket(key); + bucket.set(value, duration); } /** diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java index 5258c8558..a3d0badc0 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/SequenceUtils.java @@ -1,5 +1,6 @@ package org.dromara.common.redis.utils; +import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DatePattern; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -119,7 +120,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)); } /** @@ -130,7 +131,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)); } /** diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java index cd4c5e4aa..14abf896a 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java @@ -74,7 +74,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { */ @Override public void delete(String key) { - RedisUtils.deleteObject(key); + if (RedisUtils.deleteObject(key)) { + CAFFEINE.invalidate(key); + } } /** @@ -151,7 +153,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { */ @Override public void deleteObject(String key) { - RedisUtils.deleteObject(key); + if (RedisUtils.deleteObject(key)) { + CAFFEINE.invalidate(key); + } } /** diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java index e57d1fbde..c9f8c8c2c 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java @@ -1,6 +1,7 @@ package org.dromara.common.satoken.core.service; import cn.dev33.satoken.stp.StpInterface; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.enums.UserType; @@ -39,8 +40,12 @@ public class SaPermissionImpl implements StpInterface { if (userType == UserType.APP_USER) { // 其他端 自行根据业务编写 } - // SYS_USER 默认返回权限 - return new ArrayList<>(loginUser.getMenuPermission()); + if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) { + // SYS_USER 默认返回权限 + return new ArrayList<>(loginUser.getMenuPermission()); + } else { + return new ArrayList<>(); + } } /** @@ -62,8 +67,12 @@ public class SaPermissionImpl implements StpInterface { 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() { diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java index 7a2b9fcf6..730bae29d 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java @@ -207,7 +207,8 @@ public class LoginHelper { */ public static boolean isLogin() { try { - return getLoginUser() != null; + StpUtil.checkLogin(); + return true; } catch (Exception e) { return false; } diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java index 7af5cee9e..087132ea3 100644 --- a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java @@ -1,5 +1,6 @@ package org.dromara.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())), /** * 密码 diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java index 3f7924d9b..151d84ce2 100644 --- a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java @@ -14,6 +14,9 @@ import org.dromara.common.social.gitea.AuthGiteaRequest; import org.dromara.common.social.maxkey.AuthMaxKeyRequest; import org.dromara.common.social.topiam.AuthTopIamRequest; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + /** * 认证授权工具类 * @@ -40,7 +43,7 @@ public class SocialUtils { AuthConfig.AuthConfigBuilder builder = AuthConfig.builder() .clientId(obj.getClientId()) .clientSecret(obj.getClientSecret()) - .redirectUri(obj.getRedirectUri()) + .redirectUri(URLEncoder.encode(obj.getRedirectUri(), StandardCharsets.UTF_8)) .scopes(obj.getScopes()); return switch (source.toLowerCase()) { case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE); diff --git a/ruoyi-common/ruoyi-common-web/pom.xml b/ruoyi-common/ruoyi-common-web/pom.xml index b250fa9d0..c687ad589 100644 --- a/ruoyi-common/ruoyi-common-web/pom.xml +++ b/ruoyi-common/ruoyi-common-web/pom.xml @@ -21,11 +21,6 @@ ruoyi-common-json - - org.dromara - ruoyi-common-redis - - org.springframework.boot diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java index 32d61df1c..155a8d071 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/FilterConfig.java @@ -7,6 +7,7 @@ import org.dromara.common.web.filter.XssFilter; 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 xssFilterRegistration() { - FilterRegistrationBean 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 someFilterRegistration() { - FilterRegistrationBean 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(); } } diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java index d0b73345a..d0b6ca3eb 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java @@ -1,8 +1,8 @@ package org.dromara.common.web.enums; import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.MathGenerator; import cn.hutool.captcha.generator.RandomGenerator; -import org.dromara.common.web.utils.UnsignedMathGenerator; import lombok.AllArgsConstructor; import lombok.Getter; @@ -18,7 +18,7 @@ public enum CaptchaType { /** * 数字 */ - MATH(UnsignedMathGenerator.class), + MATH(MathGenerator.class), /** * 字符 diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/utils/UnsignedMathGenerator.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/utils/UnsignedMathGenerator.java deleted file mode 100644 index a400cff9a..000000000 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/utils/UnsignedMathGenerator.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.dromara.common.web.utils; - -import cn.hutool.captcha.generator.CodeGenerator; -import cn.hutool.core.math.Calculator; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.RandomUtil; -import org.dromara.common.core.utils.StringUtils; - -import java.io.Serial; - -/** - * 无符号计算生成器 - * - * @author Lion Li - */ -public class UnsignedMathGenerator implements CodeGenerator { - - @Serial - private static final long serialVersionUID = -5514819971774091076L; - - private static final String OPERATORS = "+-*"; - - /** - * 参与计算数字最大长度 - */ - private final int numberLength; - - /** - * 构造 - */ - public UnsignedMathGenerator() { - this(2); - } - - /** - * 构造 - * - * @param numberLength 参与计算最大数字位数 - */ - public UnsignedMathGenerator(int numberLength) { - this.numberLength = numberLength; - } - - @Override - public String generate() { - final int limit = getLimit(); - int a = RandomUtil.randomInt(limit); - int b = RandomUtil.randomInt(limit); - String max = Integer.toString(Math.max(a,b)); - String min = Integer.toString(Math.min(a,b)); - max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE); - min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE); - - return max + RandomUtil.randomChar(OPERATORS) + min + '='; - } - - @Override - public boolean verify(String code, String userInputCode) { - int result; - try { - result = Integer.parseInt(userInputCode); - } catch (NumberFormatException e) { - // 用户输入非数字 - return false; - } - - final int calculateResult = (int) Calculator.conversion(code); - return result == calculateResult; - } - - /** - * 获取验证码长度 - * - * @return 验证码长度 - */ - public int getLength() { - return this.numberLength * 2 + 2; - } - - /** - * 根据长度获取参与计算数字最大值 - * - * @return 最大值 - */ - private int getLimit() { - return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength)); - } -} diff --git a/ruoyi-extend/ruoyi-monitor-admin/Dockerfile b/ruoyi-extend/ruoyi-monitor-admin/Dockerfile index f97cb9e86..f39ef9070 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/Dockerfile +++ b/ruoyi-extend/ruoyi-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 findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java index 0339ebb06..a7335616c 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java +++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/MonitorAdminApplication.java @@ -1,5 +1,6 @@ package org.dromara.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 Lion Li */ +@EnableAdminServer @SpringBootApplication public class MonitorAdminApplication { diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/AdminServerConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/AdminServerConfig.java deleted file mode 100644 index 53d248e39..000000000 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/AdminServerConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.dromara.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 Lion Li - */ -@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(); - } - - -} diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java index 4c8b3dfa6..41372f258 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java +++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/org/dromara/monitor/admin/config/SecurityConfig.java @@ -3,7 +3,6 @@ package org.dromara.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); - } - } diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml index 622c93d3c..1dd5a8308 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml +++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml @@ -20,6 +20,9 @@ spring: ui: title: RuoYi-Vue-Plus服务监控中心 context-path: /admin + # 忽略无用警告 + thymeleaf: + check-template-location: false --- # Actuator 监控端点的配置项 management: diff --git a/ruoyi-extend/ruoyi-snailjob-server/Dockerfile b/ruoyi-extend/ruoyi-snailjob-server/Dockerfile index ef85eb2c9..e10ca1834 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/Dockerfile +++ b/ruoyi-extend/ruoyi-snailjob-server/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 findepi/graalvm:java17-native LABEL maintainer="Lion Li" diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/common/register/ServerRegister.java b/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/common/register/ServerRegister.java new file mode 100644 index 000000000..2a8a47aa4 --- /dev/null +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/java/com/aizuda/snailjob/server/common/register/ServerRegister.java @@ -0,0 +1,146 @@ +package com.aizuda.snailjob.server.common.register; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.aizuda.snailjob.common.core.enums.NodeTypeEnum; +import com.aizuda.snailjob.common.core.util.JsonUtil; +import com.aizuda.snailjob.common.core.util.NetUtil; +import com.aizuda.snailjob.common.core.util.SnailJobVersion; +import com.aizuda.snailjob.common.core.util.StreamUtils; +import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup; +import com.aizuda.snailjob.server.common.config.SystemProperties; +import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter; +import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs; +import com.aizuda.snailjob.server.common.handler.InstanceManager; +import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.google.common.collect.Lists; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 服务端注册 + * + * @author opensnail + * @date 2023-06-07 + * @since 1.6.0 + */ +@Component(ServerRegister.BEAN_NAME) +@RequiredArgsConstructor +public class ServerRegister extends AbstractRegister { + public static final String BEAN_NAME = "serverRegister"; + private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node")); + public static final int DELAY_TIME = 30; + public static final String CURRENT_CID; + public static final String GROUP_NAME = "DEFAULT_SERVER"; + public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID"; + private final InstanceManager instanceManager; + private final SystemProperties systemProperties; + private final ServerProperties serverProperties; + + static { + CURRENT_CID = IdUtil.getSnowflakeNextIdStr(); + } + + @Override + public boolean supports(int type) { + return getNodeType().equals(type); + } + + @Override + protected void beforeProcessor(RegisterContext context) { + // 新增扩展参数 + ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs(); + serverNodeExtAttrs.setWebPort(serverProperties.getPort()); + serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion()); + + context.setGroupName(GROUP_NAME); + context.setHostId(CURRENT_CID); + String serverHost = systemProperties.getServerHost(); + if (StrUtil.isEmptyIfStr(serverHost)) { + serverHost = NetUtil.getLocalIpStr(); + } + context.setHostIp(serverHost); + context.setHostPort(systemProperties.getServerPort()); + context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY)); + context.setNamespaceId(NAMESPACE_ID); + context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs)); + } + + @Override + protected LocalDateTime getExpireAt() { + return LocalDateTime.now().plusSeconds(DELAY_TIME); + } + + @Override + protected boolean doRegister(RegisterContext context, ServerNode serverNode) { + refreshExpireAt(Lists.newArrayList(serverNode)); + return Boolean.TRUE; + } + + + @Override + protected void afterProcessor(final ServerNode serverNode) { + try { + // 同步当前POD消费的组的节点信息 + // netty的client只会注册到一个服务端,若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息 + ConcurrentMap/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName(); + if (CollUtil.isNotEmpty(allConsumerGroupName)) { + Set namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream); + if (CollUtil.isEmpty(namespaceIdSets)) { + return; + } + + List serverNodes = serverNodeMapper.selectList( + new LambdaQueryWrapper() + .eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType()) + .in(ServerNode::getNamespaceId, namespaceIdSets) + .in(ServerNode::getGroupName, allConsumerGroupName.keySet())); + for (final ServerNode node : serverNodes) { + // 刷新全量本地缓存 + instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node)); + // 刷新过期时间 + CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId()); + } + } + } catch (Exception e) { + SnailJobLog.LOCAL.error("Client refresh failed", e); + } + } + + @Override + protected Integer getNodeType() { + return NodeTypeEnum.SERVER.getType(); + } + + @Override + public void start() { + SnailJobLog.LOCAL.info("ServerRegister start"); + + serverRegisterNode.scheduleAtFixedRate(() -> { + try { + this.register(new RegisterContext()); + } catch (Exception e) { + SnailJobLog.LOCAL.error("Server-side registration failed", e); + } + }, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS); + + } + + @Override + public void close() { + SnailJobLog.LOCAL.info("ServerRegister close"); + } +} diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml index 62b4846ab..1e386db00 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml @@ -34,8 +34,6 @@ snail-job: summary-day: 7 # 配置负载均衡周期时间 load-balance-cycle-time: 10 - # 通知类型默认使用grpc(netty 已经下线) - rpc-type: grpc # 重试任务拉取的并行度 retry-max-pull-parallel: 2 diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml index 62b4846ab..1e386db00 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml @@ -34,8 +34,6 @@ snail-job: summary-day: 7 # 配置负载均衡周期时间 load-balance-cycle-time: 10 - # 通知类型默认使用grpc(netty 已经下线) - rpc-type: grpc # 重试任务拉取的并行度 retry-max-pull-parallel: 2 diff --git a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml index be588110c..ed733cb16 100644 --- a/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml +++ b/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/logback-plus.xml @@ -85,6 +85,7 @@ + diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java index 3fd124c78..64243b77c 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java @@ -1,5 +1,6 @@ package org.dromara.demo.controller; +import cn.dev33.satoken.annotation.SaIgnore; import cn.hutool.core.collection.CollUtil; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; @@ -14,6 +15,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -94,6 +96,16 @@ public class TestExcelController { exportExcelService.exportWithOptions(response); } + /** + * 自定义导出 + * + * @param response / + */ + @GetMapping("/customExport") + public void customExport(HttpServletResponse response) throws IOException { + exportExcelService.customExport(response); + } + /** * 多个sheet导出 */ diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java index 4dfa5effa..ad2392b97 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java @@ -2,6 +2,8 @@ package org.dromara.demo.service; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + /** * 导出下拉框Excel示例 * @@ -15,4 +17,11 @@ public interface IExportExcelService { * @param response / */ void exportWithOptions(HttpServletResponse response); + + /** + * 自定义导出 + * + * @param response / + */ + void customExport(HttpServletResponse response) throws IOException; } diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java index 69cf0a8af..2813cc287 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java @@ -2,17 +2,21 @@ package org.dromara.demo.service.impl; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.idev.excel.write.metadata.WriteSheet; import jakarta.servlet.http.HttpServletResponse; import lombok.Data; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.excel.core.DropDownOptions; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelWriterWrapper; import org.dromara.demo.domain.vo.ExportDemoVo; import org.dromara.demo.service.IExportExcelService; import org.springframework.stereotype.Service; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -233,4 +237,61 @@ public class ExportExcelServiceImpl implements IExportExcelService { this.name = name; } } + + + @Override + public void customExport(HttpServletResponse response) throws IOException { + String filename = ExcelUtil.encodingFilename("自定义导出"); + FileUtils.setAttachmentResponseHeader(response, filename); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); + + ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> { + // 创建表格数据,业务中一般通过数据库查询 + List excelDataList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + // 模拟数据库中的一条数据 + ExportDemoVo everyRowData = new ExportDemoVo(); + everyRowData.setNickName("用户-" + i); + everyRowData.setUserStatus(SystemConstants.NORMAL); + everyRowData.setGender("1"); + everyRowData.setPhoneNumber(String.format("175%08d", i)); + everyRowData.setEmail(String.format("175%08d", i) + "@163.com"); + everyRowData.setProvinceId(i); + everyRowData.setCityId(i); + everyRowData.setAreaId(i); + excelDataList.add(everyRowData); + } + + // 创建表格 + WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("自定义导出demo") + // 合并单元格 + // .registerWriteHandler(new CellMergeStrategy(excelDataList, true)) + .build(); + + + wrapper.write(excelDataList, sheet); + + List excelDataList2 = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + int index = 1000 + i; + // 模拟数据库中的一条数据 + ExportDemoVo everyRowData = new ExportDemoVo(); + everyRowData.setNickName("用户-" + index); + everyRowData.setUserStatus(SystemConstants.NORMAL); + everyRowData.setGender("1"); + everyRowData.setPhoneNumber(String.format("175%08d", index)); + everyRowData.setEmail(String.format("175%08d", index) + "@163.com"); + everyRowData.setProvinceId(index); + everyRowData.setCityId(index); + everyRowData.setAreaId(index); + excelDataList2.add(everyRowData); + } + + wrapper.write(excelDataList2, sheet); + + // 或者在同一个excel中创建多个表格 + // WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("自定义导出demo2").build(); + // wrapper.write(excelDataList2, sheet2); + }); + } } diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java index 6e111e3a8..44c5979ee 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/util/VelocityUtils.java @@ -3,6 +3,7 @@ package org.dromara.generator.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Dict; +import org.dromara.common.mybatis.enums.DataBaseType; import org.dromara.generator.constant.GenConstants; import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.StringUtils; @@ -118,11 +119,12 @@ public class VelocityUtils { templates.add("vm/java/serviceImpl.java.vm"); templates.add("vm/java/controller.java.vm"); templates.add("vm/xml/mapper.xml.vm"); - if (DataBaseHelper.isOracle()) { + DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(); + if (dataBaseType.isOracle()) { templates.add("vm/sql/oracle/sql.vm"); - } else if (DataBaseHelper.isPostgerSql()) { + } else if (dataBaseType.isPostgreSql()) { templates.add("vm/sql/postgres/sql.vm"); - } else if (DataBaseHelper.isSqlServer()) { + } else if (dataBaseType.isSqlServer()) { templates.add("vm/sql/sqlserver/sql.vm"); } else { templates.add("vm/sql/sql.vm"); diff --git a/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm b/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm index 35a468e80..eeef5f556 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm +++ b/ruoyi-modules/ruoyi-generator/src/main/resources/vm/ts/types.ts.vm @@ -54,11 +54,8 @@ export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{ #end #end #end - /** - * 日期范围参数 - */ - params?: any; + /** + * 日期范围参数 + */ + params?: any; } - - - diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java index b8ad8cc39..d5c3ea734 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/AlipayBillTask.java @@ -4,8 +4,8 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.dromara.common.json.utils.JsonUtils; import org.dromara.job.entity.BillDto; import org.springframework.stereotype.Component; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java index bff15f97e..47d730584 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/SummaryBillTask.java @@ -3,8 +3,8 @@ package org.dromara.job.snailjob; import cn.hutool.core.util.StrUtil; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.dromara.common.json.utils.JsonUtils; import org.dromara.job.entity.BillDto; import org.springframework.stereotype.Component; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java index e5339f5b4..aa3d99e4c 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestAnnoJobExecutor.java @@ -2,9 +2,9 @@ package org.dromara.job.snailjob; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.core.util.JsonUtil; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.springframework.stereotype.Component; /** diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java index d77e72e6c..9cb830936 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestBroadcastJob.java @@ -3,8 +3,8 @@ package org.dromara.job.snailjob; import cn.hutool.core.util.RandomUtil; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java index 6f7c21fb6..93da0a903 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestClassJobExecutor.java @@ -2,7 +2,7 @@ package org.dromara.job.snailjob; import com.aizuda.snailjob.client.job.core.dto.JobArgs; import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor; -import com.aizuda.snailjob.client.model.ExecuteResult; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.springframework.stereotype.Component; /** diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java index 6589ed1c5..f58d7724a 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapJobAnnotation.java @@ -6,8 +6,8 @@ import com.aizuda.snailjob.client.job.core.MapHandler; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.annotation.MapExecutor; import com.aizuda.snailjob.client.job.core.dto.MapArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.springframework.stereotype.Component; import java.util.List; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java index 4ae2fa80a..f926016f9 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestMapReduceAnnotation1.java @@ -8,8 +8,8 @@ import com.aizuda.snailjob.client.job.core.annotation.MapExecutor; import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor; import com.aizuda.snailjob.client.job.core.dto.MapArgs; import com.aizuda.snailjob.client.job.core.dto.ReduceArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.springframework.stereotype.Component; import java.util.List; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java index 07a1bc566..060fcd4d6 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/TestStaticShardingJob.java @@ -1,9 +1,10 @@ package org.dromara.job.snailjob; +import cn.hutool.core.convert.Convert; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.springframework.stereotype.Component; /** @@ -17,7 +18,7 @@ import org.springframework.stereotype.Component; public class TestStaticShardingJob { public ExecuteResult jobExecute(JobArgs jobArgs) { - String jobParams = String.valueOf(jobArgs.getJobParams()); + String jobParams = Convert.toStr(jobArgs.getJobParams()); SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams); // 获得jobArgs 中传入的开始id和结束id String[] split = jobParams.split(","); diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java index d8caf1af0..e19a48ea9 100644 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java +++ b/ruoyi-modules/ruoyi-job/src/main/java/org/dromara/job/snailjob/WechatBillTask.java @@ -4,8 +4,8 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.model.ExecuteResult; import com.aizuda.snailjob.common.log.SnailJobLog; +import com.aizuda.snailjob.model.dto.ExecuteResult; import org.dromara.common.json.utils.JsonUtils; import org.dromara.job.entity.BillDto; import org.springframework.stereotype.Component; diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java index 5767c8df0..4a82536f1 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/CacheController.java @@ -53,6 +53,13 @@ public class CacheController { } } + /** + * 缓存监控列表信息 + * + * @param info 信息 + * @param dbSize 数据库 + * @param commandStats 命令统计 + */ public record CacheListInfoVo(Properties info, Long dbSize, List> commandStats) {} } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java index 86c08996f..d1b5b9bd5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysMenuController.java @@ -179,6 +179,12 @@ public class SysMenuController extends BaseController { return toAjax(menuService.deleteMenuById(menuId)); } + /** + * 角色菜单列表树信息 + * + * @param checkedKeys 选中菜单列表 + * @param menus 菜单下拉树结构列表 + */ public record MenuTreeSelectVo(List checkedKeys, List> menus) { } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java index 8903a7406..9621cb239 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java @@ -17,6 +17,7 @@ import org.dromara.common.web.core.BaseController; import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserPasswordBo; import org.dromara.system.domain.bo.SysUserProfileBo; +import org.dromara.system.domain.vo.ProfileUserVo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.domain.vo.SysUserVo; import org.dromara.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); } @@ -126,8 +129,20 @@ public class SysProfileController extends BaseController { return R.fail("上传图片异常,请联系管理员"); } + /** + * 用户头像信息 + * + * @param imgUrl 头像地址 + */ public record AvatarVo(String imgUrl) {} - public record ProfileVo(SysUserVo user, String roleGroup, String postGroup) {} + /** + * 用户个人信息 + * + * @param user 用户信息 + * @param roleGroup 用户所属角色组 + * @param postGroup 用户所属岗位组 + */ + public record ProfileVo(ProfileUserVo user, String roleGroup, String postGroup) {} } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java index 8609e1950..755a9ecab 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java @@ -235,6 +235,12 @@ public class SysRoleController extends BaseController { return R.ok(selectVo); } + /** + * 角色部门列表树信息 + * + * @param checkedKeys 选中部门列表 + * @param depts 下拉树结构列表 + */ public record DeptTreeSelectVo(List checkedKeys, List> depts) {} } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java index eecbc9f53..b27a40694 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantPackageBo.java @@ -37,7 +37,7 @@ public class SysTenantPackageBo extends BaseEntity { /** * 关联菜单id */ - @AutoMapping(target = "menuIds", expression = "java(org.dromara.common.core.utils.StringUtils.join(source.getMenuIds(), \",\"))") + @AutoMapping(target = "menuIds", expression = "java(org.dromara.common.core.utils.StringUtils.joinComma(source.getMenuIds()))") private Long[] menuIds; /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java index b2c11485c..840ddaf2e 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/MetaVo.java @@ -32,6 +32,11 @@ public class MetaVo { */ private String link; + /** + * 激活菜单 + */ + private String activeMenu; + public MetaVo(String title, String icon) { this.title = title; this.icon = icon; @@ -58,4 +63,16 @@ public class MetaVo { } } + public MetaVo(String title, String icon, Boolean noCache, String link, String activeMenu) { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) { + this.link = link; + } + if (StringUtils.startWithAnyIgnoreCase(activeMenu, "/")) { + this.activeMenu = activeMenu; + } + } + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java new file mode 100644 index 000000000..70fc5d3bc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ProfileUserVo.java @@ -0,0 +1,90 @@ +package org.dromara.system.domain.vo; + +import lombok.Data; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.constant.TransConstant; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 用户信息视图对象 sys_user + * + * @author Lion Li + */ +@Data +public class ProfileUserVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户账号 + */ + 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.DEPT_ID_TO_NAME, mapper = "deptId") + private String deptName; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java index 196eefb3c..1f93c5030 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysDeptMapper.java @@ -30,7 +30,9 @@ public interface SysDeptMapper extends BaseMapperPlus { */ default String buildDeptByRoleSql(Long roleId) { return """ - select dept_id from sys_role_dept where role_id = %d + select srd.dept_id from sys_role_dept srd + left join sys_role sr on sr.role_id = srd.role_id + where srd.role_id = %d and sr.status = 0 """.formatted(roleId); } @@ -47,7 +49,9 @@ public interface SysDeptMapper extends BaseMapperPlus { default String buildParentDeptByRoleSql(Long roleId) { return """ select parent_id from sys_dept where dept_id in ( - select dept_id from sys_role_dept where role_id = %d + select srd.dept_id from sys_role_dept srd + left join sys_role sr on sr.role_id = srd.role_id + where srd.role_id = %d and sr.status = 0 ) """.formatted(roleId); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java index 10bc015f2..b0903057c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java @@ -32,7 +32,9 @@ public interface SysMenuMapper extends BaseMapperPlus { default String buildMenuByUserSql(Long userId) { return """ select menu_id from sys_role_menu where role_id in ( - select role_id from sys_user_role where user_id = %d + select sur.role_id from sys_user_role sur + left join sys_role sr on sr.role_id = sur.role_id + where sur.user_id = %d and sr.status = 0 ) """.formatted(userId); } @@ -50,7 +52,9 @@ public interface SysMenuMapper extends BaseMapperPlus { */ default String buildMenuByRoleSql(Long roleId) { return """ - select menu_id from sys_role_menu where role_id = %d + select srm.menu_id from sys_role_menu srm + left join sys_role sr on sr.role_id = srm.role_id + where srm.role_id = %d and sr.status = 0 """.formatted(roleId); } @@ -68,7 +72,9 @@ public interface SysMenuMapper extends BaseMapperPlus { default String buildParentMenuByRoleSql(Long roleId) { return """ select parent_id from sys_menu where menu_id in ( - select menu_id from sys_role_menu where role_id = %d + select srm.menu_id from sys_role_menu srm + left join sys_role sr on sr.role_id = srm.role_id + where srm.role_id = %d and sr.status = 0 ) """.formatted(roleId); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java index 2b995beec..112dfb046 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java @@ -112,7 +112,7 @@ public class SysClientServiceImpl implements ISysClientService { @Override public Boolean updateByBo(SysClientBo bo) { SysClient update = MapstructUtils.convert(bo, SysClient.class); - update.setGrantType(String.join(",", bo.getGrantTypeList())); + update.setGrantType(StringUtils.joinComma(bo.getGrantTypeList())); return baseMapper.updateById(update) > 0; } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java index c9b3e478c..d96fa1246 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java @@ -172,7 +172,7 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService { List list = baseMapper.selectByIds(configIds); list.forEach(config -> { if (StringUtils.equals(SystemConstants.YES, config.getConfigType())) { - throw new ServiceException(String.format("内置参数【%s】不能删除", config.getConfigKey())); + throw new ServiceException("内置参数【{}】不能删除", config.getConfigKey()); } CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey()); }); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java index 7e4e24210..4e6db154c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java @@ -192,7 +192,7 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService { list.add(vo.getDeptName()); } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java index 0ef908d30..6d4d9fea9 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java @@ -143,7 +143,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService boolean assigned = dictDataMapper.exists(new LambdaQueryWrapper() .eq(SysDictData::getDictType, x.getDictType())); if (assigned) { - throw new ServiceException(String.format("%1$s已分配,不能删除", x.getDictName())); + throw new ServiceException("{}已分配,不能删除", x.getDictName()); } }); baseMapper.deleteByIds(dictIds); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java index efe9029f8..19d6bfb69 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java @@ -185,7 +185,7 @@ public class SysMenuServiceImpl implements ISysMenuService { router.setPath(menu.getRouterPath()); router.setComponent(menu.getComponentInfo()); router.setQuery(menu.getQueryParam()); - router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath(), menu.getRemark())); List cMenus = menu.getChildren(); if (CollUtil.isNotEmpty(cMenus) && SystemConstants.TYPE_DIR.equals(menu.getMenuType())) { router.setAlwaysShow(true); @@ -199,7 +199,7 @@ public class SysMenuServiceImpl implements ISysMenuService { children.setPath(menu.getPath()); children.setComponent(menu.getComponent()); children.setName(frameName); - children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath(), menu.getRemark())); children.setQuery(menu.getQueryParam()); childrenList.add(children); router.setChildren(childrenList); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java index a67b04ea9..539c1fc62 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java @@ -120,7 +120,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService { private void validEntityBeforeSave(SysOssConfig entity) { if (StringUtils.isNotEmpty(entity.getConfigKey()) && !checkConfigKeyUnique(entity)) { - throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!"); + throw new ServiceException("操作配置'{}'失败, 配置key已存在!", entity.getConfigKey()); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java index e60b21ea7..33341a91d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java @@ -116,7 +116,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { } } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } @Override diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java index c859a8e6a..56a0c4bc1 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java @@ -225,7 +225,7 @@ public class SysPostServiceImpl implements ISysPostService, PostService { List 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); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java index f901748de..7b31f7b1f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTaskAssigneeServiceImpl.java @@ -59,7 +59,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService { TableDataInfo page = roleService.selectPageRoleList(bo, pageQuery); // 使用封装的字段映射方法进行转换 List handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(), - item -> String.valueOf(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,7 +83,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService { TableDataInfo page = postService.selectPagePostList(bo, pageQuery); // 使用封装的字段映射方法进行转换 List handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(), - p -> String.valueOf(p.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, SysPostVo::getDeptId, SysPostVo::getCreateTime); + item -> Convert.toStr(item.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, item -> Convert.toStr(item.getDeptId()), SysPostVo::getCreateTime); return new TaskAssigneeDTO(page.getTotal(), handlers); } @@ -107,7 +107,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService { TableDataInfo page = deptService.selectPageDeptList(bo, pageQuery); // 使用封装的字段映射方法进行转换 List handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(), - d -> String.valueOf(d.getDeptId()), SysDeptVo::getDeptCategory, SysDeptVo::getDeptName, SysDeptVo::getParentId, SysDeptVo::getCreateTime); + item -> Convert.toStr(item.getDeptId()), SysDeptVo::getDeptCategory, SysDeptVo::getDeptName, item -> Convert.toStr(item.getParentId()), SysDeptVo::getCreateTime); return new TaskAssigneeDTO(page.getTotal(), handlers); } @@ -131,7 +131,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService { TableDataInfo page = userService.selectPageUserList(bo, pageQuery); // 使用封装的字段映射方法进行转换 List handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(), - u -> String.valueOf(u.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, SysUserVo::getDeptId, SysUserVo::getCreateTime); + item -> Convert.toStr(item.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, item -> Convert.toStr(item.getDeptId()), SysUserVo::getCreateTime); return new TaskAssigneeDTO(page.getTotal(), handlers); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java index 8d69e96ea..baefdf792 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java @@ -88,11 +88,7 @@ public class SysTenantPackageServiceImpl implements ISysTenantPackageService { SysTenantPackage add = MapstructUtils.convert(bo, SysTenantPackage.class); // 保存菜单id List menuIds = Arrays.asList(bo.getMenuIds()); - if (CollUtil.isNotEmpty(menuIds)) { - add.setMenuIds(StringUtils.join(menuIds, ", ")); - } else { - add.setMenuIds(""); - } + add.setMenuIds(CollUtil.isNotEmpty(menuIds) ? StringUtils.joinComma(menuIds) : ""); boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setPackageId(add.getPackageId()); @@ -109,11 +105,7 @@ public class SysTenantPackageServiceImpl implements ISysTenantPackageService { SysTenantPackage update = MapstructUtils.convert(bo, SysTenantPackage.class); // 保存菜单id List menuIds = Arrays.asList(bo.getMenuIds()); - if (CollUtil.isNotEmpty(menuIds)) { - update.setMenuIds(StringUtils.join(menuIds, ", ")); - } else { - update.setMenuIds(""); - } + update.setMenuIds(CollUtil.isNotEmpty(menuIds) ? StringUtils.joinComma(menuIds) : ""); return baseMapper.updateById(update) > 0; } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index 475c46793..888e092bd 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -354,7 +354,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; } @@ -622,7 +622,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { list.add(nickname); } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java index 0bc027f6f..cacb2a257 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java @@ -83,4 +83,13 @@ public interface FlowConstant { */ String WF_TASK_STATUS = "wf_task_status"; + /** + * 自动通过 + */ + String AUTO_PASS = "autoPass"; + + /** + * 业务编码 + */ + String BUSINESS_CODE = "businessCode"; } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java new file mode 100644 index 000000000..a74af3b2d --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/CopySettingEnum.java @@ -0,0 +1,20 @@ +package org.dromara.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 抄送设置枚举 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum CopySettingEnum implements NodeExtEnum { + ; + private final String label; + private final String value; + private final boolean selected; + +} + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java index c604f97e7..fff268837 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskAssigneeEnum.java @@ -3,6 +3,7 @@ package org.dromara.workflow.common.enums; import lombok.AllArgsConstructor; import lombok.Getter; import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; import java.util.Arrays; import java.util.List; @@ -110,5 +111,30 @@ public enum TaskAssigneeEnum { .map(TaskAssigneeEnum::getCode) .collect(Collectors.toList()); } + + /** + * 判断当前办理人类型是否需要调用部门服务(deptService) + * + * @return 如果类型是 USER、DEPT 或 POST,则返回 true;否则返回 false + */ + public boolean needsDeptService() { + return this == USER || this == DEPT || 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, "#"); + } + } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java new file mode 100644 index 000000000..dbd54ed54 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/VariablesEnum.java @@ -0,0 +1,20 @@ +package org.dromara.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 变量枚举 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum VariablesEnum implements NodeExtEnum { + ; + private final String label; + private final String value; + private final boolean selected; + +} + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java index ea2168d56..c798dc5c2 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java @@ -89,6 +89,16 @@ public class FlwInstanceController extends BaseController { return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds)); } + /** + * 按照实例id删除已完成得流程实例 + * + * @param instanceIds 实例id + */ + @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}") + public R deleteHisByInstanceIds(@PathVariable List instanceIds) { + return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds)); + } + /** * 撤销流程 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java index a3af88b9a..4370ff82b 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java @@ -191,12 +191,12 @@ public class FlwTaskController extends BaseController { /** * 获取可驳回的前置节点 * - * @param definitionId 流程定义id + * @param taskId 任务id * @param nowNodeCode 当前节点 */ - @GetMapping("/getBackTaskNode/{definitionId}/{nowNodeCode}") - public R> getBackTaskNode(@PathVariable Long definitionId, @PathVariable String nowNodeCode) { - return R.ok(flwTaskService.getBackTaskNode(definitionId, nowNodeCode)); + @GetMapping("/getBackTaskNode/{taskId}/{nowNodeCode}") + public R> getBackTaskNode(@PathVariable Long taskId, @PathVariable String nowNodeCode) { + return R.ok(flwTaskService.getBackTaskNode(taskId, nowNodeCode)); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java new file mode 100644 index 000000000..932b0380e --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowInstanceBizExt.java @@ -0,0 +1,59 @@ +package org.dromara.workflow.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 流程实例业务扩展对象 flow_instance_biz_ext + * + * @author may + * @date 2025-08-05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("flow_instance_biz_ext") +public class FlowInstanceBizExt extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程实例ID + */ + private Long instanceId; + + /** + * 业务ID + */ + private String businessId; + + /** + * 业务编码 + */ + private String businessCode; + + /** + * 业务标题 + */ + private String businessTitle; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java index 7d42a9b5f..b54873ca6 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/TestLeave.java @@ -29,6 +29,11 @@ public class TestLeave extends BaseEntity { @TableId(value = "id") private Long id; + /** + * 申请编号 + */ + private String applyCode; + /** * 请假类型 */ diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java index 360fc3b81..e24629352 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java @@ -53,6 +53,11 @@ public class CompleteTaskBo implements Serializable { */ private String notice; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量 */ diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java index ea21a81e7..b31f4fa14 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/StartProcessBo.java @@ -1,9 +1,11 @@ package org.dromara.workflow.domain.bo; +import cn.hutool.core.util.ObjectUtil; import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.dromara.common.core.validate.AddGroup; +import org.dromara.workflow.domain.FlowInstanceBizExt; import java.io.Serial; import java.io.Serializable; @@ -34,11 +36,21 @@ public class StartProcessBo implements Serializable { @NotBlank(message = "流程定义编码不能为空", groups = {AddGroup.class}) private String flowCode; + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + /** * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} */ private Map variables; + /** + * 流程业务扩展信息 + */ + private FlowInstanceBizExt bizExt; + public Map getVariables() { if (variables == null) { return new HashMap<>(16); @@ -46,4 +58,11 @@ public class StartProcessBo implements Serializable { variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); return variables; } + + public FlowInstanceBizExt getBizExt() { + if (ObjectUtil.isNull(bizExt)) { + bizExt = new FlowInstanceBizExt(); + } + return bizExt; + } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java index b040dbe98..2463569bd 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java @@ -36,6 +36,11 @@ public class TestLeaveBo extends BaseEntity { */ private String flowCode; + /** + * 申请编号 + */ + private String applyCode; + /** * 请假类型 */ diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java new file mode 100644 index 000000000..67ef9e2c3 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCopyVo.java @@ -0,0 +1,36 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.constant.TransConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 抄送对象 + * + * @author AprilWind + */ +@Data +public class FlowCopyVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户名称 + */ + @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId") + private String userName; + + public FlowCopyVo(Long userId) { + this.userId = userId; + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java index 8776a76b8..ee0fd6330 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowHisTaskVo.java @@ -203,6 +203,18 @@ public class FlowHisTaskVo implements Serializable { */ private String runDuration; + //业务扩展信息开始 + /** + * 业务编码 + */ + private String businessCode; + + /** + * 业务标题 + */ + private String businessTitle; + //业务扩展信息结束 + /** * 设置创建时间并计算任务运行时长 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java index 75543f419..fce6725ff 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowInstanceVo.java @@ -134,4 +134,16 @@ public class FlowInstanceVo { @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category") private String categoryName; + //业务扩展信息开始 + /** + * 业务编码 + */ + private String businessCode; + + /** + * 业务标题 + */ + private String businessTitle; + //业务扩展信息结束 + } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java index 28753be28..25a7d85b1 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java @@ -11,6 +11,7 @@ import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; import java.util.List; +import java.util.Map; /** * 任务视图 @@ -185,4 +186,30 @@ public class FlowTaskVo implements Serializable { */ private List buttonList; + /** + * 抄送对象 ID 集合 + *

+ * 根据扩展属性中 CopySettingEnum 类型的数据生成,存储需要抄送的对象 ID + */ + private List copyList; + + /** + * 自定义参数 Map + *

+ * 根据扩展属性中 VariablesEnum 类型的数据生成,存储 key=value 格式的自定义参数 + */ + private Map varList; + + //业务扩展信息开始 + /** + * 业务编码 + */ + private String businessCode; + + /** + * 业务标题 + */ + private String businessTitle; + //业务扩展信息结束 + } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java new file mode 100644 index 000000000..5fb3380b4 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java @@ -0,0 +1,45 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Node 扩展属性解析结果 VO + *

+ * 用于封装从扩展属性 JSON 中解析出的各类信息,包括按钮权限、抄送对象和自定义参数。 + * + * @author AprilWind + */ +@Data +public class NodeExtVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 按钮权限列表 + *

+ * 根据扩展属性中 ButtonPermissionEnum 类型的数据生成,每个元素表示一个按钮及其是否勾选。 + */ + private List buttonPermissions; + + /** + * 抄送对象 ID 集合 + *

+ * 根据扩展属性中 CopySettingEnum 类型的数据生成,存储需要抄送的对象 ID + */ + private Set copySettings; + + /** + * 自定义参数 Map + *

+ * 根据扩展属性中 VariablesEnum 类型的数据生成,存储 key=value 格式的自定义参数 + */ + private Map variables; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java index 4c9b4e936..2f34158b0 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java @@ -32,6 +32,12 @@ public class TestLeaveVo implements Serializable { @ExcelProperty(value = "主键") private Long id; + /** + * 申请编号 + */ + @ExcelProperty(value = "申请编号") + private String applyCode; + /** * 请假类型 */ diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java index 43af0105e..e908a2085 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java @@ -1,6 +1,7 @@ package org.dromara.workflow.handler; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.dto.UserDTO; @@ -59,6 +60,6 @@ public class WorkflowPermissionHandler implements PermissionHandler { } String storageIds = CollUtil.join(permissions, StringUtils.SEPARATOR); List users = flwTaskAssigneeService.fetchUsersByStorageIds(storageIds); - return StreamUtils.toList(users, userDTO -> String.valueOf(userDTO.getUserId())); + return StreamUtils.toList(users, userDTO -> Convert.toStr(userDTO.getUserId())); } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java index 88a4dbf5f..2390bb251 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java @@ -1,33 +1,39 @@ package org.dromara.workflow.listener; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.enums.BusinessStatusEnum; +import org.dromara.common.core.service.UserService; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; +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; 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.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.common.enums.TaskStatusEnum; import org.dromara.workflow.domain.bo.FlowCopyBo; +import org.dromara.workflow.domain.vo.NodeExtVo; import org.dromara.workflow.handler.FlowProcessEventHandler; import org.dromara.workflow.service.IFlwCommonService; import org.dromara.workflow.service.IFlwInstanceService; +import org.dromara.workflow.service.IFlwNodeExtService; import org.dromara.workflow.service.IFlwTaskService; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * 全局任务办理监听 @@ -41,10 +47,11 @@ import java.util.Map; public class WorkflowGlobalListener implements GlobalListener { private final IFlwTaskService flwTaskService; - private final IFlwInstanceService instanceService; + private final IFlwInstanceService flwInstanceService; private final FlowProcessEventHandler flowProcessEventHandler; private final IFlwCommonService flwCommonService; - private final InsService insService; + private final IFlwNodeExtService nodeExtService; + private final UserService userService; /** * 创建监听器,任务创建时执行 @@ -63,6 +70,25 @@ public class WorkflowGlobalListener implements GlobalListener { */ @Override public void start(ListenerVariable listenerVariable) { + String ext = listenerVariable.getNode().getExt(); + if (StringUtils.isNotBlank(ext)) { + NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext); + Map variable = listenerVariable.getVariable(); + Set copyList = nodeExt.getCopySettings(); + if (CollUtil.isNotEmpty(copyList)) { + List list = StreamUtils.toList(copyList, x -> { + FlowCopyBo bo = new FlowCopyBo(); + Long id = Convert.toLong(x); + bo.setUserId(id); + bo.setUserName(userService.selectUserNameById(id)); + return bo; + }); + variable.put(FlowConstant.FLOW_COPY_LIST, list); + } + if (CollUtil.isNotEmpty(nodeExt.getVariables())) { + variable.putAll(nodeExt.getVariables()); + } + } } /** @@ -125,12 +151,15 @@ public class WorkflowGlobalListener implements GlobalListener { if (StringUtils.isNotBlank(status)) { flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false); } - 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 (!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()); + FlowEngine.insService().updateById(instance); + } } } //发布任务事件 @@ -158,12 +187,9 @@ public class WorkflowGlobalListener implements GlobalListener { if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) { List messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {}); String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE); - // 消息通知 - if (CollUtil.isNotEmpty(messageType)) { - flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); - } + flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); } - insService.removeVariables(instance.getId(), + FlowEngine.insService().removeVariables(instance.getId(), FlowConstant.FLOW_COPY_LIST, FlowConstant.MESSAGE_TYPE, FlowConstant.MESSAGE_NOTICE, @@ -187,7 +213,7 @@ public class WorkflowGlobalListener implements GlobalListener { if (flwTaskService.isTaskEnd(instanceId)) { String status = BusinessStatusEnum.FINISH.getStatus(); // 更新流程状态为已完成 - instanceService.updateStatus(instanceId, status); + flwInstanceService.updateStatus(instanceId, status); log.info("流程已结束,状态更新为: {}", status); return status; } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java new file mode 100644 index 000000000..e11613cf4 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java @@ -0,0 +1,61 @@ +package org.dromara.workflow.mapper; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.workflow.domain.FlowInstanceBizExt; + +import java.util.List; + +/** + * 流程实例业务扩展Mapper接口 + * + * @author may + * @date 2025-08-05 + */ +public interface FlwInstanceBizExtMapper extends BaseMapperPlus { + + /** + * 根据 instanceId 保存或更新流程实例业务扩展 + * + * @param entity 流程实例业务扩展实体 + * @return 操作是否成功 + */ + default int saveOrUpdateByInstanceId(FlowInstanceBizExt entity) { + // 查询是否存在 + FlowInstanceBizExt exist = this.selectOne(new LambdaQueryWrapper() + .eq(FlowInstanceBizExt::getInstanceId, entity.getInstanceId())); + + if (ObjectUtil.isNotNull(exist)) { + // 存在就带上主键更新 + entity.setId(exist.getId()); + return updateById(entity); + } else { + // 不存在就插入 + return insert(entity); + } + } + + /** + * 按照流程实例ID删除单个流程实例业务扩展 + * + * @param instanceId 流程实例ID + * @return 删除的行数 + */ + default int deleteByInstId(Long instanceId) { + return this.delete(new LambdaQueryWrapper() + .eq(FlowInstanceBizExt::getInstanceId, instanceId)); + } + + /** + * 按照流程实例ID批量删除流程实例业务扩展 + * + * @param instanceIds 流程实例ID列表 + * @return 删除的行数 + */ + default int deleteByInstIds(List instanceIds) { + return this.delete(new LambdaQueryWrapper() + .in(FlowInstanceBizExt::getInstanceId, instanceIds)); + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java index 09fed0d63..54ffe977e 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java @@ -85,6 +85,14 @@ public interface IFlwInstanceService { */ boolean deleteByInstanceIds(List instanceIds); + /** + * 按照实例id删除已完成得流程实例 + * + * @param instanceIds 删除的实例id + * @return 删除结果 + */ + boolean deleteHisByInstanceIds(List instanceIds); + /** * 撤销流程 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java index 959516535..2ccc6a73e 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java @@ -1,8 +1,6 @@ package org.dromara.workflow.service; -import org.dromara.workflow.domain.vo.ButtonPermissionVo; - -import java.util.List; +import org.dromara.workflow.domain.vo.NodeExtVo; /** * 流程节点扩展属性 服务层 @@ -12,11 +10,23 @@ import java.util.List; public interface IFlwNodeExtService { /** - * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选 + * 解析扩展属性 JSON 并构建 Node 扩展属性对象 + *

+ * 根据传入的 JSON 字符串,将扩展属性分为三类: + * 1. ButtonPermissionEnum:解析为按钮权限列表,标记每个按钮是否勾选 + * 2. CopySettingEnum:解析为抄送对象 ID 集合 + * 3. VariablesEnum:解析为自定义参数 Map + * + *

示例 JSON: + * [ + * {"code": "ButtonPermissionEnum", "value": "back,termination"}, + * {"code": "CopySettingEnum", "value": "1"}, + * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"} + * ] * * @param ext 扩展属性 JSON 字符串 - * @return 按钮权限 VO 列表 + * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map */ - List buildButtonPermissionsFromExt(String ext); + NodeExtVo parseNodeExt(String ext); } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java index 67279f340..f9dc45e1d 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java @@ -111,11 +111,11 @@ public interface IFlwTaskService { /** * 获取可驳回的前置节点 * - * @param definitionId 流程定义id + * @param taskId 任务id * @param nowNodeCode 当前节点 * @return 结果 */ - List getBackTaskNode(Long definitionId, String nowNodeCode); + List getBackTaskNode(Long taskId, String nowNodeCode); /** * 终止任务 @@ -165,6 +165,14 @@ public interface IFlwTaskService { */ List selectByInstId(Long instanceId); + /** + * 按照实例id查询任务 + * + * @param instanceIds 列表 + * @return 结果 + */ + List selectByInstIds(List instanceIds); + /** * 判断流程是否已结束(即该流程实例下是否还有未完成的任务) * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java index 26d91a5b4..1e2f6c268 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java @@ -1,6 +1,7 @@ package org.dromara.workflow.service.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -8,13 +9,11 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.utils.MapstructUtils; -import org.dromara.common.core.utils.ObjectUtils; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.core.utils.TreeBuildUtils; +import org.dromara.common.core.utils.*; import org.dromara.common.mybatis.helper.DataBaseHelper; import org.dromara.warm.flow.core.service.DefService; import org.dromara.warm.flow.orm.entity.FlowDefinition; +import org.dromara.warm.flow.ui.service.CategoryService; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.domain.FlowCategory; @@ -38,7 +37,7 @@ import java.util.List; @ConditionalOnEnable @RequiredArgsConstructor @Service -public class FlwCategoryServiceImpl implements IFlwCategoryService { +public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryService { private final DefService defService; private final FlwCategoryMapper baseMapper; @@ -97,16 +96,31 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { } return TreeBuildUtils.buildMultiRoot( categoryList, - node -> String.valueOf(node.getCategoryId()), - node -> String.valueOf(node.getParentId()), + node -> Convert.toStr(node.getCategoryId()), + node -> Convert.toStr(node.getParentId()), (node, treeNode) -> treeNode - .setId(String.valueOf(node.getCategoryId())) - .setParentId(String.valueOf(node.getParentId())) + .setId(Convert.toStr(node.getCategoryId())) + .setParentId(Convert.toStr(node.getParentId())) .setName(node.getCategoryName()) .setWeight(node.getOrderNum()) ); } + /** + * 工作流查询分类 + * + * @return 分类树结构列表 + */ + @Override + public List queryCategory() { + List list = this.queryList(new FlowCategoryBo()); + return StreamUtils.toList(list, category -> new org.dromara.warm.flow.core.dto.Tree() + .setId(Convert.toStr(category.getCategoryId())) + .setName(category.getCategoryName()) + .setParentId(Convert.toStr(category.getParentId())) + ); + } + /** * 校验流程分类名称是否唯一 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java index dba24e419..3557b96e1 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java @@ -12,7 +12,6 @@ import org.dromara.common.core.service.DeptService; import org.dromara.common.core.service.DictService; import org.dromara.common.core.service.UserService; import org.dromara.common.core.utils.DateUtils; -import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.warm.flow.core.dto.DefJson; @@ -57,12 +56,8 @@ public class FlwChartExtServiceImpl implements ChartExtService { */ @Override public void execute(DefJson defJson) { - // 临时修复 后续版本将通过defjson获取流程实例ID - String[] parts = ServletUtils.getRequest().getRequestURI().split("/"); - Long instanceId = Convert.toLong(parts[parts.length - 1]); - // 根据流程实例ID查询所有相关的历史任务列表 - List flowHisTasks = this.getHisTaskGroupedByNode(instanceId); + List flowHisTasks = this.getHisTaskGroupedByNode(defJson.getInstance().getId()); if (CollUtil.isEmpty(flowHisTasks)) { return; } @@ -143,7 +138,8 @@ public class FlwChartExtServiceImpl implements ChartExtService { "fontSize", "14px", "zIndex", "1000", "maxWidth", "500px", - "overflowY", "visible", + "maxHeight", "300px", + "overflowY", "auto", "overflowX", "hidden", "color", "#333", "pointerEvents", "auto", diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java index efc25a3f8..d0b53853b 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java @@ -11,9 +11,8 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mail.utils.MailUtils; import org.dromara.common.sse.dto.SseMessageDto; import org.dromara.common.sse.utils.SseMessageUtils; +import org.dromara.warm.flow.core.FlowEngine; import org.dromara.warm.flow.core.entity.Node; -import org.dromara.warm.flow.core.enums.SkipType; -import org.dromara.warm.flow.core.service.NodeService; import org.dromara.warm.flow.orm.entity.FlowTask; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.enums.MessageTypeEnum; @@ -21,8 +20,10 @@ import org.dromara.workflow.service.IFlwCommonService; import org.dromara.workflow.service.IFlwTaskService; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; +import java.util.Set; /** @@ -35,19 +36,27 @@ import java.util.stream.Collectors; @RequiredArgsConstructor @Service public class FlwCommonServiceImpl implements IFlwCommonService { - private final NodeService nodeService; + + private static final String DEFAULT_SUBJECT = "单据审批提醒"; /** - * 发送消息 + * 根据流程实例发送消息给当前处理人 * * @param flowName 流程定义名称 - * @param messageType 消息类型 - * @param message 消息内容,为空则发送默认配置的消息内容 + * @param instId 流程实例ID + * @param messageType 消息类型列表 + * @param message 消息内容,为空则使用默认消息 */ @Override public void sendMessage(String flowName, Long instId, List messageType, String message) { + if (CollUtil.isNotEmpty(messageType)) { + return; + } IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class); List list = flwTaskService.selectByInstId(instId); + if (CollUtil.isEmpty(list)) { + return; + } if (StringUtils.isBlank(message)) { message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。"; } @@ -55,19 +64,25 @@ public class FlwCommonServiceImpl implements IFlwCommonService { if (CollUtil.isEmpty(userList)) { return; } - sendMessage(messageType, message, "单据审批提醒", userList); + sendMessage(messageType, message, DEFAULT_SUBJECT, userList); } /** - * 发送消息 + * 发送消息给指定用户列表 * - * @param messageType 消息类型 + * @param messageType 消息类型列表 * @param message 消息内容 * @param subject 邮件标题 - * @param userList 接收用户 + * @param userList 接收用户列表 */ @Override public void sendMessage(List messageType, String message, String subject, List userList) { + if (CollUtil.isEmpty(messageType) || CollUtil.isEmpty(userList)) { + return; + } + List userIds = new ArrayList<>(StreamUtils.toSet(userList, UserDTO::getUserId)); + Set emails = StreamUtils.toSet(userList, UserDTO::getEmail); + for (String code : messageType) { MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code); if (ObjectUtil.isEmpty(messageTypeEnum)) { @@ -76,13 +91,11 @@ public class FlwCommonServiceImpl implements IFlwCommonService { switch (messageTypeEnum) { case SYSTEM_MESSAGE -> { SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList())); + dto.setUserIds(userIds); dto.setMessage(message); SseMessageUtils.publishMessage(dto); } - case EMAIL_MESSAGE -> { - MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), subject, message); - } + case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message); case SMS_MESSAGE -> { //todo 短信发送 } @@ -100,8 +113,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService { */ @Override public String applyNodeCode(Long definitionId) { - Node startNode = nodeService.getStartNode(definitionId); - Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey()); - return nextNode.getNodeCode(); + List firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>()); + return firstBetweenNode.get(0).getNodeCode(); } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java index d19c7313d..b89a369cf 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java @@ -128,7 +128,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { } } if (CollUtil.isNotEmpty(errorMsg)) { - throw new ServiceException("节点【" + StringUtils.join(errorMsg, ",") + "】未配置办理人!"); + throw new ServiceException("节点【{}】未配置办理人!", StringUtils.joinComma(errorMsg)); } } return defService.publish(id); @@ -188,7 +188,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 +231,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()); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java index 513ae7821..be6dfaf1a 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java @@ -110,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()); @@ -211,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 instanceIds) { + // 获取实例信息 + List instances = insService.getByIds(instanceIds); + if (CollUtil.isEmpty(instances)) { + log.warn("未找到对应的流程实例信息,无法执行删除操作。"); + return false; + } + // 获取定义信息 + Map 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 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; } /** @@ -371,6 +424,10 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { } try { Map 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); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java index fca21a367..b5f4c6deb 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java @@ -1,8 +1,8 @@ package org.dromara.workflow.service.impl; import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.dto.DictTypeDTO; @@ -13,14 +13,16 @@ import org.dromara.warm.flow.ui.service.NodeExtService; import org.dromara.warm.flow.ui.vo.NodeExt; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.enums.ButtonPermissionEnum; +import org.dromara.workflow.common.enums.CopySettingEnum; import org.dromara.workflow.common.enums.NodeExtEnum; +import org.dromara.workflow.common.enums.VariablesEnum; import org.dromara.workflow.domain.vo.ButtonPermissionVo; +import org.dromara.workflow.domain.vo.NodeExtVo; import org.dromara.workflow.service.IFlwNodeExtService; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 流程设计器-节点扩展属性 @@ -36,14 +38,35 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService /** * 存储不同 dictType 对应的配置信息 */ - private static final Map CHILD_NODE_MAP = new HashMap<>(); - - record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) { - } + private static final Map> CHILD_NODE_MAP; static { - CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(), - new ButtonPermission("权限按钮", 4, false, true)); + CHILD_NODE_MAP = Map.of( + CopySettingEnum.class.getSimpleName(), + Map.of( + "label", "抄送对象", + "type", 2, + "must", false, + "multiple", false, + "desc", "设置该节点的抄送办理人" + ), + VariablesEnum.class.getSimpleName(), + Map.of( + "label", "自定义参数", + "type", 2, + "must", false, + "multiple", false, + "desc", "节点执行时可以使用的自定义参数" + ), + ButtonPermissionEnum.class.getSimpleName(), + Map.of( + "label", "权限按钮", + "type", 4, + "must", false, + "multiple", true, + "desc", "控制该节点的按钮权限" + ) + ); } private final DictService dictService; @@ -56,6 +79,9 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService @Override public List getNodeExt() { List nodeExtList = new ArrayList<>(); + // 构建基础设置页面 + nodeExtList.add(buildNodeExt("wf_basic_tab", "基础设置", 1, + List.of(CopySettingEnum.class, VariablesEnum.class))); // 构建按钮权限页面 nodeExtList.add(buildNodeExt("wf_button_tab", "权限", 2, List.of(ButtonPermissionEnum.class))); @@ -105,9 +131,20 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService return null; } String simpleName = enumClass.getSimpleName(); - NodeExt.ChildNode childNode = buildChildNodeMap(simpleName); + NodeExt.ChildNode childNode = new NodeExt.ChildNode(); + Map map = CHILD_NODE_MAP.get(simpleName); // 编码,此json中唯 childNode.setCode(simpleName); + // label名称 + childNode.setLabel(Convert.toStr(map.get("label"))); + // 1:输入框 2:文本域 3:下拉框 4:选择框 + childNode.setType(Convert.toInt(map.get("type"), 1)); + // 是否必填 + childNode.setMust(Convert.toBool(map.get("must"), false)); + // 是否多选 + childNode.setMultiple(Convert.toBool(map.get("multiple"), true)); + // 描述 + childNode.setDesc(Convert.toStr(map.get("desc"), null)); // 字典,下拉框和复选框时用到 childNode.setDict(Arrays.stream(enumClass.getEnumConstants()) .map(NodeExtEnum.class::cast) @@ -128,12 +165,18 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService if (ObjectUtil.isNull(dictTypeDTO)) { return null; } - NodeExt.ChildNode childNode = buildChildNodeMap(dictType); + NodeExt.ChildNode childNode = new NodeExt.ChildNode(); // 编码,此json中唯一 childNode.setCode(dictType); // label名称 childNode.setLabel(dictTypeDTO.getDictName()); - // 描述 + // 1:输入框 2:文本域 3:下拉框 4:选择框 + childNode.setType(3); + // 是否必填 + childNode.setMust(false); + // 是否多选 + childNode.setMultiple(true); + // 描述 (可根据描述参数解析更多配置,如type,must,multiple等) childNode.setDesc(dictTypeDTO.getRemark()); // 字典,下拉框和复选框时用到 childNode.setDict(dictService.getDictData(dictType) @@ -144,100 +187,71 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService } /** - * 根据 CHILD_NODE_MAP 中的配置信息,构建一个基本的 ChildNode 对象 - * 该方法用于设置 ChildNode 的常规属性,例如 label、type、是否必填、是否多选等 + * 解析扩展属性 JSON 并构建 Node 扩展属性对象 + *

+ * 根据传入的 JSON 字符串,将扩展属性分为三类: + * 1. ButtonPermissionEnum:解析为按钮权限列表,标记每个按钮是否勾选 + * 2. CopySettingEnum:解析为抄送对象 ID 集合 + * 3. VariablesEnum:解析为自定义参数 Map * - * @param key CHILD_NODE_MAP 的 key - * @return 返回构建好的 ChildNode 对象 - */ - private NodeExt.ChildNode buildChildNodeMap(String key) { - NodeExt.ChildNode childNode = new NodeExt.ChildNode(); - ButtonPermission bp = CHILD_NODE_MAP.get(key); - if (bp == null) { - childNode.setType(1); - childNode.setMust(false); - childNode.setMultiple(true); - return childNode; - } - // label名称 - childNode.setLabel(bp.label()); - // 1:输入框 2:输入框 3:下拉框 4:选择框 - childNode.setType(bp.type()); - // 是否必填 - childNode.setMust(bp.must()); - // 是否多选 - childNode.setMultiple(bp.multiple()); - return childNode; - } - - /** - * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选 + *

示例 JSON: + * [ + * {"code": "ButtonPermissionEnum", "value": "back,termination"}, + * {"code": "CopySettingEnum", "value": "1"}, + * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"} + * ] * * @param ext 扩展属性 JSON 字符串 - * @return 按钮权限 VO 列表 + * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map */ @Override - public List buildButtonPermissionsFromExt(String ext) { - // 解析 ext 为 Map>,用于标记权限 - Map> permissionMap = JsonUtils.parseArray(ext, ButtonPermissionVo.class) - .stream() - .collect(Collectors.toMap( - ButtonPermissionVo::getCode, - item -> StringUtils.splitList(item.getValue()).stream() - .map(String::trim) - .filter(StrUtil::isNotBlank) - .collect(Collectors.toSet()), - (a, b) -> b, - HashMap::new - )); + public NodeExtVo parseNodeExt(String ext) { + NodeExtVo nodeExtVo = new NodeExtVo(); - // 构建按钮权限列表,标记哪些按钮在 permissionMap 中出现(表示已勾选) - return buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class)); - } + // 解析 JSON 为 Dict 列表 + List nodeExtMap = JsonUtils.parseArrayMap(ext); - /** - * 将权限映射与按钮权限来源(枚举类或字典类型)进行匹配,生成权限视图列表 - *

- * 使用说明: - * - sources 支持传入多个来源类型,支持 NodeExtEnum 枚举类 或 字典类型字符串(dictType) - * - 若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型 - *

- * 示例: - * buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class, "custom_button_dict")); - * - * @param permissionMap 权限映射 - * @param sources 枚举类或字典类型列表 - * @return 按钮权限视图对象列表 - */ - @SuppressWarnings("unchecked cast") - private List buildPermissionsFromSources(Map> permissionMap, List sources) { - return sources.stream() - .flatMap(source -> { - if (source instanceof Class clazz && NodeExtEnum.class.isAssignableFrom(clazz)) { - Set selectedSet = permissionMap.getOrDefault(clazz.getSimpleName(), Collections.emptySet()); - return extractDictItems(this.buildChildNode((Class) clazz), selectedSet).stream(); - } else if (source instanceof String dictType) { - Set selectedSet = permissionMap.getOrDefault(dictType, Collections.emptySet()); - return extractDictItems(this.buildChildNode(dictType), selectedSet).stream(); - } - return Stream.empty(); - }).toList(); - } + for (Dict nodeExt : nodeExtMap) { + String code = nodeExt.getStr("code"); + String value = nodeExt.getStr("value"); - /** - * 从节点子项中提取字典项,并构建按钮权限视图对象列表 - * - * @param childNode 子节点 - * @param selectedSet 已选中的值集 - * @return 按钮权限视图对象列表 - */ - private List extractDictItems(NodeExt.ChildNode childNode, Set selectedSet) { - return Optional.ofNullable(childNode) - .map(NodeExt.ChildNode::getDict) - .orElse(List.of()) - .stream() - .map(dict -> new ButtonPermissionVo(dict.getValue(), selectedSet.contains(dict.getValue()))) - .toList(); + if (ButtonPermissionEnum.class.getSimpleName().equals(code)) { + // 解析按钮权限 + // 将 value 拆分为 Set,便于精确匹配 + Set buttonSet = StringUtils.str2Set(value, StringUtils.SEPARATOR); + + // 获取按钮字典配置 + NodeExt.ChildNode childNode = buildChildNode(ButtonPermissionEnum.class); + + // 构建 ButtonPermissionVo 列表 + List buttonList = Optional.ofNullable(childNode) + .map(NodeExt.ChildNode::getDict) + .orElse(List.of()) + .stream() + .map(dict -> new ButtonPermissionVo(dict.getValue(), buttonSet.contains(dict.getValue()))) + .toList(); + + nodeExtVo.setButtonPermissions(buttonList); + + } else if (CopySettingEnum.class.getSimpleName().equals(code)) { + // 解析抄送对象 ID 集合 + nodeExtVo.setCopySettings(StringUtils.str2Set(value, StringUtils.SEPARATOR)); + + } else if (VariablesEnum.class.getSimpleName().equals(code)) { + // 解析自定义参数 + // 将 key=value 字符串拆分为 Map + Map variables = Arrays.stream(StringUtils.split(value, StringUtils.SEPARATOR)) + .map(s -> StringUtils.split(s, "=")) + .filter(arr -> arr.length == 2) + .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1])); + + nodeExtVo.setVariables(variables); + } else { + // 未知扩展类型,记录日志 + log.warn("未知扩展类型:code={}, value={}", code, value); + } + } + return nodeExtVo; } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java index be9567a5d..e06b31448 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java @@ -162,7 +162,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService { TableDataInfo page = this.queryPageList(bo, pageQuery); // 使用封装的字段映射方法进行转换 List 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); } @@ -182,7 +182,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() + ); } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java index 88839bb5c..9e9c091ad 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/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 lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,6 +15,7 @@ import org.dromara.common.core.domain.model.TaskAssigneeBody; import org.dromara.common.core.enums.FormatsType; import org.dromara.common.core.service.*; import org.dromara.common.core.utils.DateUtils; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.warm.flow.ui.dto.HandlerFunDto; import org.dromara.warm.flow.ui.dto.HandlerQuery; @@ -28,6 +30,7 @@ import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.springframework.stereotype.Service; import java.util.*; +import java.util.stream.Collectors; /** * 流程设计器-获取办理人权限设置列表 @@ -90,31 +93,25 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand return Collections.emptyList(); } // 解析并归类 ID,同时记录原始顺序和对应解析结果 - Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); - Map> parsedMap = new LinkedHashMap<>(); - List spelList = new ArrayList<>(); + Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); + Map> parsedMap = new LinkedHashMap<>(); for (String storageId : storageIds) { - Pair parsed = this.parseStorageId(storageId); + Pair 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, "$") || StringUtils.startsWith(storageId, "#")) { - // $前缀表示默认办理人变量策略 - // #前缀表示spel办理人变量策略 - spelList.add(storageId); } } // 查询所有类型对应的 ID 名称映射 - Map> nameMap = new EnumMap<>(TaskAssigneeEnum.class); + Map> nameMap = new EnumMap<>(TaskAssigneeEnum.class); typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids))); - Map spelMap = spelService.selectRemarksBySpels(spelList); // 组装返回结果,保持原始顺序 return parsedMap.entrySet().stream() .map(entry -> { String storageId = entry.getKey(); - Pair parsed = entry.getValue(); - String handlerName = (parsed == null) ? spelMap.get(storageId) + Pair parsed = entry.getValue(); + String handlerName = (parsed == null) ? null : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap()) .get(parsed.getValue()); return new HandlerFeedBackVo(storageId, handlerName); @@ -138,20 +135,37 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand * 根据任务办理类型获取部门数据 */ private List fetchDeptData(TaskAssigneeEnum type) { - if (type == TaskAssigneeEnum.USER || type == TaskAssigneeEnum.DEPT || type == TaskAssigneeEnum.POST) { + if (type.needsDeptService()) { return deptService.selectDeptsByList(); } 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.needsDeptService()) { + return deptService.selectDeptNameByIds(groupName); + } + return DEFAULT_GROUP_NAME; + } + /** * 构建部门树状结构 */ private TreeFunDto buildDeptTree(List depts) { return new TreeFunDto<>(depts) - .setId(dept -> String.valueOf(dept.getDeptId())) + .setId(dept -> Convert.toStr(dept.getDeptId())) .setName(DeptDTO::getDeptName) - .setParentId(dept -> String.valueOf(dept.getParentId())); + .setParentId(dept -> Convert.toStr(dept.getParentId())); } /** @@ -162,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(deptService::selectDeptNameByIds) - .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())); } @@ -182,9 +193,9 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand if (StringUtils.isEmpty(storageIds)) { return List.of(); } - Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); + Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); for (String storageId : storageIds.split(StringUtils.SEPARATOR)) { - Pair parsed = this.parseStorageId(storageId); + Pair parsed = this.parseStorageId(storageId); if (parsed != null) { typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue()); } @@ -205,13 +216,17 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand * 如果类型为部门(DEPT),则通过部门ID列表查询; * 如果类型为岗位(POST)或无法识别的类型,则返回空列表 */ - private List getUsersByType(TaskAssigneeEnum type, List ids) { + private List getUsersByType(TaskAssigneeEnum type, List ids) { + if (type == TaskAssigneeEnum.SPEL) { + return new ArrayList<>(); + } + List longIds = StreamUtils.toList(ids, Convert::toLong); return switch (type) { - case USER -> userService.selectListByIds(ids); - case ROLE -> userService.selectUsersByRoleIds(ids); - case DEPT -> userService.selectUsersByDeptIds(ids); - case POST -> userService.selectUsersByPostIds(ids); - case SPEL -> new ArrayList<>(); + case USER -> userService.selectListByIds(longIds); + case ROLE -> userService.selectUsersByRoleIds(longIds); + case DEPT -> userService.selectUsersByDeptIds(longIds); + case POST -> userService.selectUsersByPostIds(longIds); + default -> new ArrayList<>(); }; } @@ -222,14 +237,28 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand * @param ids ID 列表(如用户ID、角色ID等) * @return 返回 Map,其中 key 为 ID,value 为对应的名称 */ - private Map getNamesByType(TaskAssigneeEnum type, List ids) { - return switch (type) { - case USER -> userService.selectUserNamesByIds(ids); - case ROLE -> roleService.selectRoleNamesByIds(ids); - case DEPT -> deptService.selectDeptNamesByIds(ids); - case POST -> postService.selectPostNamesByIds(ids); - case SPEL -> new HashMap<>(); + private Map getNamesByType(TaskAssigneeEnum type, List ids) { + if (type == TaskAssigneeEnum.SPEL) { + return spelService.selectRemarksBySpels(ids); + } + + List longIds = StreamUtils.toList(ids, Convert::toLong); + Map rawMap = switch (type) { + case USER -> userService.selectUserNamesByIds(longIds); + case ROLE -> roleService.selectRoleNamesByIds(longIds); + case DEPT -> deptService.selectDeptNamesByIds(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 + )); } /** @@ -238,24 +267,20 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand * @param storageId 例如 "user:123" 或 "456" * @return Pair(TaskAssigneeEnum, Long),如果格式非法返回 null */ - private Pair parseStorageId(String storageId) { + private Pair 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()); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java index b3069e535..81807b58f 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java @@ -3,6 +3,7 @@ package org.dromara.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; @@ -21,6 +22,7 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; @@ -29,6 +31,7 @@ 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; @@ -41,10 +44,14 @@ import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.common.enums.TaskAssigneeType; import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.dromara.workflow.domain.FlowInstanceBizExt; import org.dromara.workflow.domain.bo.*; +import org.dromara.workflow.domain.vo.FlowCopyVo; import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowTaskVo; +import org.dromara.workflow.domain.vo.NodeExtVo; import org.dromara.workflow.mapper.FlwCategoryMapper; +import org.dromara.workflow.mapper.FlwInstanceBizExtMapper; import org.dromara.workflow.mapper.FlwTaskMapper; import org.dromara.workflow.service.IFlwCommonService; import org.dromara.workflow.service.IFlwNodeExtService; @@ -85,6 +92,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { private final IFlwTaskAssigneeService flwTaskAssigneeService; private final IFlwCommonService flwCommonService; private final IFlwNodeExtService flwNodeExtService; + private final FlwInstanceBizExtMapper flwInstanceBizExtMapper; /** * 启动任务 @@ -98,6 +106,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (StringUtils.isBlank(businessId)) { throw new ServiceException("启动工作流时必须包含业务ID"); } + // 启动流程实例(提交申请) Map variables = startProcessBo.getVariables(); // 流程发起人 @@ -106,9 +115,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService { variables.put(INITIATOR_DEPT_ID, LoginHelper.getDeptId()); // 业务id variables.put(BUSINESS_ID, businessId); + FlowInstanceBizExt bizExt = startProcessBo.getBizExt(); + + // 获取已有流程实例 FlowInstance flowInstance = flowInstanceMapper.selectOne(new LambdaQueryWrapper<>(FlowInstance.class) .eq(FlowInstance::getBusinessId, businessId)); + if (ObjectUtil.isNotNull(flowInstance)) { + // 已存在流程 BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus()); List taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId())); taskService.mergeVariable(flowInstance, variables); @@ -116,9 +130,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService { StartProcessReturnDTO dto = new StartProcessReturnDTO(); dto.setProcessInstanceId(taskList.get(0).getInstanceId()); dto.setTaskId(taskList.get(0).getId()); + // 保存流程实例业务信息 + this.buildFlowInstanceBizExt(flowInstance, bizExt); 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); + variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt)); FlowParams flowParams = FlowParams.build() + .handler(startProcessBo.getHandler()) .flowCode(startProcessBo.getFlowCode()) .variable(startProcessBo.getVariables()) .flowStatus(BusinessStatusEnum.DRAFT.getStatus()); @@ -128,6 +152,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService { } catch (Exception e) { throw new ServiceException(e.getMessage()); } + // 保存流程实例业务信息 + this.buildFlowInstanceBizExt(instance, bizExt); // 申请人执行流程 List taskList = taskService.list(new FlowTask().setInstanceId(instance.getId())); if (taskList.size() > 1) { @@ -139,6 +165,31 @@ public class FlwTaskServiceImpl implements IFlwTaskService { return dto; } + /** + * 生成业务编号,如果已有则直接返回已有值 + */ + private String generateBusinessCode(FlowInstanceBizExt bizExt) { + if (StringUtils.isBlank(bizExt.getBusinessCode())) { + // TODO: 按照自己业务规则生成编号 + String businessCode = Convert.toStr(System.currentTimeMillis()); + bizExt.setBusinessCode(businessCode); + return businessCode; + } + return bizExt.getBusinessCode(); + } + + /** + * 构建流程实例业务信息 + * + * @param instance 流程实例 + * @param bizExt 流程业务扩展信息 + */ + private void buildFlowInstanceBizExt(Instance instance, FlowInstanceBizExt bizExt) { + bizExt.setInstanceId(instance.getId()); + bizExt.setBusinessId(instance.getBusinessId()); + flwInstanceBizExtMapper.saveOrUpdateByInstanceId(bizExt); + } + /** * 办理任务 * @@ -155,11 +206,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService { // 获取抄送人 List flowCopyList = completeTaskBo.getFlowCopyList(); // 设置抄送人 - completeTaskBo.getVariables().put(FlowConstant.FLOW_COPY_LIST, flowCopyList); + Map 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 +221,24 @@ 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 assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap()); if (CollUtil.isNotEmpty(assigneeMap)) { - completeTaskBo.getVariables().putAll(assigneeMap); + variables.putAll(assigneeMap); } // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息 FlowParams flowParams = FlowParams.build() - .variable(completeTaskBo.getVariables()) + .handler(completeTaskBo.getHandler()) + .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 +246,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 flowTaskList = selectByInstId(instanceId); + if (CollUtil.isEmpty(flowTaskList)) { + return; + } + List 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 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 +305,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { List 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 +326,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 +348,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService { List 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 +411,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (CollUtil.isEmpty(taskList)) { return; } - List associatedUsers = FlowEngine.userService() - .getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId)); + List associatedUsers = FlowEngine.userService().getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId)); Map> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated); // 组装用户数据回任务列表 for (FlowTaskVo task : taskList) { @@ -418,22 +508,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService { /** * 获取可驳回的前置节点 * - * @param definitionId 流程定义id - * @param nowNodeCode 当前节点 + * @param taskId 任务id + * @param nowNodeCode 当前节点 */ @Override - public List getBackTaskNode(Long definitionId, String nowNodeCode) { - List nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), definitionId); + public List getBackTaskNode(Long taskId, String nowNodeCode) { + FlowTask task = flowTaskMapper.selectById(taskId); + List nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId()); if (!CollUtil.isNotEmpty(nodeCodes)) { return nodeCodes; } + List 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 nodes = nodeService.previousNodeList(definitionId, nowNodeCode); + List nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode); if (CollUtil.isNotEmpty(nodes)) { return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType())); } @@ -477,8 +573,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { */ @Override public List selectByIdList(List taskIdList) { - return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class) - .in(FlowTask::getId, taskIdList)); + return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getId, taskIdList)); } /** @@ -504,8 +599,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (ObjectUtil.isNull(flowNode)) { throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在"); } + NodeExtVo nodeExtVo = flwNodeExtService.parseNodeExt(flowNode.getExt()); //设置按钮权限 - flowTaskVo.setButtonList(flwNodeExtService.buildButtonPermissionsFromExt(flowNode.getExt())); + flowTaskVo.setButtonList(nodeExtVo.getButtonPermissions()); + if (CollUtil.isNotEmpty(nodeExtVo.getCopySettings())) { + List list = StreamUtils.toList(nodeExtVo.getCopySettings(), x -> new FlowCopyVo(Convert.toLong(x))); + flowTaskVo.setCopyList(list); + } else { + flowTaskVo.setCopyList(new ArrayList<>()); + } + flowTaskVo.setVarList(nodeExtVo.getVariables()); flowTaskVo.setNodeRatio(flowNode.getNodeRatio()); flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId()))); return flowTaskVo; @@ -535,17 +638,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService { //办理人变量替换 ExpressionUtil.evalVariable(buildNextTaskList, FlowParams.build().variable(mergeVariable)); for (FlowNode flowNode : nextFlowNodes) { - Optional first = buildNextTaskList.stream() - .filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())) - .findFirst(); - first.ifPresent(t -> { - if (CollUtil.isNotEmpty(t.getPermissionList())) { - List users = flwTaskAssigneeService.fetchUsersByStorageIds(String.join(StringUtils.SEPARATOR, t.getPermissionList())); - if (CollUtil.isNotEmpty(users)) { - flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId()))); + StreamUtils.findFirst(buildNextTaskList, t -> t.getNodeCode().equals(flowNode.getNodeCode())) + .ifPresent(first -> { + List users; + if (CollUtil.isNotEmpty(first.getPermissionList()) + && CollUtil.isNotEmpty(users = flwTaskAssigneeService.fetchUsersByStorageIds(StringUtils.joinComma(first.getPermissionList())))) { + flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId()))); } - } - }); + }); } } return nextFlowNodes; @@ -559,8 +659,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 +669,17 @@ public class FlwTaskServiceImpl implements IFlwTaskService { */ @Override public List 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 selectByInstIds(List instanceIds) { + return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getInstanceId, instanceIds)); } /** @@ -582,8 +690,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { */ @Override public boolean isTaskEnd(Long instanceId) { - boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper() - .eq(FlowTask::getInstanceId, instanceId)); + boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper().eq(FlowTask::getInstanceId, instanceId)); return !exists; } @@ -596,8 +703,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); } @@ -680,8 +786,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); } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java index 47fdfaff7..5f2246f6c 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java @@ -4,17 +4,16 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.domain.dto.CompleteTaskDTO; import org.dromara.common.core.domain.dto.StartProcessDTO; -import org.dromara.common.core.domain.dto.StartProcessReturnDTO; -import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; +import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.WorkflowService; @@ -108,6 +107,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true); // 截止日期也算一天 bo.setLeaveDays((int) day + 1); + bo.setApplyCode(System.currentTimeMillis() + StrUtil.EMPTY); TestLeave add = MapstructUtils.convert(bo, TestLeave.class); if (StringUtils.isBlank(add.getStatus())) { add.setStatus(BusinessStatusEnum.DRAFT.getStatus()); @@ -125,22 +125,24 @@ public class TestLeaveServiceImpl implements ITestLeaveService { long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true); // 截止日期也算一天 bo.setLeaveDays((int) day + 1); + if (ObjectUtil.isNull(bo.getId())) { + bo.setApplyCode(System.currentTimeMillis() + StrUtil.EMPTY); + } TestLeave leave = MapstructUtils.convert(bo, TestLeave.class); boolean flag = baseMapper.insertOrUpdate(leave); if (flag) { bo.setId(leave.getId()); // 后端发起需要忽略权限 bo.getParams().put("ignore", true); - StartProcessReturnDTO result = workflowService.startWorkFlow(new StartProcessDTO() {{ - setBusinessId(leave.getId().toString()); - setFlowCode(StringUtils.isEmpty(bo.getFlowCode()) ? "leave1" : bo.getFlowCode()); - setVariables(bo.getParams()); - }}); - boolean flag1 = workflowService.completeTask(new CompleteTaskDTO() {{ - setTaskId(result.getTaskId()); - setMessageType(List.of("1")); - setVariables(bo.getParams()); - }}); + + StartProcessDTO startProcess = new StartProcessDTO(); + startProcess.setBusinessId(leave.getId().toString()); + startProcess.setFlowCode(StringUtils.isEmpty(bo.getFlowCode()) ? "leave1" : bo.getFlowCode()); + startProcess.setVariables(bo.getParams()); + // 后端发起 如果没有登录用户 比如定时任务 可以手动设置一个处理人id + // startProcess.setHandler("0"); + + boolean flag1 = workflowService.startCompleteTask(startProcess); if (!flag1) { throw new ServiceException("流程发起异常"); } @@ -191,6 +193,10 @@ public class TestLeaveServiceImpl implements ITestLeaveService { String message = Convert.toStr(params.get("message")); } if (processEvent.getSubmit()) { + if(StringUtils.isBlank(testLeave.getApplyCode())){ + String businessCode = MapUtil.getStr(params, "businessCode",StrUtil.EMPTY); + testLeave.setApplyCode(businessCode); + } testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus()); } baseMapper.updateById(testLeave); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java index 0c0224000..6ef0c3ef2 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java @@ -6,17 +6,21 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.dto.CompleteTaskDTO; import org.dromara.common.core.domain.dto.StartProcessDTO; import org.dromara.common.core.domain.dto.StartProcessReturnDTO; +import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.WorkflowService; import org.dromara.common.core.utils.StringUtils; import org.dromara.warm.flow.orm.entity.FlowInstance; import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.enums.MessageTypeEnum; import org.dromara.workflow.domain.bo.CompleteTaskBo; import org.dromara.workflow.domain.bo.StartProcessBo; import org.dromara.workflow.service.IFlwDefinitionService; import org.dromara.workflow.service.IFlwInstanceService; import org.dromara.workflow.service.IFlwTaskService; 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,35 @@ public class WorkflowServiceImpl implements WorkflowService { return flwTaskService.completeTask(completeTask); } + /** + * 启动流程并办理第一个任务 + * + * @param startProcess 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean startCompleteTask(StartProcessDTO startProcess) { + try { + StartProcessBo processBo = new StartProcessBo(); + processBo.setBusinessId(startProcess.getBusinessId()); + processBo.setFlowCode(startProcess.getFlowCode()); + processBo.setVariables(startProcess.getVariables()); + processBo.setHandler(startProcess.getHandler()); + + StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo); + CompleteTaskBo taskBo = new CompleteTaskBo(); + taskBo.setTaskId(result.getTaskId()); + taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode())); + taskBo.setVariables(startProcess.getVariables()); + taskBo.setHandler(startProcess.getHandler()); + + boolean flag = flwTaskService.completeTask(taskBo); + if (!flag) { + throw new ServiceException("流程发起异常"); + } + return true; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml new file mode 100644 index 000000000..c2cc9c7c6 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceBizExtMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml index 30e2267dc..0ae7adbf3 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwInstanceMapper.xml @@ -27,9 +27,12 @@ fd.version, fd.form_custom, fd.form_path, - fd.category + fd.category, + biz.business_code, + biz.business_title from flow_instance fi left join flow_definition fd on fi.definition_id = fd.id + left join flow_instance_biz_ext biz on biz.instance_id = fi.id ${ew.getCustomSqlSegment} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml index f539030b4..c19992847 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml @@ -30,11 +30,14 @@ COALESCE(t.form_path, d.form_path) as form_path, d.version, uu.processed_by, - uu.type + uu.type, + biz.business_code, + biz.business_title from flow_task t left join flow_user uu on uu.associated = t.id left join flow_definition d on t.definition_id = d.id left join flow_instance i on t.instance_id = i.id + left join flow_instance_biz_ext biz on biz.instance_id = i.id where t.node_type = 1 and t.del_flag = '0' and uu.del_flag = '0' @@ -71,10 +74,13 @@ c.flow_name, c.flow_code, c.category, - c.version + c.version, + biz.business_code, + biz.business_title from flow_his_task a left join flow_instance b on a.instance_id = b.id left join flow_definition c on a.definition_id = c.id + left join flow_instance_biz_ext biz on biz.instance_id = b.id where a.del_flag ='0' and b.del_flag = '0' and c.del_flag = '0' @@ -100,11 +106,14 @@ d.flow_name, d.flow_code, d.category, - d.version + d.version, + biz.business_code, + biz.business_title from flow_user a left join flow_his_task b on a.associated = b.task_id left join flow_instance c on b.instance_id = c.id left join flow_definition d on c.definition_id=d.id + left join flow_instance_biz_ext biz on biz.instance_id = c.id where a.type = '4' and a.del_flag = '0' and b.del_flag = '0' diff --git a/script/sql/oracle/oracle_ry_vue_5.X.sql b/script/sql/oracle/oracle_ry_vue_5.X.sql index daee48561..041e99a57 100644 --- a/script/sql/oracle/oracle_ry_vue_5.X.sql +++ b/script/sql/oracle/oracle_ry_vue_5.X.sql @@ -447,11 +447,11 @@ insert into sys_menu values('115', '代码生成', '3', '2', 'gen', insert into sys_menu values('121', '租户管理', '6', '1', 'tenant', 'system/tenant/index', '', 1, 0, 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, sysdate, null, null, '租户管理菜单'); insert into sys_menu values('122', '租户套餐管理', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, sysdate, null, null, '租户套餐管理菜单'); insert into sys_menu values('123', '客户端管理', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate, null, null, '客户端管理菜单'); -insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate, null, null, ''); -insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate, null, null, ''); -insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate, null, null, ''); -insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate, null, null, ''); -insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, ''); +insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate, null, null, '/tool/gen'); +insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate, null, null, '/system/role'); +insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate, null, null, '/system/user'); +insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate, null, null, '/system/dict'); +insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate, null, null, '/system/oss'); -- springboot-admin监控 insert into sys_menu values('117', 'Admin监控', '2', '5', 'Admin', 'monitor/admin/index', '', 1, 0, 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, sysdate, null, null, 'Admin监控菜单'); diff --git a/script/sql/oracle/oracle_ry_workflow.sql b/script/sql/oracle/oracle_ry_workflow.sql index fbbc3cb0e..d8768796c 100644 --- a/script/sql/oracle/oracle_ry_workflow.sql +++ b/script/sql/oracle/oracle_ry_workflow.sql @@ -6,6 +6,7 @@ create table FLOW_DEFINITION ID NUMBER(20) not null, FLOW_CODE VARCHAR2(40) not null, FLOW_NAME VARCHAR2(100) not null, + MODEL_VALUE VARCHAR2(40) default 'CLASSICS' not null, CATEGORY VARCHAR2(100), VERSION VARCHAR2(20) not null, IS_PUBLISH NUMBER(1) default 0 not null, @@ -28,6 +29,7 @@ comment on table FLOW_DEFINITION is '流程定义表'; comment on column FLOW_DEFINITION.ID is '主键id'; comment on column FLOW_DEFINITION.FLOW_CODE is '流程编码'; comment on column FLOW_DEFINITION.FLOW_NAME is '流程名称'; +comment on column FLOW_DEFINITION.MODEL_VALUE is '设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)'; comment on column FLOW_DEFINITION.CATEGORY is '流程类别'; comment on column FLOW_DEFINITION.VERSION is '流程版本'; comment on column FLOW_DEFINITION.IS_PUBLISH is '是否发布 (0未发布 1已发布 9失效)'; @@ -219,7 +221,7 @@ create table FLOW_HIS_TASK TARGET_NODE_NAME VARCHAR2(200), APPROVER VARCHAR2(40), COOPERATE_TYPE NUMBER(1) default 0, - COLLABORATOR VARCHAR2(40), + COLLABORATOR VARCHAR2(500), SKIP_TYPE VARCHAR2(10), FLOW_STATUS VARCHAR2(20), FORM_CUSTOM VARCHAR2(1) default 'N', @@ -377,12 +379,39 @@ COMMENT ON COLUMN flow_spel.update_time IS '更新时间'; INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '根据部门id获取部门负责人', '0', '0', 103, 1, SYSDATE, 1, SYSDATE); INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, SYSDATE, 1, SYSDATE); -INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '流程达式定义菜单'); -INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, ''); +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +CREATE TABLE flow_instance_biz_ext ( + id NUMBER(20), + tenant_id VARCHAR2(20) DEFAULT '000000', + create_dept NUMBER(20), + create_by NUMBER(20), + create_time TIMESTAMP, + update_by NUMBER(20), + update_time TIMESTAMP, + business_code VARCHAR2(255), + business_title VARCHAR2(1000), + del_flag CHAR(1) DEFAULT '0', + instance_id NUMBER(20), + business_id VARCHAR2(255) +); + +alter table flow_instance_biz_ext add constraint pk_fi_biz_ext primary key (id); + +COMMENT ON TABLE flow_instance_biz_ext IS '流程实例业务扩展表'; +COMMENT ON COLUMN flow_instance_biz_ext.id IS '主键id'; +COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '租户编号'; +COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '创建者'; +COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '创建时间'; +COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '更新者'; +COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '更新时间'; +COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '业务编码'; +COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '业务标题'; +COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '删除标志(0代表存在 1代表删除)'; +COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '流程实例Id'; +COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '业务Id'; -- ---------------------------- -- 请假单信息 @@ -391,6 +420,7 @@ CREATE TABLE test_leave ( id NUMBER (20) NOT NULL, tenant_id VARCHAR2 (20) DEFAULT '000000', + apply_code VARCHAR2 (50) NOT NULL, leave_type VARCHAR2 (255) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, @@ -409,6 +439,7 @@ alter table test_leave add constraint pk_test_leave primary key (id); COMMENT ON TABLE test_leave IS '请假申请表'; COMMENT ON COLUMN test_leave.id IS 'ID'; COMMENT ON COLUMN test_leave.tenant_id IS '租户编号'; +COMMENT ON COLUMN test_leave.apply_code IS '申请编号'; COMMENT ON COLUMN test_leave.leave_type IS '请假类型'; COMMENT ON COLUMN test_leave.start_date IS '开始时间'; COMMENT ON COLUMN test_leave.end_date IS '结束时间'; @@ -432,7 +463,7 @@ INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '/workflow/processDefinition'); INSERT INTO sys_menu VALUES ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11623', '流程分类查询', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, SYSDATE, NULL, NULL, ''); @@ -441,6 +472,13 @@ INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', ' INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '流程达式定义菜单'); +INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, ''); + INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, SYSDATE, NULL, NULL, '请假申请菜单'); INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11640', '请假申请新增', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, SYSDATE, NULL, NULL, ''); diff --git a/script/sql/postgres/postgres_ry_vue_5.X.sql b/script/sql/postgres/postgres_ry_vue_5.X.sql index 86a9eddd6..b5da8e096 100644 --- a/script/sql/postgres/postgres_ry_vue_5.X.sql +++ b/script/sql/postgres/postgres_ry_vue_5.X.sql @@ -448,11 +448,11 @@ insert into sys_menu values('115', '代码生成', '3', '2', 'gen', insert into sys_menu values('121', '租户管理', '6', '1', 'tenant', 'system/tenant/index', '', '1', '0', 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, now(), null, null, '租户管理菜单'); insert into sys_menu values('122', '租户套餐管理', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', '1', '0', 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, now(), null, null, '租户套餐管理菜单'); insert into sys_menu values('123', '客户端管理', '1', '11', 'client', 'system/client/index', '', '1', '0', 'C', '0', '0', 'system:client:list', 'international', 103, 1, now(), null, null, '客户端管理菜单'); -insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', '1', '1', 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, now(), null, null, ''); -insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', '1', '1', 'C', '1', '0', 'system:role:edit', '#', 103, 1, now(), null, null, ''); -insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', '1', '1', 'C', '1', '0', 'system:user:edit', '#', 103, 1, now(), null, null, ''); -insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', '1', '1', 'C', '1', '0', 'system:dict:list', '#', 103, 1, now(), null, null, ''); -insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', '1', '1', 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, ''); +insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', '1', '1', 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, now(), null, null, '/tool/gen'); +insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', '1', '1', 'C', '1', '0', 'system:role:edit', '#', 103, 1, now(), null, null, '/system/role'); +insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', '1', '1', 'C', '1', '0', 'system:user:edit', '#', 103, 1, now(), null, null, '/system/user'); +insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', '1', '1', 'C', '1', '0', 'system:dict:list', '#', 103, 1, now(), null, null, '/system/dict'); +insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', '1', '1', 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, now(), null, null, '/system/oss'); -- springboot-admin监控 insert into sys_menu values('117', 'Admin监控', '2', '5', 'Admin', 'monitor/admin/index', '', '1', '0', 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, now(), null, null, 'Admin监控菜单'); diff --git a/script/sql/postgres/postgres_ry_workflow.sql b/script/sql/postgres/postgres_ry_workflow.sql index 29cfa7aa3..7df5ab0e1 100644 --- a/script/sql/postgres/postgres_ry_workflow.sql +++ b/script/sql/postgres/postgres_ry_workflow.sql @@ -1,24 +1,22 @@ --- ---------------------------- --- 0、warm-flow-all.sql,地址:https://gitee.com/dromara/warm-flow/blob/master/sql/postgresql/postgresql-warm-flow-all.sql --- ---------------------------- CREATE TABLE flow_definition ( - id int8 NOT NULL, -- 主键id - flow_code varchar(40) NOT NULL, -- 流程编码 - flow_name varchar(100) NOT NULL, -- 流程名称 - category varchar(100) NULL, -- 流程类别 - "version" varchar(20) NOT NULL, -- 流程版本 - is_publish int2 NOT NULL DEFAULT 0, -- 是否发布(0未发布 1已发布 9失效) - form_custom bpchar(1) NULL DEFAULT 'N':: character varying, -- 审批表单是否自定义(Y是 N否) - form_path varchar(100) NULL, -- 审批表单路径 - activity_status int2 NOT NULL DEFAULT 1, -- 流程激活状态(0挂起 1激活) - listener_type varchar(100) NULL, -- 监听器类型 - listener_path varchar(400) NULL, -- 监听器路径 - ext varchar(500) NULL, -- 扩展字段,预留给业务系统使用 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + flow_code varchar(40) NOT NULL, + flow_name varchar(100) NOT NULL, + model_value varchar(40) NOT NULL DEFAULT 'CLASSICS', + category varchar(100) NULL, + "version" varchar(20) NOT NULL, + is_publish int2 NOT NULL DEFAULT 0, + form_custom bpchar(1) NULL DEFAULT 'N':: character varying, + form_path varchar(100) NULL, + activity_status int2 NOT NULL DEFAULT 1, + listener_type varchar(100) NULL, + listener_path varchar(400) NULL, + ext varchar(500) NULL, + create_time timestamp NULL, + update_time timestamp NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_definition_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_definition IS '流程定义表'; @@ -26,6 +24,7 @@ COMMENT ON TABLE flow_definition IS '流程定义表'; COMMENT ON COLUMN flow_definition.id IS '主键id'; COMMENT ON COLUMN flow_definition.flow_code IS '流程编码'; COMMENT ON COLUMN flow_definition.flow_name IS '流程名称'; +COMMENT ON COLUMN flow_definition.model_value IS '设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)'; COMMENT ON COLUMN flow_definition.category IS '流程类别'; COMMENT ON COLUMN flow_definition."version" IS '流程版本'; COMMENT ON COLUMN flow_definition.is_publish IS '是否发布(0未发布 1已发布 9失效)'; @@ -42,27 +41,27 @@ COMMENT ON COLUMN flow_definition.tenant_id IS '租户id'; CREATE TABLE flow_node ( - id int8 NOT NULL, -- 主键id - node_type int2 NOT NULL, -- 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - definition_id int8 NOT NULL, -- 流程定义id - node_code varchar(100) NOT NULL, -- 流程节点编码 - node_name varchar(100) NULL, -- 流程节点名称 - permission_flag varchar(200) NULL, -- 权限标识(权限类型:权限标识,可以多个,用@@隔开) - node_ratio numeric(6, 3) NULL, -- 流程签署比例值 - coordinate varchar(100) NULL, -- 坐标 - any_node_skip varchar(100) NULL, -- 任意结点跳转 - listener_type varchar(100) NULL, -- 监听器类型 - listener_path varchar(400) NULL, -- 监听器路径 - handler_type varchar(100) NULL, -- 处理器类型 - handler_path varchar(400) NULL, -- 处理器路径 - form_custom bpchar(1) NULL DEFAULT 'N':: character varying, -- 审批表单是否自定义(Y是 N否) - form_path varchar(100) NULL, -- 审批表单路径 - "version" varchar(20) NOT NULL, -- 版本 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - ext text NULL, -- 扩展属性 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + node_type int2 NOT NULL, + definition_id int8 NOT NULL, + node_code varchar(100) NOT NULL, + node_name varchar(100) NULL, + permission_flag varchar(200) NULL, + node_ratio numeric(6, 3) NULL, + coordinate varchar(100) NULL, + any_node_skip varchar(100) NULL, + listener_type varchar(100) NULL, + listener_path varchar(400) NULL, + handler_type varchar(100) NULL, + handler_path varchar(400) NULL, + form_custom bpchar(1) NULL DEFAULT 'N':: character varying, + form_path varchar(100) NULL, + "version" varchar(20) NOT NULL, + create_time timestamp NULL, + update_time timestamp NULL, + ext text NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_node_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_node IS '流程节点表'; @@ -92,20 +91,20 @@ COMMENT ON COLUMN flow_node.tenant_id IS '租户id'; CREATE TABLE flow_skip ( - id int8 NOT NULL, -- 主键id - definition_id int8 NOT NULL, -- 流程定义id - now_node_code varchar(100) NOT NULL, -- 当前流程节点的编码 - now_node_type int2 NULL, -- 当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - next_node_code varchar(100) NOT NULL, -- 下一个流程节点的编码 - next_node_type int2 NULL, -- 下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - skip_name varchar(100) NULL, -- 跳转名称 - skip_type varchar(40) NULL, -- 跳转类型(PASS审批通过 REJECT退回) - skip_condition varchar(200) NULL, -- 跳转条件 - coordinate varchar(100) NULL, -- 坐标 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + definition_id int8 NOT NULL, + now_node_code varchar(100) NOT NULL, + now_node_type int2 NULL, + next_node_code varchar(100) NOT NULL, + next_node_type int2 NULL, + skip_name varchar(100) NULL, + skip_type varchar(40) NULL, + skip_condition varchar(200) NULL, + coordinate varchar(100) NULL, + create_time timestamp NULL, + update_time timestamp NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_skip_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_skip IS '节点跳转关联表'; @@ -127,22 +126,22 @@ COMMENT ON COLUMN flow_skip.tenant_id IS '租户id'; CREATE TABLE flow_instance ( - id int8 NOT NULL, -- 主键id - definition_id int8 NOT NULL, -- 对应flow_definition表的id - business_id varchar(40) NOT NULL, -- 业务id - node_type int2 NOT NULL, -- 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - node_code varchar(40) NOT NULL, -- 流程节点编码 - node_name varchar(100) NULL, -- 流程节点名称 - variable text NULL, -- 任务变量 - flow_status varchar(20) NOT NULL, -- 流程状态(0待提交 1审批中 2 审批通过 8已完成 9已退回 10失效) - activity_status int2 NOT NULL DEFAULT 1, -- 流程激活状态(0挂起 1激活) - def_json text NULL, -- 流程定义json - create_by varchar(64) NULL DEFAULT '':: character varying, -- 创建者 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - ext varchar(500) NULL, -- 扩展字段,预留给业务系统使用 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + definition_id int8 NOT NULL, + business_id varchar(40) NOT NULL, + node_type int2 NOT NULL, + node_code varchar(40) NOT NULL, + node_name varchar(100) NULL, + variable text NULL, + flow_status varchar(20) NOT NULL, + activity_status int2 NOT NULL DEFAULT 1, + def_json text NULL, + create_by varchar(64) NULL DEFAULT '':: character varying, + create_time timestamp NULL, + update_time timestamp NULL, + ext varchar(500) NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_instance_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_instance IS '流程实例表'; @@ -166,19 +165,19 @@ COMMENT ON COLUMN flow_instance.tenant_id IS '租户id'; CREATE TABLE flow_task ( - id int8 NOT NULL, -- 主键id - definition_id int8 NOT NULL, -- 对应flow_definition表的id - instance_id int8 NOT NULL, -- 对应flow_instance表的id - node_code varchar(100) NOT NULL, -- 节点编码 - node_name varchar(100) NULL, -- 节点名称 - node_type int2 NOT NULL, -- 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - flow_status varchar(20) NOT NULL, -- 流程状态(0待提交 1审批中 2 审批通过 8已完成 9已退回 10失效) - form_custom bpchar(1) NULL DEFAULT 'N':: character varying, -- 审批表单是否自定义(Y是 N否) - form_path varchar(100) NULL, -- 审批表单路径 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + definition_id int8 NOT NULL, + instance_id int8 NOT NULL, + node_code varchar(100) NOT NULL, + node_name varchar(100) NULL, + node_type int2 NOT NULL, + flow_status varchar(20) NOT NULL, + form_custom bpchar(1) NULL DEFAULT 'N':: character varying, + form_path varchar(100) NULL, + create_time timestamp NULL, + update_time timestamp NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_task_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_task IS '待办任务表'; @@ -199,29 +198,29 @@ COMMENT ON COLUMN flow_task.tenant_id IS '租户id'; CREATE TABLE flow_his_task ( - id int8 NOT NULL, -- 主键id - definition_id int8 NOT NULL, -- 对应flow_definition表的id - instance_id int8 NOT NULL, -- 对应flow_instance表的id - task_id int8 NOT NULL, -- 对应flow_task表的id - node_code varchar(100) NULL, -- 开始节点编码 - node_name varchar(100) NULL, -- 开始节点名称 - node_type int2 NULL, -- 开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) - target_node_code varchar(200) NULL, -- 目标节点编码 - target_node_name varchar(200) NULL, -- 结束节点名称 - approver varchar(40) NULL, -- 审批者 - cooperate_type int2 NOT NULL DEFAULT 0, -- 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签) - collaborator varchar(40) NULL, -- 协作人(只有转办、会签、票签、委派) - skip_type varchar(10) NULL, -- 流转类型(PASS通过 REJECT退回 NONE无动作) - flow_status varchar(20) NOT NULL, -- 流程状态(0待提交 1审批中 2 审批通过 8已完成 9已退回 10失效) - form_custom bpchar(1) NULL DEFAULT 'N':: character varying, -- 审批表单是否自定义(Y是 N否) - form_path varchar(100) NULL, -- 审批表单路径 - ext text NULL, -- 扩展字段,预留给业务系统使用 - message varchar(500) NULL, -- 审批意见 - variable text NULL, -- 任务变量 - create_time timestamp NULL, -- 创建时间 - update_time timestamp NULL, -- 更新时间 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + definition_id int8 NOT NULL, + instance_id int8 NOT NULL, + task_id int8 NOT NULL, + node_code varchar(100) NULL, + node_name varchar(100) NULL, + node_type int2 NULL, + target_node_code varchar(200) NULL, + target_node_name varchar(200) NULL, + approver varchar(40) NULL, + cooperate_type int2 NOT NULL DEFAULT 0, + collaborator varchar(500) NULL, + skip_type varchar(10) NULL, + flow_status varchar(20) NOT NULL, + form_custom bpchar(1) NULL DEFAULT 'N':: character varying, + form_path varchar(100) NULL, + ext text NULL, + message varchar(500) NULL, + variable text NULL, + create_time timestamp NULL, + update_time timestamp NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_his_task_pkey PRIMARY KEY (id) ); COMMENT ON TABLE flow_his_task IS '历史任务记录表'; @@ -252,15 +251,15 @@ COMMENT ON COLUMN flow_his_task.tenant_id IS '租户id'; CREATE TABLE flow_user ( - id int8 NOT NULL, -- 主键id - "type" bpchar(1) NOT NULL, -- 人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3流程实例的抄送人权限 4待办任务的委托人权限) - processed_by varchar(80) NULL, -- 权限人 - associated int8 NOT NULL, -- 任务表id - create_time timestamp NULL, -- 创建时间 - create_by varchar(80) NULL, -- 创建人 - update_time timestamp NULL, -- 更新时间 - del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志 - tenant_id varchar(40) NULL, -- 租户id + id int8 NOT NULL, + "type" bpchar(1) NOT NULL, + processed_by varchar(80) NULL, + associated int8 NOT NULL, + create_time timestamp NULL, + create_by varchar(80) NULL, + update_time timestamp NULL, + del_flag bpchar(1) NULL DEFAULT '0':: character varying, + tenant_id varchar(40) NULL, CONSTRAINT flow_user_pk PRIMARY KEY (id) ); CREATE INDEX user_processed_type ON flow_user USING btree (processed_by, type); @@ -326,7 +325,7 @@ INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '离职', 2, -- 流程spel表达式定义表 -- ---------------------------- CREATE TABLE flow_spel ( - id BIGINT NOT NULL, + id int8 NOT NULL, component_name VARCHAR(255), method_name VARCHAR(255), method_params VARCHAR(255), @@ -334,10 +333,10 @@ CREATE TABLE flow_spel ( remark VARCHAR(255), status CHAR(1) DEFAULT '0', del_flag CHAR(1) DEFAULT '0', - create_dept BIGINT, - create_by BIGINT, + create_dept int8, + create_by int8, create_time TIMESTAMP, - update_by BIGINT, + update_by int8, update_time TIMESTAMP, PRIMARY KEY (id) ); @@ -360,12 +359,38 @@ COMMENT ON COLUMN flow_spel.update_time IS '更新时间'; INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '根据部门id获取部门负责人', '0', '0', 103, 1, now(), 1, now()); INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, now(), 1, now()); -INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '流程达式定义菜单'); -INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, ''); +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +CREATE TABLE flow_instance_biz_ext ( + id int8, + tenant_id VARCHAR(20) DEFAULT '000000', + create_dept int8, + create_by int8, + create_time TIMESTAMP, + update_by int8, + update_time TIMESTAMP, + business_code VARCHAR(255), + business_title VARCHAR(1000), + del_flag CHAR(1) DEFAULT '0', + instance_id int8, + business_id VARCHAR(255), + PRIMARY KEY (id) +); + +COMMENT ON TABLE flow_instance_biz_ext IS '流程实例业务扩展表'; +COMMENT ON COLUMN flow_instance_biz_ext.id IS '主键id'; +COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '租户编号'; +COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '创建者'; +COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '创建时间'; +COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '更新者'; +COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '更新时间'; +COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '业务编码'; +COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '业务标题'; +COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '删除标志(0代表存在 1代表删除)'; +COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '流程实例Id'; +COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '业务Id'; -- ---------------------------- -- 请假单信息 @@ -374,6 +399,7 @@ CREATE TABLE test_leave ( id int8 NOT NULL, tenant_id VARCHAR(20) DEFAULT '000000'::varchar, + apply_code VARCHAR(50) NOT NULL, leave_type VARCHAR(255) NOT NULL, start_date TIMESTAMP NOT NULL, end_date TIMESTAMP NOT NULL, @@ -391,6 +417,7 @@ CREATE TABLE test_leave COMMENT ON TABLE test_leave IS '请假申请表'; COMMENT ON COLUMN test_leave.id IS 'id'; COMMENT ON COLUMN test_leave.tenant_id IS '租户编号'; +COMMENT ON COLUMN test_leave.apply_code IS '申请编号'; COMMENT ON COLUMN test_leave.leave_type IS '请假类型'; COMMENT ON COLUMN test_leave.start_date IS '开始时间'; COMMENT ON COLUMN test_leave.end_date IS '结束时间'; @@ -414,7 +441,7 @@ INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, ''); -INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '/workflow/processDefinition'); INSERT INTO sys_menu VALUES ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11623', '流程分类查询', '11622', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:query', '#', 103, 1, now(), NULL, NULL, ''); @@ -422,6 +449,14 @@ INSERT INTO sys_menu VALUES ('11624', '流程分类新增', '11622', '2', '#', ' INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:edit', '#', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, now(), NULL, NULL, ''); + +INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '流程达式定义菜单'); +INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, ''); + INSERT INTO sys_menu VALUES ('11638', '请假申请', '5', '1', 'leave', 'workflow/leave/index', '', '1', '0', 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, now(), NULL, NULL, '请假申请菜单'); INSERT INTO sys_menu VALUES ('11639', '请假申请查询', '11638', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:query', '#', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11640', '请假申请新增', '11638', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:add', '#', 103, 1, now(), NULL, NULL, ''); diff --git a/script/sql/ry_vue_5.X.sql b/script/sql/ry_vue_5.X.sql index ddc20876d..dae47920d 100644 --- a/script/sql/ry_vue_5.X.sql +++ b/script/sql/ry_vue_5.X.sql @@ -282,11 +282,11 @@ insert into sys_menu values('115', '代码生成', '3', '2', 'gen', insert into sys_menu values('121', '租户管理', '6', '1', 'tenant', 'system/tenant/index', '', 1, 0, 'C', '0', '0', 'system:tenant:list', 'list', 103, 1, sysdate(), null, null, '租户管理菜单'); insert into sys_menu values('122', '租户套餐管理', '6', '2', 'tenantPackage', 'system/tenantPackage/index', '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list', 'form', 103, 1, sysdate(), null, null, '租户套餐管理菜单'); insert into sys_menu values('123', '客户端管理', '1', '11', 'client', 'system/client/index', '', 1, 0, 'C', '0', '0', 'system:client:list', 'international', 103, 1, sysdate(), null, null, '客户端管理菜单'); -insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate(), null, null, ''); -insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate(), null, null, ''); -insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate(), null, null, ''); -insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate(), null, null, ''); -insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values('116', '修改生成配置', '3', '2', 'gen-edit/index/:tableId', 'tool/gen/editTable', '', 1, 1, 'C', '1', '0', 'tool:gen:edit', '#', 103, 1, sysdate(), null, null, '/tool/gen'); +insert into sys_menu values('130', '分配用户', '1', '2', 'role-auth/user/:roleId', 'system/role/authUser', '', 1, 1, 'C', '1', '0', 'system:role:edit', '#', 103, 1, sysdate(), null, null, '/system/role'); +insert into sys_menu values('131', '分配角色', '1', '1', 'user-auth/role/:userId', 'system/user/authRole', '', 1, 1, 'C', '1', '0', 'system:user:edit', '#', 103, 1, sysdate(), null, null, '/system/user'); +insert into sys_menu values('132', '字典数据', '1', '6', 'dict-data/index/:dictId', 'system/dict/data', '', 1, 1, 'C', '1', '0', 'system:dict:list', '#', 103, 1, sysdate(), null, null, '/system/dict'); +insert into sys_menu values('133', '文件配置管理', '1', '10', 'oss-config/index', 'system/oss/config', '', 1, 1, 'C', '1', '0', 'system:ossConfig:list', '#', 103, 1, sysdate(), null, null, '/system/oss'); -- springboot-admin监控 insert into sys_menu values('117', 'Admin监控', '2', '5', 'Admin', 'monitor/admin/index', '', 1, 0, 'C', '0', '0', 'monitor:admin:list', 'dashboard', 103, 1, sysdate(), null, null, 'Admin监控菜单'); diff --git a/script/sql/ry_workflow.sql b/script/sql/ry_workflow.sql index b3cfe1140..7419d6fb7 100644 --- a/script/sql/ry_workflow.sql +++ b/script/sql/ry_workflow.sql @@ -6,6 +6,7 @@ CREATE TABLE `flow_definition` `id` bigint NOT NULL COMMENT '主键id', `flow_code` varchar(40) NOT NULL COMMENT '流程编码', `flow_name` varchar(100) NOT NULL COMMENT '流程名称', + `model_value` varchar(40) NOT NULL DEFAULT 'CLASSICS' COMMENT '设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)', `category` varchar(100) DEFAULT NULL COMMENT '流程类别', `version` varchar(20) NOT NULL COMMENT '流程版本', `is_publish` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否发布(0未发布 1已发布 9失效)', @@ -119,7 +120,7 @@ CREATE TABLE `flow_his_task` `target_node_name` varchar(200) DEFAULT NULL COMMENT '结束节点名称', `approver` varchar(40) DEFAULT NULL COMMENT '审批者', `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', - `collaborator` varchar(40) DEFAULT NULL COMMENT '协作人', + `collaborator` varchar(500) DEFAULT NULL COMMENT '协作人', `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', @@ -206,13 +207,35 @@ CREATE TABLE flow_spel ( INSERT INTO flow_spel VALUES (1, 'spelRuleComponent', 'selectDeptLeaderById', 'initiatorDeptId', '#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', '根据部门id获取部门负责人', '0', '0', 103, 1, sysdate(), 1, sysdate()); INSERT INTO flow_spel VALUES (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, sysdate(), 1, sysdate()); +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- + +create table flow_instance_biz_ext ( + id bigint not null comment '主键id', + tenant_id varchar(20) default '000000' null comment '租户编号', + create_dept bigint null comment '创建部门', + create_by bigint null comment '创建者', + create_time datetime null comment '创建时间', + update_by bigint null comment '更新者', + update_time datetime null comment '更新时间', + business_code varchar(255) null comment '业务编码', + business_title varchar(1000) null comment '业务标题', + del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)', + instance_id bigint null comment '流程实例Id', + business_id varchar(255) null comment '业务Id', + PRIMARY KEY (id) +) ENGINE = InnoDB COMMENT '流程实例业务扩展表'; + -- ---------------------------- -- 请假单信息 -- ---------------------------- + create table test_leave ( id bigint(20) not null comment 'id', - tenant_id varchar(20) default '000000' comment '租户编号', + tenant_id varchar(20) default '000000' comment '租户编号', + apply_code varchar(50) not null comment '申请编号', leave_type varchar(255) not null comment '请假类型', start_date datetime not null comment '开始时间', end_date datetime not null comment '结束时间', @@ -239,7 +262,7 @@ INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', '2', 'spel', insert into sys_menu values ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, ''); -insert into sys_menu values ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '/workflow/processDefinition'); insert into sys_menu values ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, ''); -- 流程分类管理相关按钮 insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1,sysdate(), null, null, ''); diff --git a/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql b/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql index 195a46c4a..5db798848 100644 --- a/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql +++ b/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql @@ -1690,15 +1690,15 @@ INSERT sys_menu VALUES (122, N'租户套餐管理', 6, 2, N'tenantPackage', N'sy GO INSERT sys_menu VALUES (123, N'客户端管理', 1, 11, N'client', N'system/client/index', N'', 1, 0, N'C', N'0', N'0', N'system:client:list', N'international', 103, 1, getdate(), NULL, NULL, N'客户端管理菜单') GO -INSERT sys_menu VALUES (116, N'修改生成配置', 3, 2, N'gen-edit/index/:tableId', N'tool/gen/editTable', N'', 1, 1, N'C', N'1', N'0', N'tool:gen:edit', N'#', 103, 1, getdate(), null, null, N''); +INSERT sys_menu VALUES (116, N'修改生成配置', 3, 2, N'gen-edit/index/:tableId', N'tool/gen/editTable', N'', 1, 1, N'C', N'1', N'0', N'tool:gen:edit', N'#', 103, 1, getdate(), null, null, N'/tool/gen'); GO -INSERT sys_menu VALUES (130, N'分配用户', 1, 2, N'role-auth/user/:roleId', N'system/role/authUser', N'', 1, 1, N'C', N'1', N'0', N'system:role:edit', N'#', 103, 1, getdate(), null, null, N''); +INSERT sys_menu VALUES (130, N'分配用户', 1, 2, N'role-auth/user/:roleId', N'system/role/authUser', N'', 1, 1, N'C', N'1', N'0', N'system:role:edit', N'#', 103, 1, getdate(), null, null, N'/system/role'); GO -INSERT sys_menu VALUES (131, N'分配角色', 1, 1, N'user-auth/role/:userId', N'system/user/authRole', N'', 1, 1, N'C', N'1', N'0', N'system:user:edit', N'#', 103, 1, getdate(), null, null, N''); +INSERT sys_menu VALUES (131, N'分配角色', 1, 1, N'user-auth/role/:userId', N'system/user/authRole', N'', 1, 1, N'C', N'1', N'0', N'system:user:edit', N'#', 103, 1, getdate(), null, null, N'/system/user'); GO -INSERT sys_menu VALUES (132, N'字典数据', 1, 6, N'dict-data/index/:dictId', N'system/dict/data', N'', 1, 1, N'C', N'1', N'0', N'system:dict:list', N'#', 103, 1, getdate(), null, null, N''); +INSERT sys_menu VALUES (132, N'字典数据', 1, 6, N'dict-data/index/:dictId', N'system/dict/data', N'', 1, 1, N'C', N'1', N'0', N'system:dict:list', N'#', 103, 1, getdate(), null, null, N'/system/dict'); GO -INSERT sys_menu VALUES (133, N'文件配置管理', 1, 10, N'oss-config/index', N'system/oss/config', N'', 1, 1, N'C', N'1', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), null, null, N''); +INSERT sys_menu VALUES (133, N'文件配置管理', 1, 10, N'oss-config/index', N'system/oss/config', N'', 1, 1, N'C', N'1', N'0', N'system:ossConfig:list', N'#', 103, 1, getdate(), null, null, N'/system/oss'); GO INSERT sys_menu VALUES (117, N'Admin监控', 2, 5, N'Admin', N'monitor/admin/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:admin:list', N'dashboard', 103, 1, getdate(), NULL, NULL, N'Admin监控菜单'); diff --git a/script/sql/sqlserver/sqlserver_ry_workflow.sql b/script/sql/sqlserver/sqlserver_ry_workflow.sql index 6dc4004e5..f668c56ad 100644 --- a/script/sql/sqlserver/sqlserver_ry_workflow.sql +++ b/script/sql/sqlserver/sqlserver_ry_workflow.sql @@ -5,6 +5,7 @@ CREATE TABLE flow_definition ( id bigint NOT NULL, flow_code nvarchar(40) NOT NULL, flow_name nvarchar(100) NOT NULL, + model_value nvarchar(40) DEFAULT('CLASSICS') NOT NULL, category nvarchar(100) NULL, version nvarchar(20) NOT NULL, is_publish tinyint DEFAULT('0') NULL, @@ -46,6 +47,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'flow_name' GO +EXEC sp_addextendedproperty +'MS_Description', N'设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)', +'SCHEMA', N'dbo', +'TABLE', N'flow_definition', +'COLUMN', N'model_value' +GO + EXEC sp_addextendedproperty 'MS_Description', N'流程类别', 'SCHEMA', N'dbo', @@ -725,7 +733,7 @@ CREATE TABLE flow_his_task ( target_node_name nvarchar(100) NULL, approver nvarchar(40) NULL, cooperate_type tinyint DEFAULT('0') NULL, - collaborator nvarchar(40) NULL, + collaborator nvarchar(500) NULL, skip_type nvarchar(10) NOT NULL, flow_status nvarchar(20) NOT NULL, form_custom nchar(1) DEFAULT('N') NULL, @@ -1147,7 +1155,9 @@ CREATE TABLE flow_spel ( create_time DATETIME, update_by BIGINT, update_time DATETIME, - CONSTRAINT PK_flow_spel PRIMARY KEY (id) + CONSTRAINT PK__flow_spel__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id) + WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ON [PRIMARY] ); GO @@ -1253,22 +1263,118 @@ GO INSERT flow_spel VALUES (2, NULL, NULL, N'initiator', N'${initiator}', N'流程发起人', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE()); GO -INSERT sys_menu VALUES (N'11801', N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单'); +CREATE TABLE flow_instance_biz_ext ( + id BIGINT NOT NULL, + tenant_id VARCHAR(20) DEFAULT ('000000'), + create_dept BIGINT, + create_by BIGINT, + create_time DATETIME, + update_by BIGINT, + update_time DATETIME, + business_code VARCHAR(255), + business_title VARCHAR(1000), + del_flag CHAR(1) DEFAULT ('0'), + instance_id BIGINT, + business_id VARCHAR(255), + CONSTRAINT PK__fi_biz_ext__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id) + WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ON [PRIMARY] +); + +EXEC sp_addextendedproperty + 'MS_Description', N'流程实例业务扩展表', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext' GO -INSERT sys_menu VALUES (N'11802', N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N''); + +EXEC sp_addextendedproperty + 'MS_Description', N'主键id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'id' GO -INSERT sys_menu VALUES (N'11803', N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N''); + +EXEC sp_addextendedproperty + 'MS_Description', N'租户编号', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'tenant_id' GO -INSERT sys_menu VALUES (N'11804', N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); + +EXEC sp_addextendedproperty + 'MS_Description', N'创建部门', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_dept' GO -INSERT sys_menu VALUES (N'11805', N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N''); + +EXEC sp_addextendedproperty + 'MS_Description', N'创建者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_by' GO -INSERT sys_menu VALUES (N'11806', N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N''); + +EXEC sp_addextendedproperty + 'MS_Description', N'创建时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'update_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'update_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'删除标志', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'del_flag' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务编码', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_code' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务标题', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_title' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'流程实例Id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'instance_id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务Id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_id' GO CREATE TABLE test_leave ( id bigint NOT NULL, tenant_id nvarchar(20) DEFAULT('000000') NULL, + apply_code nvarchar(50) NOT NULL, leave_type nvarchar(255) NOT NULL, start_date datetime2(7) NOT NULL, end_date datetime2(7) NOT NULL, @@ -1301,6 +1407,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'tenant_id' GO +EXEC sp_addextendedproperty +'MS_Description', N'申请编号', +'SCHEMA', N'dbo', +'TABLE', N'test_leave', +'COLUMN', N'apply_code' +GO + EXEC sp_addextendedproperty 'MS_Description', N'请假类型', 'SCHEMA', N'dbo', @@ -1406,7 +1519,7 @@ INSERT sys_menu VALUES (11630, N'流程监控', 11616, 4, N'monitor', NULL, N'', GO INSERT sys_menu VALUES (11631, N'待办任务', 11630, 2, N'allTaskWaiting', N'workflow/task/allTaskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N''); GO -INSERT sys_menu VALUES (11700, N'流程设计', 11616, 5, N'design/index', N'workflow/processDefinition/design', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11700, N'流程设计', 11616, 5, N'design/index', N'workflow/processDefinition/design', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'/workflow/processDefinition'); GO INSERT sys_menu VALUES (11701, N'请假申请', 11616, 6, N'leaveEdit/index', N'workflow/leave/leaveEdit', N'', 1, 1, N'C', N'1', N'0', N'workflow:leave:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO @@ -1423,6 +1536,19 @@ GO INSERT sys_menu VALUES (11627, N'流程分类导出', 11622, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:export', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO +INSERT sys_menu VALUES (11801, N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单'); +GO +INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +GO +INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +GO +INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +GO +INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +GO +INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +GO + -- 请假测试相关按钮 INSERT sys_menu VALUES (11638, N'请假申请', 5, 1, N'leave', N'workflow/leave/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:leave:list', N'#', 103, 1, GETDATE(), NULL, NULL, N'请假申请菜单'); GO diff --git a/script/sql/update/oracle/update_5.4.1-5.5.0.sql b/script/sql/update/oracle/update_5.4.1-5.5.0.sql index e3236abc5..c4fe2a2f7 100644 --- a/script/sql/update/oracle/update_5.4.1-5.5.0.sql +++ b/script/sql/update/oracle/update_5.4.1-5.5.0.sql @@ -44,3 +44,54 @@ INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3 INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, ''); +ALTER TABLE flow_definition ADD model_value VARCHAR2(40) DEFAULT 'CLASSICS' NOT NULL; +COMMENT ON COLUMN flow_definition.model_value IS '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)'; + +UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike'); + +ALTER TABLE flow_his_task MODIFY (collaborator VARCHAR2(500) DEFAULT NULL NULL); +COMMENT ON COLUMN flow_his_task.collaborator IS '协作人'; + +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +CREATE TABLE flow_instance_biz_ext ( + id NUMBER(20), + tenant_id VARCHAR2(20) DEFAULT '000000', + create_dept NUMBER(20), + create_by NUMBER(20), + create_time TIMESTAMP, + update_by NUMBER(20), + update_time TIMESTAMP, + business_code VARCHAR2(255), + business_title VARCHAR2(1000), + del_flag CHAR(1) DEFAULT '0', + instance_id NUMBER(20), + business_id VARCHAR2(255) +); + +alter table flow_instance_biz_ext add constraint pk_fi_biz_ext primary key (id); + +COMMENT ON TABLE flow_instance_biz_ext IS '流程实例业务扩展表'; +COMMENT ON COLUMN flow_instance_biz_ext.id IS '主键id'; +COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '租户编号'; +COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '创建者'; +COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '创建时间'; +COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '更新者'; +COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '更新时间'; +COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '业务编码'; +COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '业务标题'; +COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '删除标志(0代表存在 1代表删除)'; +COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '流程实例Id'; +COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '业务Id'; + +ALTER TABLE test_leave ADD COLUMN apply_code VARCHAR2(50) NOT NULL; +COMMENT ON COLUMN test_leave.apply_code IS '申请编号'; + +update sys_menu set remark = '/tool/gen' where menu_id = 116; +update sys_menu set remark = '/system/role' where menu_id = 130; +update sys_menu set remark = '/system/user' where menu_id = 131; +update sys_menu set remark = '/system/dict' where menu_id = 132; +update sys_menu set remark = '/system/oss' where menu_id = 133; +update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700; diff --git a/script/sql/update/postgres/update_5.4.1-5.5.0.sql b/script/sql/update/postgres/update_5.4.1-5.5.0.sql index 6f3b86b95..46e960509 100644 --- a/script/sql/update/postgres/update_5.4.1-5.5.0.sql +++ b/script/sql/update/postgres/update_5.4.1-5.5.0.sql @@ -43,3 +43,57 @@ INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3 INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, ''); +ALTER TABLE flow_definition ADD COLUMN model_value VARCHAR(40) NOT NULL DEFAULT 'CLASSICS'; +COMMENT ON COLUMN flow_definition.model_value IS '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)'; + +UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike'); + +ALTER TABLE flow_his_task + ALTER COLUMN collaborator DROP NOT NULL, + ALTER COLUMN collaborator SET DEFAULT NULL, + ALTER COLUMN collaborator TYPE VARCHAR(500); +COMMENT ON COLUMN flow_his_task.collaborator IS '协作人'; + + +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +CREATE TABLE flow_instance_biz_ext ( + id int8, + tenant_id VARCHAR(20) DEFAULT '000000', + create_dept int8, + create_by int8, + create_time TIMESTAMP, + update_by int8, + update_time TIMESTAMP, + business_code VARCHAR(255), + business_title VARCHAR(1000), + del_flag CHAR(1) DEFAULT '0', + instance_id int8, + business_id VARCHAR(255), + PRIMARY KEY (id) +); + +COMMENT ON TABLE flow_instance_biz_ext IS '流程实例业务扩展表'; +COMMENT ON COLUMN flow_instance_biz_ext.id IS '主键id'; +COMMENT ON COLUMN flow_instance_biz_ext.tenant_id IS '租户编号'; +COMMENT ON COLUMN flow_instance_biz_ext.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_instance_biz_ext.create_by IS '创建者'; +COMMENT ON COLUMN flow_instance_biz_ext.create_time IS '创建时间'; +COMMENT ON COLUMN flow_instance_biz_ext.update_by IS '更新者'; +COMMENT ON COLUMN flow_instance_biz_ext.update_time IS '更新时间'; +COMMENT ON COLUMN flow_instance_biz_ext.business_code IS '业务编码'; +COMMENT ON COLUMN flow_instance_biz_ext.business_title IS '业务标题'; +COMMENT ON COLUMN flow_instance_biz_ext.del_flag IS '删除标志(0代表存在 1代表删除)'; +COMMENT ON COLUMN flow_instance_biz_ext.instance_id IS '流程实例Id'; +COMMENT ON COLUMN flow_instance_biz_ext.business_id IS '业务Id'; + +ALTER TABLE test_leave ADD COLUMN apply_code VARCHAR(50) NOT NULL; +COMMENT ON COLUMN test_leave.apply_code IS '申请编号'; + +update sys_menu set remark = '/tool/gen' where menu_id = 116; +update sys_menu set remark = '/system/role' where menu_id = 130; +update sys_menu set remark = '/system/user' where menu_id = 131; +update sys_menu set remark = '/system/dict' where menu_id = 132; +update sys_menu set remark = '/system/oss' where menu_id = 133; +update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700; diff --git a/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql b/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql index 2005df4f8..c2cc1b5b3 100644 --- a/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql +++ b/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql @@ -121,16 +121,174 @@ GO INSERT flow_spel VALUES (2, NULL, NULL, N'initiator', N'${initiator}', N'流程发起人', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE()); GO -INSERT sys_menu VALUES (N'11801', N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单'); +INSERT sys_menu VALUES (11801, N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单'); GO -INSERT sys_menu VALUES (N'11802', N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11802, N'流程spel达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO -INSERT sys_menu VALUES (N'11803', N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11803, N'流程spel达式定义新增', N'11801', 2, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:add', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO -INSERT sys_menu VALUES (N'11804', N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11804, N'流程spel达式定义修改', N'11801', 3, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO -INSERT sys_menu VALUES (N'11805', N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11805, N'流程spel达式定义删除', N'11801', 4, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO -INSERT sys_menu VALUES (N'11806', N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N''); +INSERT sys_menu VALUES (11806, N'流程spel达式定义导出', N'11801', 5, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:export', N'#', 103, 1, GETDATE(), NULL, NULL, N''); GO +ALTER TABLE flow_definition ADD model_value VARCHAR(40) NOT NULL CONSTRAINT DF_flow_definition_model_value DEFAULT 'CLASSICS'; +GO +EXEC sp_addextendedproperty +'MS_Description', N'设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)', +'SCHEMA', N'dbo', +'TABLE', N'flow_definition', +'COLUMN', N'model_value' +GO +UPDATE flow_skip SET skip_condition = REPLACE(skip_condition, 'notNike', 'notLike'); +GO +ALTER TABLE flow_his_task ALTER COLUMN collaborator VARCHAR(500) NULL; +GO +IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description', +'SCHEMA', N'dbo', +'TABLE', N'flow_his_task', +'COLUMN', N'collaborator')) > 0) + EXEC sp_updateextendedproperty +'MS_Description', N'协作人', +'SCHEMA', N'dbo', +'TABLE', N'flow_his_task', +'COLUMN', N'collaborator' +ELSE + EXEC sp_addextendedproperty +'MS_Description', N'协作人', +'SCHEMA', N'dbo', +'TABLE', N'flow_his_task', +'COLUMN', N'collaborator' +GO + +CREATE TABLE flow_instance_biz_ext ( + id BIGINT NOT NULL, + tenant_id VARCHAR(20) DEFAULT ('000000'), + create_dept BIGINT, + create_by BIGINT, + create_time DATETIME, + update_by BIGINT, + update_time DATETIME, + business_code VARCHAR(255), + business_title VARCHAR(1000), + del_flag CHAR(1) DEFAULT ('0'), + instance_id BIGINT, + business_id VARCHAR(255), + CONSTRAINT PK__fi_biz_ext__D54EE9B4AE98B9C1 PRIMARY KEY CLUSTERED (id) + WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ON [PRIMARY] +); + +EXEC sp_addextendedproperty + 'MS_Description', N'流程实例业务扩展表', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'主键id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'租户编号', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'tenant_id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建部门', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_dept' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'create_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'update_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'update_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'删除标志', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'del_flag' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务编码', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_code' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务标题', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_title' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'流程实例Id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'instance_id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'业务Id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_instance_biz_ext', + 'COLUMN', N'business_id' +GO + +ALTER TABLE test_leave ADD apply_code nvarchar(50) NOT NULL; +GO +EXEC sp_addextendedproperty +'MS_Description', N'申请编号', +'SCHEMA', N'dbo', +'TABLE', N'test_leave', +'COLUMN', N'apply_code' +GO + +update sys_menu set remark = N'/tool/gen' where menu_id = 116; +GO +update sys_menu set remark = N'/system/role' where menu_id = 130; +GO +update sys_menu set remark = N'/system/user' where menu_id = 131; +GO +update sys_menu set remark = N'/system/dict' where menu_id = 132; +GO +update sys_menu set remark = N'/system/oss' where menu_id = 133; +GO +update sys_menu set remark = N'/workflow/processDefinition' where menu_id = 11700; +GO diff --git a/script/sql/update/update_5.4.1-5.5.0.sql b/script/sql/update/update_5.4.1-5.5.0.sql index 5154d0146..980d098a9 100644 --- a/script/sql/update/update_5.4.1-5.5.0.sql +++ b/script/sql/update/update_5.4.1-5.5.0.sql @@ -28,3 +28,40 @@ INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3 INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, ''); INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, sysdate(), NULL, NULL, ''); +ALTER TABLE `flow_definition` + ADD COLUMN `model_value` varchar(40) NOT NULL DEFAULT 'CLASSICS' COMMENT '设计器模式(CLASSICS经典模式 MIMIC仿钉钉模式)' AFTER `flow_name`; + +update flow_skip set skip_condition = REPLACE(skip_condition,'notNike','notLike'); + +ALTER TABLE `flow_his_task` + MODIFY COLUMN `collaborator` varchar(500) NULL DEFAULT NULL COMMENT '协作人' AFTER `cooperate_type`; + +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- + +create table flow_instance_biz_ext ( + id bigint not null comment '主键id', + tenant_id varchar(20) default '000000' null comment '租户编号', + create_dept bigint null comment '创建部门', + create_by bigint null comment '创建者', + create_time datetime null comment '创建时间', + update_by bigint null comment '更新者', + update_time datetime null comment '更新时间', + business_code varchar(255) null comment '业务编码', + business_title varchar(1000) null comment '业务标题', + del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)', + instance_id bigint null comment '流程实例Id', + business_id varchar(255) null comment '业务Id', + PRIMARY KEY (id) +) ENGINE = InnoDB COMMENT '流程实例业务扩展表'; + +ALTER TABLE `test_leave` + ADD COLUMN `apply_code` varchar(50) NOT NULL COMMENT '申请编号' AFTER `tenant_id`; + +update sys_menu set remark = '/tool/gen' where menu_id = 116; +update sys_menu set remark = '/system/role' where menu_id = 130; +update sys_menu set remark = '/system/user' where menu_id = 131; +update sys_menu set remark = '/system/dict' where menu_id = 132; +update sys_menu set remark = '/system/oss' where menu_id = 133; +update sys_menu set remark = '/workflow/processDefinition' where menu_id = 11700;