diff --git a/README.md b/README.md index bd86ca5e6..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) ## 赞助商 @@ -37,7 +37,10 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc
**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/**
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11
-[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group) +aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong
+Ruoyi-Plus-Uniapp - https://ruoyi.plus
+ +[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group) # 本框架与RuoYi的功能差异 diff --git a/pom.xml b/pom.xml index f8dab42db..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.5.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/AuthController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java index 7fbc57f95..89b9ab63c 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java @@ -21,6 +21,8 @@ import org.dromara.common.core.domain.model.SocialLoginBody; import org.dromara.common.core.utils.*; import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.ratelimiter.annotation.RateLimiter; +import org.dromara.common.ratelimiter.enums.LimitType; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.social.config.properties.SocialLoginConfigProperties; import org.dromara.common.social.config.properties.SocialProperties; @@ -198,6 +200,7 @@ public class AuthController { * * @return 租户列表 */ + @RateLimiter(time = 60, count = 20, limitType = LimitType.IP) @GetMapping("/tenant/list") public R tenantList(HttpServletRequest request) throws Exception { // 返回对象 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 03cb2f987..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: # 国际化资源文件路径 @@ -127,6 +134,7 @@ tenant: - sys_user_role - sys_client - sys_oss_config + - flow_spel # MyBatisPlus配置 # https://baomidou.com/config/ @@ -189,13 +197,6 @@ springdoc: name: Lion Li email: crazylionli@163.com url: https://gitee.com/dromara/RuoYi-Vue-Plus - components: - # 鉴权方式配置 - security-schemes: - apiKey: - type: APIKEY - in: HEADER - name: ${sa-token.token-name} #这里定义了两个分组,可定义多个,也可以不定义 group-configs: - group: 1.演示模块 @@ -213,20 +214,10 @@ springdoc: xss: # 过滤开关 enabled: true - # 排除链接(多个用逗号分隔) + # 排除链接 excludeUrls: - /system/notice -# 全局线程池相关配置 -# 如使用JDK21请直接使用虚拟线程 不要开启此配置 -thread-pool: - # 是否开启线程池 - enabled: false - # 队列最大长度 - queueCapacity: 128 - # 线程池维护线程所允许的空闲时间 - keepAliveSeconds: 300 - --- # 分布式锁 lock4j 全局配置 lock4j: # 获取分布式锁超时时间,默认为 3000 毫秒 @@ -266,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-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties index cce11c85d..f2777f77b 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间 user.password.not.blank=用户密码不能为空 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.not.valid=* 5-50个字符 +user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符 user.email.not.valid=邮箱格式错误 user.email.not.blank=邮箱不能为空 user.phonenumber.not.blank=用户手机号不能为空 diff --git a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties index f948c4ab8..306a48f6a 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties @@ -17,6 +17,7 @@ user.username.length.valid=Account length must be between {min} and {max} charac user.password.not.blank=Password cannot be empty user.password.length.valid=Password length must be between {min} and {max} characters user.password.not.valid=* 5-50 characters +user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character user.email.not.valid=Mailbox format error user.email.not.blank=Mailbox cannot be blank user.phonenumber.not.blank=Phone number cannot be blank diff --git a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties index cce11c85d..f2777f77b 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间 user.password.not.blank=用户密码不能为空 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.not.valid=* 5-50个字符 +user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符 user.email.not.valid=邮箱格式错误 user.email.not.blank=邮箱不能为空 user.phonenumber.not.blank=用户手机号不能为空 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 85893e1dc..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 @@ -52,17 +52,17 @@ public class TaskAssigneeDTO implements Serializable { */ public static List convertToHandlerList( List sourceList, - Function storageId, + Function storageId, Function handlerCode, Function handlerName, - Function groupName, + Function groupName, Function createTimeMapper) { return sourceList.stream() .map(item -> new TaskHandler( - String.valueOf(storageId.apply(item)), + 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/domain/event/ProcessEvent.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java index 7b15b85ae..d830dbe03 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java @@ -27,6 +27,11 @@ public class ProcessEvent implements Serializable { */ private String flowCode; + /** + * 实例id + */ + private Long instanceId; + /** * 业务id */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java index a9bcf7e93..0984727ac 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java @@ -4,6 +4,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.util.Map; /** * 流程任务监听 @@ -46,6 +47,11 @@ public class ProcessTaskEvent implements Serializable { */ private Long taskId; + /** + * 实例id + */ + private Long instanceId; + /** * 业务id */ @@ -56,4 +62,9 @@ public class ProcessTaskEvent implements Serializable { */ private String status; + /** + * 办理参数 + */ + private Map params; + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java index 36e33b24b..143c95907 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/PasswordLoginBody.java @@ -26,6 +26,7 @@ public class PasswordLoginBody extends LoginBody { */ @NotBlank(message = "{user.password.not.blank}") @Length(min = 5, max = 30, message = "{user.password.length.valid}") +// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}") private String password; } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java index cced26b2b..3f232492a 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/RegisterBody.java @@ -26,8 +26,12 @@ public class RegisterBody extends LoginBody { */ @NotBlank(message = "{user.password.not.blank}") @Length(min = 5, max = 30, message = "{user.password.length.valid}") +// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}") private String password; + /** + * 用户类型 + */ private String userType; } 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/DeptService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java index f93d1778a..725718ae8 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DeptService.java @@ -3,6 +3,7 @@ package org.dromara.common.core.service; import org.dromara.common.core.domain.dto.DeptDTO; import java.util.List; +import java.util.Map; /** * 通用 部门服务 @@ -34,4 +35,12 @@ public interface DeptService { */ List selectDeptsByList(); + /** + * 根据部门 ID 列表查询部门名称映射关系 + * + * @param deptIds 部门 ID 列表 + * @return Map,其中 key 为部门 ID,value 为对应的部门名称 + */ + Map selectDeptNamesByIds(List deptIds); + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java index 41d4e8308..58c68d676 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/PostService.java @@ -1,5 +1,8 @@ package org.dromara.common.core.service; +import java.util.List; +import java.util.Map; + /** * 通用 岗位服务 * @@ -7,4 +10,12 @@ package org.dromara.common.core.service; */ public interface PostService { + /** + * 根据岗位 ID 列表查询岗位名称映射关系 + * + * @param postIds 岗位 ID 列表 + * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 + */ + Map selectPostNamesByIds(List postIds); + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java index ba62c82ae..d2805b7e0 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/RoleService.java @@ -1,5 +1,8 @@ package org.dromara.common.core.service; +import java.util.List; +import java.util.Map; + /** * 通用 角色服务 * @@ -7,4 +10,12 @@ package org.dromara.common.core.service; */ public interface RoleService { + /** + * 根据角色 ID 列表查询角色名称映射关系 + * + * @param roleIds 角色 ID 列表 + * @return Map,其中 key 为角色 ID,value 为对应的角色名称 + */ + Map selectRoleNamesByIds(List roleIds); + } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java index 4903c3860..eefeef011 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java @@ -100,28 +100,4 @@ public interface UserService { */ Map selectUserNamesByIds(List userIds); - /** - * 根据角色 ID 列表查询角色名称映射关系 - * - * @param roleIds 角色 ID 列表 - * @return Map,其中 key 为角色 ID,value 为对应的角色名称 - */ - Map selectRoleNamesByIds(List roleIds); - - /** - * 根据部门 ID 列表查询部门名称映射关系 - * - * @param deptIds 部门 ID 列表 - * @return Map,其中 key 为部门 ID,value 为对应的部门名称 - */ - Map selectDeptNamesByIds(List deptIds); - - /** - * 根据岗位 ID 列表查询岗位名称映射关系 - * - * @param postIds 岗位 ID 列表 - * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 - */ - Map selectPostNamesByIds(List postIds); - } 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..f9e53a508 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 @@ -7,7 +7,6 @@ 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; 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-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java index c199015c0..35b6ce9ea 100644 --- a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java +++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java @@ -54,14 +54,15 @@ public class SpringDocConfig { openApi.externalDocs(properties.getExternalDocs()); openApi.tags(properties.getTags()); openApi.paths(properties.getPaths()); - openApi.components(properties.getComponents()); - Set keySet = properties.getComponents().getSecuritySchemes().keySet(); - List list = new ArrayList<>(); - SecurityRequirement securityRequirement = new SecurityRequirement(); - keySet.forEach(securityRequirement::addList); - list.add(securityRequirement); - openApi.security(list); - + if (properties.getComponents() != null) { + openApi.components(properties.getComponents()); + Set keySet = properties.getComponents().getSecuritySchemes().keySet(); + List list = new ArrayList<>(); + SecurityRequirement securityRequirement = new SecurityRequirement(); + keySet.forEach(securityRequirement::addList); + list.add(securityRequirement); + openApi.security(list); + } return openApi; } 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-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-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java index 8ab2719e1..d68be2251 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java @@ -27,9 +27,7 @@ import org.springframework.http.HttpMethod; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; -import java.util.Collection; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; /** * 操作日志记录处理 @@ -176,14 +174,28 @@ public class LogAspect { if (ArrayUtil.isEmpty(paramsArray)) { return params.toString(); } + String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES); for (Object o : paramsArray) { if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { - String str = JsonUtils.toJsonString(o); - Dict dict = JsonUtils.parseMap(str); - if (MapUtil.isNotEmpty(dict)) { - MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); - MapUtil.removeAny(dict, excludeParamNames); - str = JsonUtils.toJsonString(dict); + String str = ""; + if (o instanceof List list) { + List list1 = new ArrayList<>(); + for (Object obj : list) { + String str1 = JsonUtils.toJsonString(obj); + Dict dict = JsonUtils.parseMap(str1); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, exclude); + list1.add(dict); + } + } + str = JsonUtils.toJsonString(list1); + } else { + str = JsonUtils.toJsonString(o); + Dict dict = JsonUtils.parseMap(str); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, exclude); + str = JsonUtils.toJsonString(dict); + } } params.add(str); } 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/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java index 24557edb6..0d777f4e0 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.toolkit.Db; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.dromara.common.core.utils.MapstructUtils; @@ -130,7 +132,7 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的单个VO对象 */ default V selectVoById(Serializable id) { - return selectVoById(id, this.currentVoClass()); + return this.selectVoById(id, this.currentVoClass()); } /** @@ -156,7 +158,7 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的VO对象列表 */ default List selectVoByIds(Collection idList) { - return selectVoByIds(idList, this.currentVoClass()); + return this.selectVoByIds(idList, this.currentVoClass()); } /** @@ -182,7 +184,7 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的VO对象列表 */ default List selectVoByMap(Map map) { - return selectVoByMap(map, this.currentVoClass()); + return this.selectVoByMap(map, this.currentVoClass()); } /** @@ -208,7 +210,7 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的单个VO对象 */ default V selectVoOne(Wrapper wrapper) { - return selectVoOne(wrapper, this.currentVoClass()); + return this.selectVoOne(wrapper, this.currentVoClass()); } /** @@ -219,11 +221,12 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的单个VO对象 */ default V selectVoOne(Wrapper wrapper, boolean throwEx) { - return selectVoOne(wrapper, this.currentVoClass(), throwEx); + return this.selectVoOne(wrapper, this.currentVoClass(), throwEx); } /** - * 根据条件查询单个VO对象,并指定返回的VO对象的类型 + * 根据条件查询单个VO对象,并指定返回的VO对象的类型(自动拼接 limit 1) + * 注意不要再自己添加 limit 1 做限制了 * * @param wrapper 查询条件Wrapper * @param voClass 返回的VO对象的Class对象 @@ -231,11 +234,12 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回 */ default C selectVoOne(Wrapper wrapper, Class voClass) { - return selectVoOne(wrapper, voClass, true); + return this.selectVoOne(wrapper, voClass, true); } /** - * 根据条件查询单个实体对象,并将其转换为指定的VO对象 + * 根据条件查询单个实体对象,并将其转换为指定的VO对象(自动拼接 limit 1) + * 注意不要再自己添加 limit 1 做限制了 * * @param wrapper 查询条件Wrapper * @param voClass 要转换的VO类的Class对象 @@ -251,13 +255,33 @@ public interface BaseMapperPlus extends BaseMapper { return MapstructUtils.convert(obj, voClass); } + /** + * 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数) + * 注意不要再自己添加 limit 1 做限制了 + *

+ * 注意: + * 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效 + * 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录 + *

+ * + * @param queryWrapper 查询条件(可为 null) + * @param throwEx 是否抛出异常(已弃用,此参数不再生效) + * @return 单条记录或无数据时返回 null + */ + @Override + default T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper, boolean throwEx) { + // 强制分页查询(LIMIT 1),确保最多返回 1 条记录 + List list = this.selectList(new Page<>(1, 1), queryWrapper); + return CollUtil.isEmpty(list) ? null : list.get(0); + } + /** * 查询所有VO对象列表 * * @return 查询到的VO对象列表 */ default List selectVoList() { - return selectVoList(new QueryWrapper<>(), this.currentVoClass()); + return this.selectVoList(new QueryWrapper<>(), this.currentVoClass()); } /** @@ -294,7 +318,7 @@ public interface BaseMapperPlus extends BaseMapper { * @return 查询到的VO对象分页列表 */ default

> P selectVoPage(IPage page, Wrapper wrapper) { - return selectVoPage(page, wrapper, this.currentVoClass()); + return this.selectVoPage(page, wrapper, this.currentVoClass()); } /** 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 de5119e91..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 { /** @@ -177,12 +180,12 @@ public class OssClient { // 创建异步请求体(length如果为空会报错) BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder() .contentLength(length) - .subscribeTimeout(Duration.ofSeconds(30)) + .subscribeTimeout(Duration.ofSeconds(120)) .build(); // 使用 transferManager 进行上传 Upload upload = transferManager.upload( - x -> x.requestBody(body) + x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create()) .putObjectRequest( y -> y.bucket(properties.getBucketName()) .key(key) @@ -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 657dbbc07..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,7 +1,7 @@ package org.dromara.common.redis.utils; +import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.common.core.utils.SpringUtils; @@ -10,6 +10,10 @@ import org.redisson.api.RIdGenerator; import org.redisson.api.RedissonClient; import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; /** * 发号器工具类 @@ -23,12 +27,12 @@ public class SequenceUtils { /** * 默认初始值 */ - public static final Long DEFAULT_INIT_VALUE = 1L; + public static final long DEFAULT_INIT_VALUE = 1L; /** * 默认步长 */ - public static final Long DEFAULT_STEP_VALUE = 1L; + public static final long DEFAULT_STEP_VALUE = 1L; /** * 默认过期时间-天 @@ -40,6 +44,11 @@ public class SequenceUtils { */ public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1); + /** + * 默认最小ID容量位数 - 6位数(即至少可以生成的ID为999999个) + */ + public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6; + /** * 获取Redisson客户端实例 */ @@ -54,14 +63,11 @@ public class SequenceUtils { * @param stepValue ID步长 * @return ID生成器 */ - private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) { - if (initValue == null || initValue <= 0) { - initValue = DEFAULT_INIT_VALUE; - } - if (stepValue == null || stepValue <= 0) { - stepValue = DEFAULT_STEP_VALUE; - } + public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) { RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key); + // 初始值和步长不能小于等于0 + initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue; + stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue; // 设置初始值和步长 idGenerator.tryInit(initValue, stepValue); // 设置过期时间 @@ -69,6 +75,17 @@ public class SequenceUtils { return idGenerator; } + /** + * 获取ID生成器 + * + * @param key 业务key + * @param expireTime 过期时间 + * @return ID生成器 + */ + public static RIdGenerator getIdGenerator(String key, Duration expireTime) { + return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); + } + /** * 获取指定业务key的唯一id * @@ -78,10 +95,21 @@ public class SequenceUtils { * @param stepValue ID步长 * @return 唯一id */ - public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) { + public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) { return getIdGenerator(key, expireTime, initValue, stepValue).nextId(); } + /** + * 获取指定业务key的唯一id (ID初始值=1,ID步长=1) + * + * @param key 业务key + * @param expireTime 过期时间 + * @return 唯一id + */ + public static long getNextId(String key, Duration expireTime) { + return getIdGenerator(key, expireTime).nextId(); + } + /** * 获取指定业务key的唯一id字符串 * @@ -91,19 +119,8 @@ public class SequenceUtils { * @param stepValue ID步长 * @return 唯一id */ - public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) { - return String.valueOf(nextId(key, expireTime, initValue, stepValue)); - } - - /** - * 获取指定业务key的唯一id (ID初始值=1,ID步长=1) - * - * @param key 业务key - * @param expireTime 过期时间 - * @return 唯一id - */ - public static long nextId(String key, Duration expireTime) { - return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); + public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) { + return Convert.toStr(getNextId(key, expireTime, initValue, stepValue)); } /** @@ -113,8 +130,8 @@ public class SequenceUtils { * @param expireTime 过期时间 * @return 唯一id */ - public static String nextIdStr(String key, Duration expireTime) { - return String.valueOf(nextId(key, expireTime)); + public static String getNextIdString(String key, Duration expireTime) { + return Convert.toStr(getNextId(key, expireTime)); } /** @@ -125,56 +142,210 @@ public class SequenceUtils { * @param width 位数,不足左补0 * @return 补零后的唯一id字符串 */ - public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) { - return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0'); + public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) { + return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0'); } /** - * 获取 yyyyMMdd 开头的唯一id + * 获取 yyyyMMdd 格式的唯一id * * @return 唯一id + * @deprecated 请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)},确保不同业务的ID连续性 */ - public static String nextIdDate() { - return nextIdDate(""); + @Deprecated + public static String getDateId() { + return getDateId(""); } /** - * 获取 prefix + yyyyMMdd 开头的唯一id + * 获取 prefix + yyyyMMdd 格式的唯一id * * @param prefix 业务前缀 * @return 唯一id */ - public static String nextIdDate(String prefix) { - // 前缀+日期 构建 prefixKey - String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER)); - // 获取下一个id - long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); - // 返回完整id - return StringUtils.format("{}{}", prefixKey, nextId); + public static String getDateId(String prefix) { + return getDateId(prefix, true); } /** - * 获取 yyyyMMddHHmmss 开头的唯一id + * 获取 prefix + yyyyMMdd 格式的唯一id * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 * @return 唯一id */ - public static String nextIdDateTime() { - return nextIdDateTime(""); + public static String getDateId(String prefix, boolean isWithPrefix) { + return getDateId(prefix, isWithPrefix, -1); } /** - * 获取 prefix + yyyyMMddHHmmss 开头的唯一id + * 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}) + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getPaddedDateId(String prefix, boolean isWithPrefix) { + return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) { + return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now()); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) { + return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); + } + + /** + * 获取 prefix + yyyyMMdd 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @param initValue ID初始值 + * @param stepValue ID步长 + * @return 唯一id + */ + public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) { + return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue); + } + + /** + * 获取 yyyyMMddHHmmss 格式的唯一id + * + * @return 唯一id + * @deprecated 请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)},确保不同业务的ID连续性 + */ + @Deprecated + public static String getDateTimeId() { + return getDateTimeId("", false); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id * * @param prefix 业务前缀 * @return 唯一id */ - public static String nextIdDateTime(String prefix) { - // 前缀+日期时间 构建 prefixKey - String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER)); - // 获取下一个id - long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId(); - // 返回完整id - return StringUtils.format("{}{}", prefixKey, nextId); + public static String getDateTimeId(String prefix) { + return getDateTimeId(prefix, true); } + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix) { + return getDateTimeId(prefix, isWithPrefix, -1); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}) + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @return 唯一id + */ + public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) { + return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) { + return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now()); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param time 时间 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) { + return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE); + } + + /** + * 获取 prefix + yyyyMMddHHmmss 格式的唯一id + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param initValue ID初始值 + * @param stepValue ID步长 + * @return 唯一id + */ + public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) { + return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue); + } + + /** + * 获取指定业务key的指定时间格式的ID + * + * @param prefix 业务前缀 + * @param isWithPrefix id是否携带业务前缀 + * @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位) + * @param temporalAccessor 时间访问器 + * @param timeFormatter 时间格式 + * @param expireTime 过期时间 + * @param initValue ID初始值 + * @param stepValue ID步长 + * @return 唯一id + */ + private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) { + // 时间前缀 + String timePrefix = timeFormatter.format(temporalAccessor); + // 业务前缀 + 时间前缀 构建 prefixKey + String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix); + + // 获取id,例 -> 1 + String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue); + + // minIdCapacityBits 大于0,且 nextId 的长度小于 minIdCapacityBits,则左补0 + if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) { + nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0'); + } + + // 是否携带业务前缀 + if (isWithPrefix) { + // 例 -> P202507031 + // 其中 P 为业务前缀,202507031 为 yyyyMMdd 格式时间, 1 为nextId + return StringUtils.format("{}{}", prefixKey, nextId); + } + // 例 -> 202507031 + // 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId + return StringUtils.format("{}{}", timePrefix, nextId); + } } 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 46c61c4c8..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 @@ -53,11 +53,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { if (timeout == NEVER_EXPIRE) { RedisUtils.setCacheObject(key, value); } else { - if (RedisUtils.hasKey(key)) { - RedisUtils.setCacheObject(key, value, true); - } else { - RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); - } + RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); } CAFFEINE.invalidate(key); } @@ -78,7 +74,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { */ @Override public void delete(String key) { - RedisUtils.deleteObject(key); + if (RedisUtils.deleteObject(key)) { + CAFFEINE.invalidate(key); + } } /** @@ -134,11 +132,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { if (timeout == NEVER_EXPIRE) { RedisUtils.setCacheObject(key, object); } else { - if (RedisUtils.hasKey(key)) { - RedisUtils.setCacheObject(key, object, true); - } else { - RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); - } + RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); } CAFFEINE.invalidate(key); } @@ -159,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-sse/src/main/java/org/dromara/common/sse/controller/SseController.java b/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java index 412834cfb..f77b5b585 100644 --- a/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java +++ b/ruoyi-common/ruoyi-common-sse/src/main/java/org/dromara/common/sse/controller/SseController.java @@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.sse.core.SseEmitterManager; -import org.dromara.common.sse.dto.SseMessageDto; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; @@ -14,8 +13,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.util.List; - /** * SSE 控制器 * @@ -33,7 +30,9 @@ public class SseController implements DisposableBean { */ @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter connect() { - StpUtil.checkLogin(); + if (!StpUtil.isLogin()) { + return null; + } String tokenValue = StpUtil.getTokenValue(); Long userId = LoginHelper.getUserId(); return sseEmitterManager.connect(userId, tokenValue); @@ -51,31 +50,32 @@ public class SseController implements DisposableBean { return R.ok(); } - /** - * 向特定用户发送消息 - * - * @param userId 目标用户的 ID - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/send") - public R send(Long userId, String msg) { - SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(List.of(userId)); - dto.setMessage(msg); - sseEmitterManager.publishMessage(dto); - return R.ok(); - } - - /** - * 向所有用户发送消息 - * - * @param msg 要发送的消息内容 - */ - @GetMapping(value = "${sse.path}/sendAll") - public R send(String msg) { - sseEmitterManager.publishAll(msg); - return R.ok(); - } + // 以下为demo仅供参考 禁止使用 请在业务逻辑中使用工具发送而不是用接口发送 +// /** +// * 向特定用户发送消息 +// * +// * @param userId 目标用户的 ID +// * @param msg 要发送的消息内容 +// */ +// @GetMapping(value = "${sse.path}/send") +// public R send(Long userId, String msg) { +// SseMessageDto dto = new SseMessageDto(); +// dto.setUserIds(List.of(userId)); +// dto.setMessage(msg); +// sseEmitterManager.publishMessage(dto); +// return R.ok(); +// } +// +// /** +// * 向所有用户发送消息 +// * +// * @param msg 要发送的消息内容 +// */ +// @GetMapping(value = "${sse.path}/sendAll") +// public R send(String msg) { +// sseEmitterManager.publishAll(msg); +// return R.ok(); +// } /** * 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误 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 3458cc965..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 @@ -10,7 +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.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; /** * admin 监控 安全配置 @@ -32,14 +32,14 @@ public class SecurityConfig { 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( - new AntPathRequestMatcher(adminContextPath + "/assets/**"), - new AntPathRequestMatcher(adminContextPath + "/login") + mvc.matcher(adminContextPath + "/assets/**"), + mvc.matcher(adminContextPath + "/login") ).permitAll() .anyRequest().authenticated()) .formLogin((formLogin) -> 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 aaf474895..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 @@ -16,15 +16,26 @@ spring: --- # snail-job 服务端配置 snail-job: - # 拉取重试数据的每批次的大小 - retry-pull-page-size: 1000 - # 拉取重试数据的每批次的大小 - job-pull-page-size: 1000 - # 服务器端口 + # 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`) + server-host: + # 服务端netty的端口号 server-port: 17888 - # 日志保存时间(单位: day) + # 合并日志默认保存天数 + merge-Log-days: 1 + # 合并日志默认的条数 + merge-Log-num: 500 + # 配置每批次拉取重试数据的大小 + retry-pull-page-size: 100 + # 配置日志保存时间(单位:天) log-storage: 7 - rpc-type: grpc + # bucket的总数量 + bucket-total: 128 + # Dashboard 任务容错天数 + summary-day: 7 + # 配置负载均衡周期时间 + load-balance-cycle-time: 10 + # 重试任务拉取的并行度 + retry-max-pull-parallel: 2 --- # 监控中心配置 spring.boot.admin.client: 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 aaf474895..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 @@ -16,15 +16,26 @@ spring: --- # snail-job 服务端配置 snail-job: - # 拉取重试数据的每批次的大小 - retry-pull-page-size: 1000 - # 拉取重试数据的每批次的大小 - job-pull-page-size: 1000 - # 服务器端口 + # 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`) + server-host: + # 服务端netty的端口号 server-port: 17888 - # 日志保存时间(单位: day) + # 合并日志默认保存天数 + merge-Log-days: 1 + # 合并日志默认的条数 + merge-Log-num: 500 + # 配置每批次拉取重试数据的大小 + retry-pull-page-size: 100 + # 配置日志保存时间(单位:天) log-storage: 7 - rpc-type: grpc + # bucket的总数量 + bucket-total: 128 + # Dashboard 任务容错天数 + summary-day: 7 + # 配置负载均衡周期时间 + load-balance-cycle-time: 10 + # 重试任务拉取的并行度 + retry-max-pull-parallel: 2 --- # 监控中心配置 spring.boot.admin.client: 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/MailController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java similarity index 98% rename from ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java rename to ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java index 6fc5a17ba..48365a350 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailController.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/MailSendController.java @@ -21,7 +21,7 @@ import java.util.Arrays; @RequiredArgsConstructor @RestController @RequestMapping("/demo/mail") -public class MailController { +public class MailSendController { /** * 发送邮件 diff --git a/ruoyi-modules/ruoyi-generator/pom.xml b/ruoyi-modules/ruoyi-generator/pom.xml index 49060292f..b93b8371e 100644 --- a/ruoyi-modules/ruoyi-generator/pom.xml +++ b/ruoyi-modules/ruoyi-generator/pom.xml @@ -42,6 +42,11 @@ ruoyi-common-log + + org.dromara + ruoyi-common-idempotent + + org.apache.velocity diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java index 64ea78bac..99626a208 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/controller/GenController.java @@ -3,9 +3,11 @@ package org.dromara.generator.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; +import com.baomidou.lock.annotation.Lock4j; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -50,6 +52,7 @@ public class GenController extends BaseController { * * @param tableId 表ID */ + @RepeatSubmit() @SaCheckPermission("tool:gen:query") @GetMapping(value = "/{tableId}") public R> getInfo(@PathVariable Long tableId) { @@ -91,6 +94,7 @@ public class GenController extends BaseController { */ @SaCheckPermission("tool:gen:import") @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @RepeatSubmit() @PostMapping("/importTable") public R importTableSave(String tables, String dataName) { String[] tableNames = Convert.toStrArray(tables); @@ -105,6 +109,7 @@ public class GenController extends BaseController { */ @SaCheckPermission("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R editSave(@Validated @RequestBody GenTable genTable) { genTableService.validateEdit(genTable); @@ -170,6 +175,7 @@ public class GenController extends BaseController { */ @SaCheckPermission("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @Lock4j @GetMapping("/synchDb/{tableId}") public R synchDb(@PathVariable("tableId") Long tableId) { genTableService.synchDb(tableId); diff --git a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java index 3572e6f64..c4c79b0ba 100644 --- a/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java +++ b/ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java @@ -302,7 +302,7 @@ public class GenTableServiceImpl implements IGenTableService { tableColumn.setColumnComment(column.getComment()); tableColumn.setColumnType(column.getOriginType().toLowerCase()); tableColumn.setSort(column.getPosition()); - tableColumn.setIsRequired(column.isNullable() ? "1" : "0"); + tableColumn.setIsRequired(column.isNullable() ? "0" : "1"); tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0"); tableColumns.add(tableColumn); }); 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/monitor/SysLogininforController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java index 98ac2d58f..378717003 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLogininforController.java @@ -1,11 +1,13 @@ package org.dromara.system.controller.monitor; import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.lock.annotation.Lock4j; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.CacheConstants; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -69,6 +71,7 @@ public class SysLogininforController extends BaseController { */ @SaCheckPermission("monitor:logininfor:remove") @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @Lock4j @DeleteMapping("/clean") public R clean() { logininforService.cleanLogininfor(); @@ -77,6 +80,7 @@ public class SysLogininforController extends BaseController { @SaCheckPermission("monitor:logininfor:unlock") @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @RepeatSubmit() @GetMapping("/unlock/{userName}") public R unlock(@PathVariable("userName") String userName) { String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName; diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java index 575aba6b1..d2e4c9e6c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java @@ -1,21 +1,22 @@ package org.dromara.system.controller.monitor; import cn.dev33.satoken.annotation.SaCheckPermission; -import org.dromara.common.log.annotation.Log; -import org.dromara.common.web.core.BaseController; -import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.lock.annotation.Lock4j; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; -import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.log.enums.BusinessType; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; import org.dromara.system.domain.bo.SysOperLogBo; import org.dromara.system.domain.vo.SysOperLogVo; import org.dromara.system.service.ISysOperLogService; -import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.servlet.http.HttpServletResponse; import java.util.List; /** @@ -67,6 +68,7 @@ public class SysOperlogController extends BaseController { */ @Log(title = "操作日志", businessType = BusinessType.CLEAN) @SaCheckPermission("monitor:operlog:remove") + @Lock4j @DeleteMapping("/clean") public R clean() { operLogService.cleanOperLog(); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java index 1cab23251..0e6f171db 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java @@ -10,6 +10,7 @@ import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.dto.UserOnlineDTO; import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -81,6 +82,7 @@ public class SysUserOnlineController extends BaseController { */ @SaCheckPermission("monitor:online:forceLogout") @Log(title = "在线用户", businessType = BusinessType.FORCE) + @RepeatSubmit() @DeleteMapping("/{tokenId}") public R forceLogout(@PathVariable String tokenId) { try { @@ -114,6 +116,7 @@ public class SysUserOnlineController extends BaseController { * @param tokenId token值 */ @Log(title = "在线设备", businessType = BusinessType.FORCE) + @RepeatSubmit() @DeleteMapping("/myself/{tokenId}") public R remove(@PathVariable("tokenId") String tokenId) { try { diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java index e5d9242be..2be7858f7 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -78,6 +79,7 @@ public class SysConfigController extends BaseController { */ @SaCheckPermission("system:config:add") @Log(title = "参数管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysConfigBo config) { if (!configService.checkConfigKeyUnique(config)) { @@ -92,6 +94,7 @@ public class SysConfigController extends BaseController { */ @SaCheckPermission("system:config:edit") @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysConfigBo config) { if (!configService.checkConfigKeyUnique(config)) { @@ -106,6 +109,7 @@ public class SysConfigController extends BaseController { */ @SaCheckPermission("system:config:edit") @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/updateByKey") public R updateByKey(@RequestBody SysConfigBo config) { configService.updateConfig(config); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java index 447c3368f..3acb947bc 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.domain.R; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.web.core.BaseController; @@ -73,6 +74,7 @@ public class SysDeptController extends BaseController { */ @SaCheckPermission("system:dept:add") @Log(title = "部门管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysDeptBo dept) { if (!deptService.checkDeptNameUnique(dept)) { @@ -86,6 +88,7 @@ public class SysDeptController extends BaseController { */ @SaCheckPermission("system:dept:edit") @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysDeptBo dept) { Long deptId = dept.getDeptId(); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java index 6235f8d86..ae6d5808c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -86,6 +87,7 @@ public class SysDictDataController extends BaseController { */ @SaCheckPermission("system:dict:add") @Log(title = "字典数据", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysDictDataBo dict) { if (!dictDataService.checkDictDataUnique(dict)) { @@ -100,6 +102,7 @@ public class SysDictDataController extends BaseController { */ @SaCheckPermission("system:dict:edit") @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysDictDataBo dict) { if (!dictDataService.checkDictDataUnique(dict)) { diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java index 4b91e1ba5..d88e6ec6f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java @@ -1,10 +1,12 @@ package org.dromara.system.controller.system; import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.lock.annotation.Lock4j; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -68,6 +70,7 @@ public class SysDictTypeController extends BaseController { */ @SaCheckPermission("system:dict:add") @Log(title = "字典类型", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysDictTypeBo dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { @@ -82,6 +85,7 @@ public class SysDictTypeController extends BaseController { */ @SaCheckPermission("system:dict:edit") @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysDictTypeBo dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { @@ -109,6 +113,7 @@ public class SysDictTypeController extends BaseController { */ @SaCheckPermission("system:dict:remove") @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @Lock4j @DeleteMapping("/refreshCache") public R refreshCache() { dictTypeService.resetDictCache(); 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 6e682d89f..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 @@ -9,6 +9,7 @@ import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.TenantConstants; import org.dromara.common.core.domain.R; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.satoken.utils.LoginHelper; @@ -129,6 +130,7 @@ public class SysMenuController extends BaseController { @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckPermission("system:menu:add") @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysMenuBo menu) { if (!menuService.checkMenuNameUnique(menu)) { @@ -145,6 +147,7 @@ public class SysMenuController extends BaseController { @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckPermission("system:menu:edit") @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysMenuBo menu) { if (!menuService.checkMenuNameUnique(menu)) { @@ -176,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/SysNoticeController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java index 5d65137b1..86abb9a8c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysNoticeController.java @@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.core.service.DictService; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -55,6 +56,7 @@ public class SysNoticeController extends BaseController { */ @SaCheckPermission("system:notice:add") @Log(title = "通知公告", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysNoticeBo notice) { int rows = noticeService.insertNotice(notice); @@ -71,6 +73,7 @@ public class SysNoticeController extends BaseController { */ @SaCheckPermission("system:notice:edit") @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysNoticeBo notice) { return toAjax(noticeService.updateNotice(notice)); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java index 24ddaff29..236998041 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssConfigController.java @@ -98,6 +98,7 @@ public class SysOssConfigController extends BaseController { */ @SaCheckPermission("system:ossConfig:edit") @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysOssConfigBo bo) { return toAjax(ossConfigService.updateOssConfigStatus(bo)); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java index 5333a4aef..d39b6f90f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -19,6 +20,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -70,6 +72,7 @@ public class SysPostController extends BaseController { */ @SaCheckPermission("system:post:add") @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysPostBo post) { if (!postService.checkPostNameUnique(post)) { @@ -85,6 +88,7 @@ public class SysPostController extends BaseController { */ @SaCheckPermission("system:post:edit") @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysPostBo post) { if (!postService.checkPostNameUnique(post)) { @@ -107,7 +111,7 @@ public class SysPostController extends BaseController { @Log(title = "岗位管理", businessType = BusinessType.DELETE) @DeleteMapping("/{postIds}") public R remove(@PathVariable Long[] postIds) { - return toAjax(postService.deletePostByIds(postIds)); + return toAjax(postService.deletePostByIds(Arrays.asList(postIds))); } /** 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 e6afa3c07..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 @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -77,6 +78,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:add") @Log(title = "角色管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysRoleBo role) { roleService.checkRoleAllowed(role); @@ -94,6 +96,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysRoleBo role) { roleService.checkRoleAllowed(role); @@ -116,6 +119,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/dataScope") public R dataScope(@RequestBody SysRoleBo role) { roleService.checkRoleAllowed(role); @@ -128,6 +132,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysRoleBo role) { roleService.checkRoleAllowed(role); @@ -144,7 +149,7 @@ public class SysRoleController extends BaseController { @Log(title = "角色管理", businessType = BusinessType.DELETE) @DeleteMapping("/{roleIds}") public R remove(@PathVariable Long[] roleIds) { - return toAjax(roleService.deleteRoleByIds(roleIds)); + return toAjax(roleService.deleteRoleByIds(List.of(roleIds))); } /** @@ -181,6 +186,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) + @RepeatSubmit() @PutMapping("/authUser/cancel") public R cancelAuthUser(@RequestBody SysUserRole userRole) { return toAjax(roleService.deleteAuthUser(userRole)); @@ -194,6 +200,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) + @RepeatSubmit() @PutMapping("/authUser/cancelAll") public R cancelAuthUserAll(Long roleId, Long[] userIds) { return toAjax(roleService.deleteAuthUsers(roleId, userIds)); @@ -207,6 +214,7 @@ public class SysRoleController extends BaseController { */ @SaCheckPermission("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) + @RepeatSubmit() @PutMapping("/authUser/selectAll") public R selectAuthUserAll(Long roleId, Long[] userIds) { roleService.checkRoleDataScope(roleId); @@ -227,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/controller/system/SysTenantController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java index 66c1b7ddf..f79aa6d87 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantController.java @@ -118,6 +118,7 @@ public class SysTenantController extends BaseController { @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckPermission("system:tenant:edit") @Log(title = "租户管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysTenantBo bo) { tenantService.checkTenantAllowed(bo.getTenantId()); @@ -170,6 +171,7 @@ public class SysTenantController extends BaseController { @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckPermission("system:tenant:edit") @Log(title = "租户管理", businessType = BusinessType.UPDATE) + @Lock4j @GetMapping("/syncTenantPackage") public R syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId, @NotNull(message = "套餐ID不能为空") Long packageId) { @@ -181,6 +183,7 @@ public class SysTenantController extends BaseController { */ @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @Log(title = "租户管理", businessType = BusinessType.INSERT) + @Lock4j @GetMapping("/syncTenantDict") public R syncTenantDict() { if (!TenantHelper.isEnable()) { diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java index 4bfe597cf..0f724bec4 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysTenantPackageController.java @@ -121,6 +121,7 @@ public class SysTenantPackageController extends BaseController { @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckPermission("system:tenantPackage:edit") @Log(title = "租户套餐", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysTenantPackageBo bo) { return toAjax(tenantPackageService.updatePackageStatus(bo)); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java index 774f26a3e..2847fc299 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java @@ -16,10 +16,12 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.excel.core.ExcelResult; import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.tenant.helper.TenantHelper; import org.dromara.common.web.core.BaseController; @@ -110,7 +112,8 @@ public class SysUserController extends BaseController { // 超级管理员 如果重新加载用户信息需清除动态租户 TenantHelper.clearDynamic(); } - SysUserVo user = userService.selectUserById(loginUser.getUserId()); + + SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId())); if (ObjectUtil.isNull(user)) { return R.fail("没有权限访问用户数据!"); } @@ -154,6 +157,7 @@ public class SysUserController extends BaseController { */ @SaCheckPermission("system:user:add") @Log(title = "用户管理", businessType = BusinessType.INSERT) + @RepeatSubmit() @PostMapping public R add(@Validated @RequestBody SysUserBo user) { deptService.checkDeptDataScope(user.getDeptId()); @@ -178,6 +182,7 @@ public class SysUserController extends BaseController { */ @SaCheckPermission("system:user:edit") @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping public R edit(@Validated @RequestBody SysUserBo user) { userService.checkUserAllowed(user.getUserId()); @@ -227,6 +232,7 @@ public class SysUserController extends BaseController { @ApiEncrypt @SaCheckPermission("system:user:resetPwd") @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/resetPwd") public R resetPwd(@RequestBody SysUserBo user) { userService.checkUserAllowed(user.getUserId()); @@ -240,6 +246,7 @@ public class SysUserController extends BaseController { */ @SaCheckPermission("system:user:edit") @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysUserBo user) { userService.checkUserAllowed(user.getUserId()); @@ -272,6 +279,7 @@ public class SysUserController extends BaseController { */ @SaCheckPermission("system:user:edit") @Log(title = "用户管理", businessType = BusinessType.GRANT) + @RepeatSubmit() @PutMapping("/authRole") public R insertAuthRole(Long userId, Long[] roleIds) { userService.checkUserDataScope(userId); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java new file mode 100644 index 000000000..15a95e760 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysOssExt.java @@ -0,0 +1,75 @@ +package org.dromara.system.domain; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 附件扩展字段对象(存储在 SysOss.ext1 的 JSON 字符串中) + * + * @author AprilWind + */ +@Data +public class SysOssExt implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 所属业务类型(如 avatar、report、contract) + */ + private String bizType; + + /** + * 文件大小(单位:字节) + */ + private Long fileSize; + + /** + * 文件类型(MIME类型,如 image/png) + */ + private String contentType; + + /** + * 来源标识(如 userUpload、systemImport) + */ + private String source; + + /** + * 上传 IP 地址,便于审计和追踪 + */ + private String uploadIp; + + /** + * 附件说明或备注 + */ + private String remark; + + /** + * 附件标签,如 ["图片", "证件"] + */ + private List tags; + + /** + * 业务绑定ID(如某业务记录ID) + */ + private String refId; + + /** + * 绑定业务类型 + */ + private String refType; + + /** + * 是否为临时文件,用于区分正式或待清理 + */ + private Boolean isTemp; + + /** + * 文件MD5值(可用于去重或校验) + */ + private String md5; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java index e3ac6428f..3757f9a26 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysTenantBo.java @@ -1,17 +1,17 @@ package org.dromara.system.domain.bo; -import org.dromara.common.core.validate.AddGroup; -import org.dromara.common.core.validate.EditGroup; -import org.dromara.system.domain.SysTenant; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import jakarta.validation.constraints.*; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.system.domain.SysTenant; import java.util.Date; -import org.dromara.common.mybatis.core.domain.BaseEntity; - /** * 租户业务对象 sys_tenant * @@ -62,6 +62,7 @@ public class SysTenantBo extends BaseEntity { * 密码(创建系统用户) */ @NotBlank(message = "密码不能为空", groups = { AddGroup.class }) +// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}", groups = { AddGroup.class }) private String password; /** 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/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 e29942058..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 @@ -3,7 +3,7 @@ package org.dromara.system.mapper; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import org.apache.ibatis.annotations.Param; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.mybatis.annotation.DataColumn; import org.dromara.common.mybatis.annotation.DataPermission; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; @@ -20,6 +20,42 @@ import java.util.List; */ public interface SysDeptMapper extends BaseMapperPlus { + /** + * 构建角色对应的部门 SQL 查询语句 + * + *

该 SQL 用于查询某个角色关联的所有部门 ID,常用于数据权限控制

+ * + * @param roleId 角色ID + * @return 查询部门ID的 SQL 语句字符串 + */ + default String buildDeptByRoleSql(Long roleId) { + return """ + 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); + } + + /** + * 构建 SQL 查询,用于获取当前角色拥有的部门中所有的父部门ID + * + *

+ * 该 SQL 用于 deptCheckStrictly 场景下,排除非叶子节点(父节点)用。 + *

+ * + * @param roleId 角色ID + * @return SQL 语句字符串,查询角色下部门的所有父部门ID + */ + default String buildParentDeptByRoleSql(Long roleId) { + return """ + select parent_id from sys_dept where dept_id in ( + 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); + } + /** * 查询部门管理数据 * @@ -72,6 +108,19 @@ public interface SysDeptMapper extends BaseMapperPlus { .apply(DataBaseHelper.findInSet(parentId, "ancestors"))); } + /** + * 查询某个部门及其所有子部门ID(含自身) + * + * @param parentId 父部门ID + * @return 部门ID集合 + */ + default List selectDeptAndChildById(Long parentId) { + List deptList = this.selectListByParentId(parentId); + List deptIds = StreamUtils.toList(deptList, SysDept::getDeptId); + deptIds.add(parentId); + return deptIds; + } + /** * 根据角色ID查询部门树信息 * @@ -79,6 +128,16 @@ public interface SysDeptMapper extends BaseMapperPlus { * @param deptCheckStrictly 部门树选择项是否关联显示 * @return 选中部门列表 */ - List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + default List selectDeptListByRoleId(Long roleId, boolean deptCheckStrictly) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(SysDept::getDeptId) + .inSql(SysDept::getDeptId, this.buildDeptByRoleSql(roleId)) + .orderByAsc(SysDept::getParentId) + .orderByAsc(SysDept::getOrderNum); + if (deptCheckStrictly) { + wrapper.notInSql(SysDept::getDeptId, this.buildParentDeptByRoleSql(roleId)); + } + return this.selectObjs(wrapper); + } } 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 205413b3f..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 @@ -1,15 +1,16 @@ package org.dromara.system.mapper; -import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Constants; -import org.apache.ibatis.annotations.Param; import org.dromara.common.core.constant.SystemConstants; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.system.domain.SysMenu; import org.dromara.system.domain.vo.SysMenuVo; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * 菜单表 数据层 @@ -19,12 +20,64 @@ import java.util.List; public interface SysMenuMapper extends BaseMapperPlus { /** - * 根据用户查询系统菜单列表 + * 构建用户权限菜单 SQL * - * @param queryWrapper 查询条件 - * @return 菜单列表 + *

+ * 查询用户所属角色所拥有的菜单权限,用于权限判断、菜单加载等场景 + *

+ * + * @param userId 用户ID + * @return SQL 字符串,用于 inSql 条件 */ - List selectMenuListByUserId(@Param(Constants.WRAPPER) Wrapper queryWrapper); + default String buildMenuByUserSql(Long userId) { + return """ + select menu_id from sys_role_menu where role_id in ( + 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); + } + + /** + * 构建角色对应的菜单ID SQL 子查询 + * + *

+ * 用于根据角色ID查询其所拥有的菜单权限(用于权限标识、菜单显示等场景) + * 通常配合 inSql 使用 + *

+ * + * @param roleId 角色ID + * @return 查询菜单ID的 SQL 子查询字符串 + */ + default String buildMenuByRoleSql(Long roleId) { + return """ + 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); + } + + /** + * 构建角色所关联菜单的父菜单ID查询 SQL + * + *

+ * 用于配合菜单勾选树结构的 {@code menuCheckStrictly} 模式,过滤掉非叶子节点(父菜单), + * 只返回角色实际勾选的末级菜单 + *

+ * + * @param roleId 角色ID + * @return SQL 语句字符串(查询菜单的父菜单ID) + */ + default String buildParentMenuByRoleSql(Long roleId) { + return """ + select parent_id from sys_menu where menu_id in ( + 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); + } /** * 根据用户ID查询权限 @@ -32,7 +85,15 @@ public interface SysMenuMapper extends BaseMapperPlus { * @param userId 用户ID * @return 权限列表 */ - List selectMenuPermsByUserId(Long userId); + default Set selectMenuPermsByUserId(Long userId) { + List list = this.selectObjs( + new LambdaQueryWrapper() + .select(SysMenu::getPerms) + .inSql(SysMenu::getMenuId, this.buildMenuByUserSql(userId)) + .isNotNull(SysMenu::getPerms) + ); + return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank)); + } /** * 根据角色ID查询权限 @@ -40,7 +101,15 @@ public interface SysMenuMapper extends BaseMapperPlus { * @param roleId 角色ID * @return 权限列表 */ - List selectMenuPermsByRoleId(Long roleId); + default Set selectMenuPermsByRoleId(Long roleId) { + List list = this.selectObjs( + new LambdaQueryWrapper() + .select(SysMenu::getPerms) + .inSql(SysMenu::getMenuId, this.buildMenuByRoleSql(roleId)) + .isNotNull(SysMenu::getPerms) + ); + return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank)); + } /** * 根据用户ID查询菜单 @@ -56,14 +125,6 @@ public interface SysMenuMapper extends BaseMapperPlus { return this.selectList(lqw); } - /** - * 根据用户ID查询菜单 - * - * @param userId 用户ID - * @return 菜单列表 - */ - List selectMenuTreeByUserId(Long userId); - /** * 根据角色ID查询菜单树信息 * @@ -71,6 +132,16 @@ public interface SysMenuMapper extends BaseMapperPlus { * @param menuCheckStrictly 菜单树选择项是否关联显示 * @return 选中菜单列表 */ - List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + default List selectMenuListByRoleId(Long roleId, boolean menuCheckStrictly) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(SysMenu::getMenuId) + .inSql(SysMenu::getMenuId, buildMenuByRoleSql(roleId)) + .orderByAsc(SysMenu::getParentId) + .orderByAsc(SysMenu::getOrderNum); + if (menuCheckStrictly) { + wrapper.notInSql(SysMenu::getMenuId, this.buildParentMenuByRoleSql(roleId)); + } + return this.selectObjs(wrapper); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java index dfe9f2c2f..d8d03157a 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysPostMapper.java @@ -1,6 +1,7 @@ package org.dromara.system.mapper; import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.dromara.common.mybatis.annotation.DataColumn; import org.dromara.common.mybatis.annotation.DataPermission; @@ -33,11 +34,42 @@ public interface SysPostMapper extends BaseMapperPlus { } /** - * 查询用户所属岗位组 + * 查询岗位列表 + * + * @param queryWrapper 查询条件 + * @return 岗位信息列表 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "create_by") + }) + default List selectPostList(Wrapper queryWrapper) { + return this.selectVoList(queryWrapper); + } + + /** + * 根据岗位ID集合查询岗位数量 + * + * @param postIds 岗位ID列表 + * @return 匹配的岗位数量 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "create_by") + }) + default long selectPostCount(List postIds) { + return this.selectCount(new LambdaQueryWrapper().in(SysPost::getPostId, postIds)); + } + + /** + * 根据用户ID查询其关联的岗位列表 * * @param userId 用户ID - * @return 结果 + * @return 岗位信息列表 */ - List selectPostsByUserId(Long userId); + default List selectPostsByUserId(Long userId) { + return this.selectVoList(new LambdaQueryWrapper() + .inSql(SysPost::getPostId, "select post_id from sys_user_post where user_id = " + userId)); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java index 9cb1ea509..920780563 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysRoleMapper.java @@ -1,6 +1,7 @@ package org.dromara.system.mapper; import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Param; @@ -19,6 +20,18 @@ import java.util.List; */ public interface SysRoleMapper extends BaseMapperPlus { + /** + * 构建根据用户ID查询角色ID的SQL子查询 + * + * @param userId 用户ID + * @return 查询用户对应角色ID的SQL语句字符串 + */ + default String buildRoleByUserSql(Long userId) { + return """ + select role_id from sys_user_role where user_id = %d + """.formatted(userId); + } + /** * 分页查询角色列表 * @@ -27,22 +40,40 @@ public interface SysRoleMapper extends BaseMapperPlus { * @return 包含角色信息的分页结果 */ @DataPermission({ - @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "r.create_by") + @DataColumn(key = "deptName", value = "create_dept"), + @DataColumn(key = "userName", value = "create_by") }) - Page selectPageRoleList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + default Page selectPageRoleList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper) { + return this.selectVoPage(page, queryWrapper); + } /** - * 根据条件分页查询角色数据 + * 根据条件查询角色数据 * * @param queryWrapper 查询条件 * @return 角色数据集合信息 */ @DataPermission({ - @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "r.create_by") + @DataColumn(key = "deptName", value = "create_dept"), + @DataColumn(key = "userName", value = "create_by") }) - List selectRoleList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + default List selectRoleList(@Param(Constants.WRAPPER) Wrapper queryWrapper) { + return this.selectVoList(queryWrapper); + } + + /** + * 根据角色ID集合查询角色数量 + * + * @param roleIds 角色ID列表 + * @return 匹配的角色数量 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "create_dept"), + @DataColumn(key = "userName", value = "create_by") + }) + default long selectRoleCount(List roleIds) { + return this.selectCount(new LambdaQueryWrapper().in(SysRole::getRoleId, roleIds)); + } /** * 根据角色ID查询角色信息 @@ -51,10 +82,12 @@ public interface SysRoleMapper extends BaseMapperPlus { * @return 对应的角色信息 */ @DataPermission({ - @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "r.create_by") + @DataColumn(key = "deptName", value = "create_dept"), + @DataColumn(key = "userName", value = "create_by") }) - SysRoleVo selectRoleById(Long roleId); + default SysRoleVo selectRoleById(Long roleId) { + return this.selectVoById(roleId); + } /** * 根据用户ID查询角色 @@ -62,14 +95,11 @@ public interface SysRoleMapper extends BaseMapperPlus { * @param userId 用户ID * @return 角色列表 */ - List selectRolePermissionByUserId(Long userId); - - /** - * 根据用户ID查询角色 - * - * @param userId 用户ID - * @return 角色列表 - */ - List selectRolesByUserId(Long userId); + default List selectRolesByUserId(Long userId) { + return this.selectVoList(new LambdaQueryWrapper() + .select(SysRole::getRoleId, SysRole::getRoleName, SysRole::getRoleKey, + SysRole::getRoleSort, SysRole::getDataScope, SysRole::getStatus) + .inSql(SysRole::getRoleId, this.buildRoleByUserSql(userId))); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java index ddea2e2ac..2cfd3db98 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java @@ -30,7 +30,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") + @DataColumn(key = "userName", value = "create_by") }) default Page selectPageUserList(Page page, Wrapper queryWrapper) { return this.selectVoPage(page, queryWrapper); @@ -44,7 +44,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") + @DataColumn(key = "userName", value = "create_by") }) default List selectUserList(Wrapper queryWrapper) { return this.selectVoList(queryWrapper); @@ -58,7 +58,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "u.user_id") + @DataColumn(key = "userName", value = "u.create_by") }) List selectUserExportList(@Param(Constants.WRAPPER) Wrapper queryWrapper); @@ -71,7 +71,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "u.user_id") + @DataColumn(key = "userName", value = "u.create_by") }) Page selectAllocatedList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); @@ -83,7 +83,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "d.dept_id"), - @DataColumn(key = "userName", value = "u.user_id") + @DataColumn(key = "userName", value = "u.create_by") }) Page selectUnallocatedList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); @@ -95,7 +95,7 @@ public interface SysUserMapper extends BaseMapperPlus { */ @DataPermission({ @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") + @DataColumn(key = "userName", value = "create_by") }) default long countUserById(Long userId) { return this.selectCount(new LambdaQueryWrapper().eq(SysUser::getUserId, userId)); @@ -111,7 +111,7 @@ public interface SysUserMapper extends BaseMapperPlus { @Override @DataPermission({ @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") + @DataColumn(key = "userName", value = "create_by") }) int update(@Param(Constants.ENTITY) SysUser user, @Param(Constants.WRAPPER) Wrapper updateWrapper); @@ -124,7 +124,7 @@ public interface SysUserMapper extends BaseMapperPlus { @Override @DataPermission({ @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") + @DataColumn(key = "userName", value = "create_by") }) int updateById(@Param(Constants.ENTITY) SysUser user); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java index 83403480e..4c1c8b9df 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysUserRoleMapper.java @@ -1,5 +1,6 @@ package org.dromara.system.mapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.system.domain.SysUserRole; @@ -18,6 +19,10 @@ public interface SysUserRoleMapper extends BaseMapperPlus selectUserIdsByRoleId(Long roleId); + default List selectUserIdsByRoleId(Long roleId) { + return this.selectObjs(new LambdaQueryWrapper() + .select(SysUserRole::getUserId).eq(SysUserRole::getRoleId, roleId) + ); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java index 60863b8d3..a101643e8 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysConfigService.java @@ -14,7 +14,13 @@ import java.util.List; */ public interface ISysConfigService { - + /** + * 分页查询参数配置列表 + * + * @param config 查询条件 + * @param pageQuery 分页参数 + * @return 参数配置分页列表 + */ TableDataInfo selectPageConfigList(SysConfigBo config, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java index 2ff49bc77..fdabd3170 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictDataService.java @@ -14,7 +14,13 @@ import java.util.List; */ public interface ISysDictDataService { - + /** + * 分页查询字典数据列表 + * + * @param dictData 查询条件 + * @param pageQuery 分页参数 + * @return 字典数据分页列表 + */ TableDataInfo selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java index c2704db09..e3a04def2 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDictTypeService.java @@ -15,7 +15,13 @@ import java.util.List; */ public interface ISysDictTypeService { - + /** + * 分页查询字典类型列表 + * + * @param dictType 查询条件 + * @param pageQuery 分页参数 + * @return 字典类型分页列表 + */ TableDataInfo selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java index 6b3b7a6b7..1bc1ea9ff 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysLogininforService.java @@ -14,7 +14,13 @@ import java.util.List; */ public interface ISysLogininforService { - + /** + * 分页查询登录日志列表 + * + * @param logininfor 查询条件 + * @param pageQuery 分页参数 + * @return 登录日志分页列表 + */ TableDataInfo selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java index 8ec999d01..8482cd0ce 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysNoticeService.java @@ -14,7 +14,13 @@ import java.util.List; */ public interface ISysNoticeService { - + /** + * 分页查询通知公告列表 + * + * @param notice 查询条件 + * @param pageQuery 分页参数 + * @return 通知公告分页列表 + */ TableDataInfo selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java index 957351023..e8b340c73 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysOperLogService.java @@ -14,6 +14,13 @@ import java.util.List; */ public interface ISysOperLogService { + /** + * 分页查询操作日志列表 + * + * @param operLog 查询条件 + * @param pageQuery 分页参数 + * @return 操作日志分页列表 + */ TableDataInfo selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery); /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java index a760d497e..1caaab282 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysPostService.java @@ -14,7 +14,13 @@ import java.util.List; */ public interface ISysPostService { - + /** + * 分页查询岗位列表 + * + * @param post 查询条件 + * @param pageQuery 分页参数 + * @return 岗位分页列表 + */ TableDataInfo selectPagePostList(SysPostBo post, PageQuery pageQuery); /** @@ -110,7 +116,7 @@ public interface ISysPostService { * @param postIds 需要删除的岗位ID * @return 结果 */ - int deletePostByIds(Long[] postIds); + int deletePostByIds(List postIds); /** * 新增保存岗位信息 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java index cec4bf775..abbfc798a 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysRoleService.java @@ -16,11 +16,17 @@ import java.util.Set; */ public interface ISysRoleService { - + /** + * 分页查询角色列表 + * + * @param role 查询条件 + * @param pageQuery 分页参数 + * @return 角色分页列表 + */ TableDataInfo selectPageRoleList(SysRoleBo role, PageQuery pageQuery); /** - * 根据条件分页查询角色数据 + * 根据条件查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 @@ -112,6 +118,13 @@ public interface ISysRoleService { */ void checkRoleDataScope(Long roleId); + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色ID列表(支持传单个ID) + */ + void checkRoleDataScope(List roleIds); + /** * 通过角色ID查询角色使用数量 * @@ -167,7 +180,7 @@ public interface ISysRoleService { * @param roleIds 需要删除的角色ID * @return 结果 */ - int deleteRoleByIds(Long[] roleIds); + int deleteRoleByIds(List roleIds); /** * 取消授权用户角色 @@ -195,8 +208,29 @@ public interface ISysRoleService { */ int insertAuthUsers(Long roleId, Long[] userIds); + /** + * 根据角色ID清除该角色关联的所有在线用户的登录状态(踢出在线用户) + * + *

+ * 先判断角色是否绑定用户,若无绑定则直接返回 + * 然后遍历当前所有在线Token,查找拥有该角色的用户并强制登出 + * 注意:在线用户量过大时,操作可能导致 Redis 阻塞,需谨慎调用 + *

+ * + * @param roleId 角色ID + */ void cleanOnlineUserByRole(Long roleId); + /** + * 根据用户ID列表清除对应在线用户的登录状态(踢出指定用户) + * + *

+ * 遍历当前所有在线Token,匹配用户ID列表中的用户,强制登出 + * 注意:在线用户量过大时,操作可能导致 Redis 阻塞,需谨慎调用 + *

+ * + * @param userIds 需要清除的用户ID列表 + */ void cleanOnlineUser(List userIds); } 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 ed65ae45c..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 @@ -41,6 +41,13 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService { private final SysConfigMapper baseMapper; + /** + * 分页查询参数配置列表 + * + * @param config 查询条件 + * @param pageQuery 分页参数 + * @return 参数配置分页列表 + */ @Override public TableDataInfo selectPageConfigList(SysConfigBo config, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(config); @@ -165,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/SysDataScopeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java index 12a507203..e30080212 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java @@ -7,7 +7,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.CacheNames; import org.dromara.common.core.utils.StreamUtils; -import org.dromara.system.domain.SysDept; import org.dromara.system.domain.SysRoleDept; import org.dromara.system.mapper.SysDeptMapper; import org.dromara.system.mapper.SysRoleDeptMapper; @@ -66,13 +65,8 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService { if (ObjectUtil.isNull(deptId)) { return "-1"; } - List deptList = deptMapper.selectListByParentId(deptId); - List ids = StreamUtils.toList(deptList, SysDept::getDeptId); - ids.add(deptId); - if (CollUtil.isNotEmpty(ids)) { - return StreamUtils.join(ids, Convert::toStr); - } - return "-1"; + List deptIds = deptMapper.selectDeptAndChildById(deptId); + return CollUtil.isNotEmpty(deptIds) ? StreamUtils.join(deptIds, Convert::toStr) : "-1"; } } 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 fde4b875b..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 @@ -36,10 +36,7 @@ import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; /** * 部门管理 服务实现 @@ -110,10 +107,7 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService { if (ObjectUtil.isNotNull(bo.getBelongDeptId())) { //部门树搜索 lqw.and(x -> { - Long parentId = bo.getBelongDeptId(); - List deptList = baseMapper.selectListByParentId(parentId); - List deptIds = StreamUtils.toList(deptList, SysDept::getDeptId); - deptIds.add(parentId); + List deptIds = baseMapper.selectDeptAndChildById(bo.getBelongDeptId()); x.in(SysDept::getDeptId, deptIds); }); } @@ -198,7 +192,7 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService { list.add(vo.getDeptName()); } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } /** @@ -409,4 +403,24 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService { return baseMapper.deleteById(deptId); } + + /** + * 根据部门 ID 列表查询部门名称映射关系 + * + * @param deptIds 部门 ID 列表 + * @return Map,其中 key 为部门 ID,value 为对应的部门名称 + */ + @Override + public Map selectDeptNamesByIds(List deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return Collections.emptyMap(); + } + List list = baseMapper.selectList( + new LambdaQueryWrapper() + .select(SysDept::getDeptId, SysDept::getDeptName) + .in(SysDept::getDeptId, deptIds) + ); + return StreamUtils.toMap(list, SysDept::getDeptId, SysDept::getDeptName); + } + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java index baf1bba99..018f04338 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictDataServiceImpl.java @@ -33,6 +33,13 @@ public class SysDictDataServiceImpl implements ISysDictDataService { private final SysDictDataMapper baseMapper; + /** + * 分页查询字典数据列表 + * + * @param dictData 查询条件 + * @param pageQuery 分页参数 + * @return 字典数据分页列表 + */ @Override public TableDataInfo selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(dictData); 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 3491e887e..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 @@ -48,6 +48,13 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService private final SysDictTypeMapper baseMapper; private final SysDictDataMapper dictDataMapper; + /** + * 分页查询字典类型列表 + * + * @param dictType 查询条件 + * @param pageQuery 分页参数 + * @return 字典类型分页列表 + */ @Override public TableDataInfo selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(dictType); @@ -136,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/SysLogininforServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java index 72b497eca..c388d9919 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java @@ -108,6 +108,13 @@ public class SysLogininforServiceImpl implements ISysLogininforService { return "[" + msg.toString() + "]"; } + /** + * 分页查询登录日志列表 + * + * @param logininfor 查询条件 + * @param pageQuery 分页参数 + * @return 登录日志分页列表 + */ @Override public TableDataInfo selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery) { Map params = logininfor.getParams(); 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 10c3a9aeb..efe9029f8 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 @@ -5,8 +5,6 @@ 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; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.Constants; import org.dromara.common.core.constant.SystemConstants; @@ -67,29 +65,20 @@ public class SysMenuServiceImpl implements ISysMenuService { @Override public List selectMenuList(SysMenuBo menu, Long userId) { List menuList; - // 管理员显示所有菜单信息 - if (LoginHelper.isSuperAdmin(userId)) { - menuList = baseMapper.selectVoList(new LambdaQueryWrapper() - .like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName()) + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 管理员显示所有菜单信息 不是管理员 按用户id过滤菜单 + if (!LoginHelper.isSuperAdmin(userId)) { + // 通过用户id获取角色id 通过角色id获取菜单id 然后in菜单 + wrapper.inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId)); + } + menuList = baseMapper.selectVoList( + wrapper.like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName()) .eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible()) .eq(StringUtils.isNotBlank(menu.getStatus()), SysMenu::getStatus, menu.getStatus()) .eq(StringUtils.isNotBlank(menu.getMenuType()), SysMenu::getMenuType, menu.getMenuType()) .eq(ObjectUtil.isNotNull(menu.getParentId()), SysMenu::getParentId, menu.getParentId()) .orderByAsc(SysMenu::getParentId) .orderByAsc(SysMenu::getOrderNum)); - } else { - QueryWrapper wrapper = Wrappers.query(); - wrapper.inSql("r.role_id", "select role_id from sys_user_role where user_id = " + userId) - .like(StringUtils.isNotBlank(menu.getMenuName()), "m.menu_name", menu.getMenuName()) - .eq(StringUtils.isNotBlank(menu.getVisible()), "m.visible", menu.getVisible()) - .eq(StringUtils.isNotBlank(menu.getStatus()), "m.status", menu.getStatus()) - .eq(StringUtils.isNotBlank(menu.getMenuType()), "m.menu_type", menu.getMenuType()) - .eq(ObjectUtil.isNotNull(menu.getParentId()), "m.parent_id", menu.getParentId()) - .orderByAsc("m.parent_id") - .orderByAsc("m.order_num"); - List list = baseMapper.selectMenuListByUserId(wrapper); - menuList = MapstructUtils.convert(list, SysMenuVo.class); - } return menuList; } @@ -101,14 +90,7 @@ public class SysMenuServiceImpl implements ISysMenuService { */ @Override public Set selectMenuPermsByUserId(Long userId) { - List perms = baseMapper.selectMenuPermsByUserId(userId); - Set permsSet = new HashSet<>(); - for (String perm : perms) { - if (StringUtils.isNotEmpty(perm)) { - permsSet.addAll(StringUtils.splitList(perm.trim())); - } - } - return permsSet; + return baseMapper.selectMenuPermsByUserId(userId); } /** @@ -119,14 +101,7 @@ public class SysMenuServiceImpl implements ISysMenuService { */ @Override public Set selectMenuPermsByRoleId(Long roleId) { - List perms = baseMapper.selectMenuPermsByRoleId(roleId); - Set permsSet = new HashSet<>(); - for (String perm : perms) { - if (StringUtils.isNotEmpty(perm)) { - permsSet.addAll(StringUtils.splitList(perm.trim())); - } - } - return permsSet; + return baseMapper.selectMenuPermsByRoleId(roleId); } /** @@ -141,7 +116,13 @@ public class SysMenuServiceImpl implements ISysMenuService { if (LoginHelper.isSuperAdmin(userId)) { menus = baseMapper.selectMenuTreeAll(); } else { - menus = baseMapper.selectMenuTreeByUserId(userId); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + menus = baseMapper.selectList( + wrapper.in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU) + .eq(SysMenu::getStatus, SystemConstants.NORMAL) + .inSql(SysMenu::getMenuId, baseMapper.buildMenuByUserSql(userId)) + .orderByAsc(SysMenu::getParentId) + .orderByAsc(SysMenu::getOrderNum)); } return getChildPerms(menus, Constants.TOP_PARENT_ID); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java index 19a3ff59a..aa4135f1e 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java @@ -3,6 +3,7 @@ package org.dromara.system.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.StringUtils; @@ -16,7 +17,6 @@ import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.mapper.SysNoticeMapper; import org.dromara.system.mapper.SysUserMapper; import org.dromara.system.service.ISysNoticeService; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Arrays; @@ -34,6 +34,13 @@ public class SysNoticeServiceImpl implements ISysNoticeService { private final SysNoticeMapper baseMapper; private final SysUserMapper userMapper; + /** + * 分页查询通知公告列表 + * + * @param notice 查询条件 + * @param pageQuery 分页参数 + * @return 通知公告分页列表 + */ @Override public TableDataInfo selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(notice); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java index 27c2f32d5..9399c901d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java @@ -49,6 +49,13 @@ public class SysOperLogServiceImpl implements ISysOperLogService { insertOperlog(operLog); } + /** + * 分页查询操作日志列表 + * + * @param operLog 查询条件 + * @param pageQuery 分页参数 + * @return 操作日志分页列表 + */ @Override public TableDataInfo selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(operLog); 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 1f2bf80db..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 @@ -17,6 +17,7 @@ import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils; +import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.oss.core.OssClient; @@ -24,6 +25,7 @@ import org.dromara.common.oss.entity.UploadResult; import org.dromara.common.oss.enums.AccessPolicyType; import org.dromara.common.oss.factory.OssFactory; import org.dromara.system.domain.SysOss; +import org.dromara.system.domain.SysOssExt; import org.dromara.system.domain.bo.SysOssBo; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.mapper.SysOssMapper; @@ -114,7 +116,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { } } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } @Override @@ -199,8 +201,11 @@ public class SysOssServiceImpl implements ISysOssService, OssService { } catch (IOException e) { throw new ServiceException(e.getMessage()); } + SysOssExt ext1 = new SysOssExt(); + ext1.setFileSize(file.getSize()); + ext1.setContentType(file.getContentType()); // 保存文件信息 - return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1); } /** @@ -215,18 +220,21 @@ public class SysOssServiceImpl implements ISysOssService, OssService { String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); OssClient storage = OssFactory.instance(); UploadResult uploadResult = storage.uploadSuffix(file, suffix); + SysOssExt ext1 = new SysOssExt(); + ext1.setFileSize(file.length()); // 保存文件信息 - return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); + return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1); } @NotNull - private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) { + private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult, SysOssExt ext1) { SysOss oss = new SysOss(); oss.setUrl(uploadResult.getUrl()); oss.setFileSuffix(suffix); oss.setFileName(uploadResult.getFilename()); oss.setOriginalName(originalfileName); oss.setService(configKey); + oss.setExt1(JsonUtils.toJsonString(ext1)); baseMapper.insert(oss); SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class); return this.matchingUrl(sysOssVo); 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 122492e0c..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 @@ -14,7 +14,6 @@ import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.system.domain.SysDept; import org.dromara.system.domain.SysPost; import org.dromara.system.domain.SysUserPost; import org.dromara.system.domain.bo.SysPostBo; @@ -25,7 +24,7 @@ import org.dromara.system.mapper.SysUserPostMapper; import org.dromara.system.service.ISysPostService; import org.springframework.stereotype.Service; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -42,6 +41,13 @@ public class SysPostServiceImpl implements ISysPostService, PostService { private final SysDeptMapper deptMapper; private final SysUserPostMapper userPostMapper; + /** + * 分页查询岗位列表 + * + * @param post 查询条件 + * @param pageQuery 分页参数 + * @return 岗位分页列表 + */ @Override public TableDataInfo selectPagePostList(SysPostBo post, PageQuery pageQuery) { Page page = baseMapper.selectPagePostList(pageQuery.build(), buildQueryWrapper(post)); @@ -92,9 +98,7 @@ public class SysPostServiceImpl implements ISysPostService, PostService { } else if (ObjectUtil.isNotNull(bo.getBelongDeptId())) { //部门树搜索 wrapper.and(x -> { - List deptList = deptMapper.selectListByParentId(bo.getBelongDeptId()); - List deptIds = StreamUtils.toList(deptList, SysDept::getDeptId); - deptIds.add(bo.getBelongDeptId()); + List deptIds = deptMapper.selectDeptAndChildById(bo.getBelongDeptId()); x.in(SysPost::getDeptId, deptIds); }); } @@ -217,14 +221,14 @@ public class SysPostServiceImpl implements ISysPostService, PostService { * @return 结果 */ @Override - public int deletePostByIds(Long[] postIds) { - for (Long postId : postIds) { - SysPost post = baseMapper.selectById(postId); - if (countUserPostById(postId) > 0) { - throw new ServiceException(String.format("%1$s已分配,不能删除!", post.getPostName())); + public int deletePostByIds(List postIds) { + List list = baseMapper.selectByIds(postIds); + for (SysPost post : list) { + if (this.countUserPostById(post.getPostId()) > 0) { + throw new ServiceException("{}已分配,不能删除!", post.getPostName()); } } - return baseMapper.deleteByIds(Arrays.asList(postIds)); + return baseMapper.deleteByIds(postIds); } /** @@ -251,4 +255,23 @@ public class SysPostServiceImpl implements ISysPostService, PostService { return baseMapper.updateById(post); } + /** + * 根据岗位 ID 列表查询岗位名称映射关系 + * + * @param postIds 岗位 ID 列表 + * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 + */ + @Override + public Map selectPostNamesByIds(List postIds) { + if (CollUtil.isEmpty(postIds)) { + return Collections.emptyMap(); + } + List list = baseMapper.selectList( + new LambdaQueryWrapper() + .select(SysPost::getPostId, SysPost::getPostName) + .in(SysPost::getPostId, postIds) + ); + return StreamUtils.toMap(list, SysPost::getPostId, SysPost::getPostName); + } + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java index 0a2e485f6..53d270f0d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java @@ -7,7 +7,6 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -55,6 +54,13 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { private final SysUserRoleMapper userRoleMapper; private final SysRoleDeptMapper roleDeptMapper; + /** + * 分页查询角色列表 + * + * @param role 查询条件 + * @param pageQuery 分页参数 + * @return 角色分页列表 + */ @Override public TableDataInfo selectPageRoleList(SysRoleBo role, PageQuery pageQuery) { Page page = baseMapper.selectPageRoleList(pageQuery.build(), this.buildQueryWrapper(role)); @@ -62,7 +68,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { } /** - * 根据条件分页查询角色数据 + * 根据条件查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 @@ -74,15 +80,14 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { private Wrapper buildQueryWrapper(SysRoleBo bo) { Map params = bo.getParams(); - QueryWrapper wrapper = Wrappers.query(); - wrapper.eq("r.del_flag", SystemConstants.NORMAL) - .eq(ObjectUtil.isNotNull(bo.getRoleId()), "r.role_id", bo.getRoleId()) - .like(StringUtils.isNotBlank(bo.getRoleName()), "r.role_name", bo.getRoleName()) - .eq(StringUtils.isNotBlank(bo.getStatus()), "r.status", bo.getStatus()) - .like(StringUtils.isNotBlank(bo.getRoleKey()), "r.role_key", bo.getRoleKey()) + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(ObjectUtil.isNotNull(bo.getRoleId()), SysRole::getRoleId, bo.getRoleId()) + .like(StringUtils.isNotBlank(bo.getRoleName()), SysRole::getRoleName, bo.getRoleName()) + .eq(StringUtils.isNotBlank(bo.getStatus()), SysRole::getStatus, bo.getStatus()) + .like(StringUtils.isNotBlank(bo.getRoleKey()), SysRole::getRoleKey, bo.getRoleKey()) .between(params.get("beginTime") != null && params.get("endTime") != null, - "r.create_time", params.get("beginTime"), params.get("endTime")) - .orderByAsc("r.role_sort").orderByAsc("r.create_time"); + SysRole::getCreateTime, params.get("beginTime"), params.get("endTime")) + .orderByAsc(SysRole::getRoleSort).orderByAsc(SysRole::getCreateTime); return wrapper; } @@ -176,9 +181,9 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { */ @Override public List selectRoleByIds(List roleIds) { - return baseMapper.selectRoleList(new QueryWrapper() - .eq("r.status", SystemConstants.NORMAL) - .in(CollUtil.isNotEmpty(roleIds), "r.role_id", roleIds)); + return baseMapper.selectRoleList(new LambdaQueryWrapper() + .eq(SysRole::getStatus, SystemConstants.NORMAL) + .in(CollUtil.isNotEmpty(roleIds), SysRole::getRoleId, roleIds)); } /** @@ -249,14 +254,23 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { if (ObjectUtil.isNull(roleId)) { return; } - if (LoginHelper.isSuperAdmin()) { + this.checkRoleDataScope(Collections.singletonList(roleId)); + } + + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色ID列表(支持传单个ID) + */ + @Override + public void checkRoleDataScope(List roleIds) { + if (CollUtil.isEmpty(roleIds) || LoginHelper.isSuperAdmin()) { return; } - List roles = this.selectRoleList(new SysRoleBo(roleId)); - if (CollUtil.isEmpty(roles)) { - throw new ServiceException("没有权限访问角色数据!"); + long count = baseMapper.selectRoleCount(roleIds); + if (count != roleIds.size()) { + throw new ServiceException("没有权限访问部分角色数据!"); } - } /** @@ -359,7 +373,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { rm.setMenuId(menuId); list.add(rm); } - if (list.size() > 0) { + if (CollUtil.isNotEmpty(list)) { rows = roleMenuMapper.insertBatch(list) ? list.size() : 0; } return rows; @@ -380,7 +394,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { rd.setDeptId(deptId); list.add(rd); } - if (list.size() > 0) { + if (CollUtil.isNotEmpty(list)) { rows = roleDeptMapper.insertBatch(list) ? list.size() : 0; } return rows; @@ -412,21 +426,20 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { @CacheEvict(cacheNames = CacheNames.SYS_ROLE_CUSTOM, allEntries = true) @Override @Transactional(rollbackFor = Exception.class) - public int deleteRoleByIds(Long[] roleIds) { - for (Long roleId : roleIds) { - SysRole role = baseMapper.selectById(roleId); + public int deleteRoleByIds(List roleIds) { + this.checkRoleDataScope(roleIds); + List roles = baseMapper.selectByIds(roleIds); + for (SysRole role : roles) { checkRoleAllowed(BeanUtil.toBean(role, SysRoleBo.class)); - checkRoleDataScope(roleId); - if (countUserRoleByRoleId(roleId) > 0) { + if (countUserRoleByRoleId(role.getRoleId()) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除!", role.getRoleName())); } } - List ids = Arrays.asList(roleIds); // 删除角色与菜单关联 - roleMenuMapper.delete(new LambdaQueryWrapper().in(SysRoleMenu::getRoleId, ids)); + roleMenuMapper.delete(new LambdaQueryWrapper().in(SysRoleMenu::getRoleId, roleIds)); // 删除角色与部门关联 - roleDeptMapper.delete(new LambdaQueryWrapper().in(SysRoleDept::getRoleId, ids)); - return baseMapper.deleteByIds(ids); + roleDeptMapper.delete(new LambdaQueryWrapper().in(SysRoleDept::getRoleId, roleIds)); + return baseMapper.deleteByIds(roleIds); } /** @@ -437,6 +450,9 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { */ @Override public int deleteAuthUser(SysUserRole userRole) { + if (LoginHelper.getUserId().equals(userRole.getUserId())) { + throw new ServiceException("不允许修改当前用户角色!"); + } int rows = userRoleMapper.delete(new LambdaQueryWrapper() .eq(SysUserRole::getRoleId, userRole.getRoleId()) .eq(SysUserRole::getUserId, userRole.getUserId())); @@ -456,6 +472,9 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { @Override public int deleteAuthUsers(Long roleId, Long[] userIds) { List ids = List.of(userIds); + if (ids.contains(LoginHelper.getUserId())) { + throw new ServiceException("不允许修改当前用户角色!"); + } int rows = userRoleMapper.delete(new LambdaQueryWrapper() .eq(SysUserRole::getRoleId, roleId) .in(SysUserRole::getUserId, ids)); @@ -477,6 +496,9 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { // 新增用户与角色管理 int rows = 1; List ids = List.of(userIds); + if (ids.contains(LoginHelper.getUserId())) { + throw new ServiceException("不允许修改当前用户角色!"); + } List list = StreamUtils.toList(ids, userId -> { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); @@ -492,6 +514,17 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { return rows; } + /** + * 根据角色ID清除该角色关联的所有在线用户的登录状态(踢出在线用户) + * + *

+ * 先判断角色是否绑定用户,若无绑定则直接返回 + * 然后遍历当前所有在线Token,查找拥有该角色的用户并强制登出 + * 注意:在线用户量过大时,操作可能导致 Redis 阻塞,需谨慎调用 + *

+ * + * @param roleId 角色ID + */ @Override public void cleanOnlineUserByRole(Long roleId) { // 如果角色未绑定用户 直接返回 @@ -523,6 +556,16 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { }); } + /** + * 根据用户ID列表清除对应在线用户的登录状态(踢出指定用户) + * + *

+ * 遍历当前所有在线Token,匹配用户ID列表中的用户,强制登出 + * 注意:在线用户量过大时,操作可能导致 Redis 阻塞,需谨慎调用 + *

+ * + * @param userIds 需要清除的用户ID列表 + */ @Override public void cleanOnlineUser(List userIds) { List keys = StpUtil.searchTokenValue("", 0, -1, false); @@ -549,4 +592,23 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService { }); } + /** + * 根据角色 ID 列表查询角色名称映射关系 + * + * @param roleIds 角色 ID 列表 + * @return Map,其中 key 为角色 ID,value 为对应的角色名称 + */ + @Override + public Map selectRoleNamesByIds(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return Collections.emptyMap(); + } + List list = baseMapper.selectList( + new LambdaQueryWrapper() + .select(SysRole::getRoleId, SysRole::getRoleName) + .in(SysRole::getRoleId, roleIds) + ); + return StreamUtils.toMap(list, SysRole::getRoleId, SysRole::getRoleName); + } + } 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 0bd5873e0..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(), - SysRoleVo::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(), - SysPostVo::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(), - SysDeptVo::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(), - SysUserVo::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/SysTenantServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java index ca088607b..4442157a9 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java @@ -414,12 +414,15 @@ public class SysTenantServiceImpl implements ISysTenantService { dictTypeList.addAll(dictTypeMapper.selectList()); dictDataList.addAll(dictDataMapper.selectList()); }); - Map> typeMap = StreamUtils.groupByKey(dictTypeList, TenantEntity::getTenantId); - Map>> typeDataMap = StreamUtils.groupBy2Key( - dictDataList, TenantEntity::getTenantId, SysDictData::getDictType); - // 管理租户字典数据 - List defaultTypeMap = typeMap.get(TenantConstants.DEFAULT_TENANT_ID); - Map> defaultTypeDataMap = typeDataMap.get(TenantConstants.DEFAULT_TENANT_ID); + // 所有租户字典类型 + Map> dictTypeMap = StreamUtils.groupByKey(dictTypeList, TenantEntity::getTenantId); + // 所有租户字典数据 + Map>> dictDataMap = StreamUtils.groupBy2Key(dictDataList, TenantEntity::getTenantId, SysDictData::getDictType); + + // 默认租户字典类型列表 + List defaultDictTypeList = dictTypeMap.get(TenantConstants.DEFAULT_TENANT_ID); + // 默认租户字典数据 + Map> defaultDictDataMap = dictDataMap.get(TenantConstants.DEFAULT_TENANT_ID); // 获取所有租户编号 List tenantIds = baseMapper.selectObjs( @@ -427,57 +430,67 @@ public class SysTenantServiceImpl implements ISysTenantService { .eq(SysTenant::getStatus, SystemConstants.NORMAL), x -> { return Convert.toStr(x); }); + // 待入库的字典类型和字典数据 List saveTypeList = new ArrayList<>(); List saveDataList = new ArrayList<>(); - Set set = new HashSet<>(); + // 待同步的租户编号(用于清除对于租户的字典缓存) + Set syncTenantIds = new HashSet<>(); + // 循环所有租户,处理需要同步的数据 for (String tenantId : tenantIds) { + // 排除默认租户 if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { continue; } - for (SysDictType dictType : defaultTypeMap) { - List typeList = StreamUtils.toList(typeMap.get(tenantId), SysDictType::getDictType); - List dataList = defaultTypeDataMap.get(dictType.getDictType()); + // 根据默认租户的字典类型进行数据同步 + for (SysDictType dictType : defaultDictTypeList) { + // 获取当前租户的字典类型列表 + List typeList = StreamUtils.toList(dictTypeMap.get(tenantId), SysDictType::getDictType); + // 根据字典类型获取默认租户的字典数据 + List defaultDictDataList = defaultDictDataMap.get(dictType.getDictType()); + // 排除不需要同步的字典数据 + Set excludeDictDataSet = CollUtil.newHashSet(); + // 处理 存在type不存在data 的情况 if (typeList.contains(dictType.getDictType())) { - List dataListTenant = typeDataMap.get(tenantId).get(dictType.getDictType()); - Map map = StreamUtils.toIdentityMap(dataListTenant, SysDictData::getDictValue); - for (SysDictData dictData : dataList) { - if (!map.containsKey(dictData.getDictValue())) { - SysDictData data = BeanUtil.toBean(dictData, SysDictData.class); - // 设置字典编码为 null - data.setDictCode(null); - data.setTenantId(tenantId); - data.setCreateTime(null); - data.setUpdateTime(null); - data.setCreateDept(null); - data.setCreateBy(null); - data.setUpdateBy(null); - set.add(tenantId); - saveDataList.add(data); - } - } + // 获取租户字典数据 + Optional.ofNullable(dictDataMap.get(tenantId)) + // 获取租户当前字典类型的字典数据 + .map(tenantDictDataMap -> tenantDictDataMap.get(dictType.getDictType())) + // 保存字典数据项的字典键值,用于判断数据是否需要同步 + .map(data -> StreamUtils.toSet(data, SysDictData::getDictValue)) + // 添加到排除集合中 + .ifPresent(excludeDictDataSet::addAll); } else { + // 同步字典类型 SysDictType type = BeanUtil.toBean(dictType, SysDictType.class); type.setDictId(null); type.setTenantId(tenantId); type.setCreateTime(null); type.setUpdateTime(null); - set.add(tenantId); + syncTenantIds.add(tenantId); saveTypeList.add(type); - if (CollUtil.isNotEmpty(dataList)) { - // 筛选出 dictType 对应的 data - for (SysDictData dictData : dataList) { - SysDictData data = BeanUtil.toBean(dictData, SysDictData.class); - // 设置字典编码为 null - data.setDictCode(null); - data.setTenantId(tenantId); - data.setCreateTime(null); - data.setUpdateTime(null); - data.setCreateDept(null); - data.setCreateBy(null); - data.setUpdateBy(null); - set.add(tenantId); - saveDataList.add(data); + } + + // 默认租户字典数据不为空再去处理 + if (CollUtil.isNotEmpty(defaultDictDataList)) { + // 提前优化排除判断if条件语句,对于 && 并联条件,该优化可以避免不必要的 excludeDictDataSet.contains() 函数调用 + boolean isExclude = CollUtil.isNotEmpty(excludeDictDataSet); + // 筛选出 dictType 对应的 data + for (SysDictData dictData : defaultDictDataList) { + // 排除不需要同步的字典数据 + if (isExclude && excludeDictDataSet.contains(dictData.getDictValue())) { + continue; } + SysDictData data = BeanUtil.toBean(dictData, SysDictData.class); + // 设置字典编码为 null + data.setDictCode(null); + data.setTenantId(tenantId); + data.setCreateTime(null); + data.setUpdateTime(null); + data.setCreateDept(null); + data.setCreateBy(null); + data.setUpdateBy(null); + syncTenantIds.add(tenantId); + saveDataList.add(data); } } } @@ -490,7 +503,7 @@ public class SysTenantServiceImpl implements ISysTenantService { dictDataMapper.insertBatch(saveDataList); } }); - for (String tenantId : set) { + for (String tenantId : syncTenantIds) { TenantHelper.dynamic(tenantId, () -> CacheUtils.clear(CacheNames.SYS_DICT)); } } 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 d673a2159..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 @@ -22,7 +22,9 @@ import org.dromara.common.core.utils.*; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.system.domain.*; +import org.dromara.system.domain.SysUser; +import org.dromara.system.domain.SysUserPost; +import org.dromara.system.domain.SysUserRole; import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.vo.SysPostVo; import org.dromara.system.domain.vo.SysRoleVo; @@ -36,7 +38,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; -import java.util.stream.Collectors; /** * 用户 业务层处理 @@ -79,10 +80,8 @@ public class SysUserServiceImpl implements ISysUserService, UserService { .between(params.get("beginTime") != null && params.get("endTime") != null, "u.create_time", params.get("beginTime"), params.get("endTime")) .and(ObjectUtil.isNotNull(user.getDeptId()), w -> { - List deptList = deptMapper.selectListByParentId(user.getDeptId()); - List ids = StreamUtils.toList(deptList, SysDept::getDeptId); - ids.add(user.getDeptId()); - w.in("u.dept_id", ids); + List deptIds = deptMapper.selectDeptAndChildById(user.getDeptId()); + w.in("u.dept_id", deptIds); }).orderByAsc("u.user_id"); return baseMapper.selectUserExportList(wrapper); } @@ -100,9 +99,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { .between(params.get("beginTime") != null && params.get("endTime") != null, SysUser::getCreateTime, params.get("beginTime"), params.get("endTime")) .and(ObjectUtil.isNotNull(user.getDeptId()), w -> { - List deptList = deptMapper.selectListByParentId(user.getDeptId()); - List ids = StreamUtils.toList(deptList, SysDept::getDeptId); - ids.add(user.getDeptId()); + List ids = deptMapper.selectDeptAndChildById(user.getDeptId()); w.in(SysUser::getDeptId, ids); }).orderByAsc(SysUser::getUserId); if (StringUtils.isNotBlank(user.getExcludeUserIds())) { @@ -357,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; } @@ -454,21 +451,31 @@ public class SysUserServiceImpl implements ISysUserService, UserService { * @param clear 清除已存在的关联数据 */ private void insertUserPost(SysUserBo user, boolean clear) { - Long[] posts = user.getPostIds(); - if (ArrayUtil.isNotEmpty(posts)) { - if (clear) { - // 删除用户与岗位关联 - userPostMapper.delete(new LambdaQueryWrapper().eq(SysUserPost::getUserId, user.getUserId())); - } - // 新增用户与岗位管理 - List list = StreamUtils.toList(List.of(posts), postId -> { + Long[] postIdArr = user.getPostIds(); + if (ArrayUtil.isEmpty(postIdArr)) { + return; + } + List postIds = Arrays.asList(postIdArr); + + // 校验是否有权限操作这些岗位(含数据权限控制) + if (postMapper.selectPostCount(postIds) != postIds.size()) { + throw new ServiceException("没有权限访问岗位的数据"); + } + + // 是否清除旧的用户岗位绑定 + if (clear) { + userPostMapper.delete(new LambdaQueryWrapper().eq(SysUserPost::getUserId, user.getUserId())); + } + + // 构建用户岗位关联列表并批量插入 + List list = StreamUtils.toList(postIds, + postId -> { SysUserPost up = new SysUserPost(); up.setUserId(user.getUserId()); up.setPostId(postId); return up; }); - userPostMapper.insertBatch(list); - } + userPostMapper.insertBatch(list); } /** @@ -479,30 +486,36 @@ public class SysUserServiceImpl implements ISysUserService, UserService { * @param clear 清除已存在的关联数据 */ private void insertUserRole(Long userId, Long[] roleIds, boolean clear) { - if (ArrayUtil.isNotEmpty(roleIds)) { - List roleList = new ArrayList<>(List.of(roleIds)); - if (!LoginHelper.isSuperAdmin(userId)) { - roleList.remove(SystemConstants.SUPER_ADMIN_ID); - } - // 判断是否具有此角色的操作权限 - List roles = roleMapper.selectRoleList( - new QueryWrapper().in("r.role_id", roleList)); - if (CollUtil.isEmpty(roles)) { - throw new ServiceException("没有权限访问角色的数据"); - } - if (clear) { - // 删除用户与角色关联 - userRoleMapper.delete(new LambdaQueryWrapper().eq(SysUserRole::getUserId, userId)); - } - // 新增用户与角色管理 - List list = StreamUtils.toList(roleList, roleId -> { + if (ArrayUtil.isEmpty(roleIds)) { + return; + } + + List roleList = new ArrayList<>(Arrays.asList(roleIds)); + + // 非超级管理员,禁止包含超级管理员角色 + if (!LoginHelper.isSuperAdmin(userId)) { + roleList.remove(SystemConstants.SUPER_ADMIN_ID); + } + + // 校验是否有权限访问这些角色(含数据权限控制) + if (roleMapper.selectRoleCount(roleList) != roleList.size()) { + throw new ServiceException("没有权限访问角色的数据"); + } + + // 是否清除原有绑定 + if (clear) { + userRoleMapper.delete(new LambdaQueryWrapper().eq(SysUserRole::getUserId, userId)); + } + + // 批量插入用户-角色关联 + List list = StreamUtils.toList(roleList, + roleId -> { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); ur.setRoleId(roleId); return ur; }); - userRoleMapper.insertBatch(list); - } + userRoleMapper.insertBatch(list); } /** @@ -609,7 +622,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { list.add(nickname); } } - return String.join(StringUtils.SEPARATOR, list); + return StringUtils.joinComma(list); } /** @@ -748,69 +761,12 @@ public class SysUserServiceImpl implements ISysUserService, UserService { if (CollUtil.isEmpty(userIds)) { return Collections.emptyMap(); } - return baseMapper.selectList( - new LambdaQueryWrapper() - .select(SysUser::getUserId, SysUser::getNickName) - .in(SysUser::getUserId, userIds) - ).stream() - .collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName)); - } - - /** - * 根据角色 ID 列表查询角色名称映射关系 - * - * @param roleIds 角色 ID 列表 - * @return Map,其中 key 为角色 ID,value 为对应的角色名称 - */ - @Override - public Map selectRoleNamesByIds(List roleIds) { - if (CollUtil.isEmpty(roleIds)) { - return Collections.emptyMap(); - } - return roleMapper.selectList( - new LambdaQueryWrapper() - .select(SysRole::getRoleId, SysRole::getRoleName) - .in(SysRole::getRoleId, roleIds) - ).stream() - .collect(Collectors.toMap(SysRole::getRoleId, SysRole::getRoleName)); - } - - /** - * 根据部门 ID 列表查询部门名称映射关系 - * - * @param deptIds 部门 ID 列表 - * @return Map,其中 key 为部门 ID,value 为对应的部门名称 - */ - @Override - public Map selectDeptNamesByIds(List deptIds) { - if (CollUtil.isEmpty(deptIds)) { - return Collections.emptyMap(); - } - return deptMapper.selectList( - new LambdaQueryWrapper() - .select(SysDept::getDeptId, SysDept::getDeptName) - .in(SysDept::getDeptId, deptIds) - ).stream() - .collect(Collectors.toMap(SysDept::getDeptId, SysDept::getDeptName)); - } - - /** - * 根据岗位 ID 列表查询岗位名称映射关系 - * - * @param postIds 岗位 ID 列表 - * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 - */ - @Override - public Map selectPostNamesByIds(List postIds) { - if (CollUtil.isEmpty(postIds)) { - return Collections.emptyMap(); - } - return postMapper.selectList( - new LambdaQueryWrapper() - .select(SysPost::getPostId, SysPost::getPostName) - .in(SysPost::getPostId, postIds) - ).stream() - .collect(Collectors.toMap(SysPost::getPostId, SysPost::getPostName)); + List list = baseMapper.selectList( + new LambdaQueryWrapper() + .select(SysUser::getUserId, SysUser::getNickName) + .in(SysUser::getUserId, userIds) + ); + return StreamUtils.toMap(list, SysUser::getUserId, SysUser::getNickName); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml index 6c5d89d92..928ad2738 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -4,18 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml index 9dd3f2e1d..9e78302f3 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -4,67 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - - - - - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml index 3c79918d8..a75350072 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -4,15 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml index 4ef7b1e22..7f69e0101 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -4,56 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - select distinct r.role_id, - r.role_name, - r.role_key, - r.role_sort, - r.data_scope, - r.menu_check_strictly, - r.dept_check_strictly, - r.status, - r.del_flag, - r.create_time, - r.remark - from sys_role r - left join sys_user_role sur on sur.role_id = r.role_id - left join sys_user u on u.user_id = sur.user_id - left join sys_dept d on u.dept_id = d.dept_id - - - - - - - - - - - - diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml index bc52d1a6d..6f7cedfee 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -4,10 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - 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 aaa640bfb..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 @@ -18,6 +18,11 @@ public interface FlowConstant { */ String BUSINESS_ID = "businessId"; + /** + * 部门id + */ + String INITIATOR_DEPT_ID = "initiatorDeptId"; + /** * 委托 */ @@ -78,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 60be92fe9..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; @@ -35,7 +36,12 @@ public enum TaskAssigneeEnum { /** * 岗位 */ - POST("岗位", "post:"); + POST("岗位", "post:"), + + /** + * SPEL表达式 + */ + SPEL("SpEL表达式", ""); private final String desc; private final String code; @@ -105,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/TaskStatusEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java index d18ebb01d..c7bced890 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/TaskStatusEnum.java @@ -100,5 +100,15 @@ public enum TaskStatusEnum { return STATUS_DESC_MAP.getOrDefault(status, StrUtil.EMPTY); } + /** + * 判断状态是否为通过或退回 + * + * @param status 状态值 + * @return true 表示是通过或退回状态 + */ + public static boolean isPassOrBack(String status) { + return PASS.getStatus().equals(status) || BACK.getStatus().equals(status); + } + } 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/FlwCategoryController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java index 3018b0848..3007921db 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java @@ -66,7 +66,6 @@ public class FlwCategoryController extends BaseController { @SaCheckPermission("workflow:category:query") @GetMapping("/{categoryId}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long categoryId) { - flwCategoryService.checkCategoryDataScope(categoryId); return R.ok(flwCategoryService.queryById(categoryId)); } @@ -93,7 +92,6 @@ public class FlwCategoryController extends BaseController { @PutMapping() public R edit(@Validated(EditGroup.class) @RequestBody FlowCategoryBo category) { Long categoryId = category.getCategoryId(); - flwCategoryService.checkCategoryDataScope(categoryId); if (!flwCategoryService.checkCategoryNameUnique(category)) { return R.fail("修改流程分类'" + category.getCategoryName() + "'失败,流程分类名称已存在"); } else if (category.getParentId().equals(categoryId)) { 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 e7ea5e4cb..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 @@ -13,6 +13,7 @@ import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.domain.bo.FlowCancelBo; import org.dromara.workflow.domain.bo.FlowInstanceBo; import org.dromara.workflow.domain.bo.FlowInvalidBo; +import org.dromara.workflow.domain.bo.FlowVariableBo; import org.dromara.workflow.domain.vo.FlowInstanceVo; import org.dromara.workflow.service.IFlwInstanceService; import org.springframework.validation.annotation.Validated; @@ -88,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)); + } + /** * 撤销流程 * @@ -142,6 +153,17 @@ public class FlwInstanceController extends BaseController { return R.ok(flwInstanceService.instanceVariable(instanceId)); } + /** + * 修改流程变量 + * + * @param bo 参数 + */ + @RepeatSubmit() + @PutMapping("/updateVariable") + public R updateVariable(@Validated @RequestBody FlowVariableBo bo) { + return toAjax(flwInstanceService.updateVariable(bo)); + } + /** * 作废流程 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java new file mode 100644 index 000000000..212b6592a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwSpelController.java @@ -0,0 +1,93 @@ +package org.dromara.workflow.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.domain.bo.FlowSpelBo; +import org.dromara.workflow.domain.vo.FlowSpelVo; +import org.dromara.workflow.service.IFlwSpelService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 流程spel达式定义 + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +@ConditionalOnEnable +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/workflow/spel") +public class FlwSpelController extends BaseController { + + private final IFlwSpelService flwSpelService; + + /** + * 查询流程spel达式定义列表 + */ + @SaCheckPermission("workflow:spel:list") + @GetMapping("/list") + public TableDataInfo list(FlowSpelBo bo, PageQuery pageQuery) { + return flwSpelService.queryPageList(bo, pageQuery); + } + + /** + * 获取流程spel达式定义详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("workflow:spel:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(flwSpelService.queryById(id)); + } + + /** + * 新增流程spel达式定义 + */ + @SaCheckPermission("workflow:spel:add") + @Log(title = "流程spel达式定义", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody FlowSpelBo bo) { + return toAjax(flwSpelService.insertByBo(bo)); + } + + /** + * 修改流程spel达式定义 + */ + @SaCheckPermission("workflow:spel:edit") + @Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody FlowSpelBo bo) { + return toAjax(flwSpelService.updateByBo(bo)); + } + + /** + * 删除流程spel达式定义 + * + * @param ids 主键串 + */ + @SaCheckPermission("workflow:spel:remove") + @Log(title = "流程spel达式定义", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(flwSpelService.deleteWithValidByIds(List.of(ids), true)); + } +} 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 5534b6732..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)); } /** @@ -206,7 +206,19 @@ public class FlwTaskController extends BaseController { */ @GetMapping("/currentTaskAllUser/{taskId}") public R> currentTaskAllUser(@PathVariable Long taskId) { - return R.ok(flwTaskService.currentTaskAllUser(taskId)); + return R.ok(flwTaskService.currentTaskAllUser(List.of(taskId))); } + /** + * 催办任务 + * + * @param bo 参数 + * @return 结果 + */ + @PostMapping("/urgeTask") + public R urgeTask(@RequestBody FlowUrgeTaskBo bo) { + return toAjax(flwTaskService.urgeTask(bo)); + } + + } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java index 98825d92d..39bb41f2c 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java @@ -82,6 +82,17 @@ public class TestLeaveController extends BaseController { return R.ok(testLeaveService.insertByBo(bo)); } + /** + * 提交请假并提交流程 + */ + @SaCheckPermission("workflow:leave:add") + @Log(title = "请假", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/submitAndFlowStart") + public R submitAndFlowStart(@Validated(AddGroup.class) @RequestBody TestLeaveBo bo) { + return R.ok(testLeaveService.submitAndFlowStart(bo)); + } + /** * 修改请假 */ 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/FlowSpel.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java new file mode 100644 index 000000000..be74ccb90 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowSpel.java @@ -0,0 +1,69 @@ +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.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 流程spel达式定义对象 flow_spel + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("flow_spel") +public class FlowSpel extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @TableId(value = "id") + private Long id; + + /** + * 组件名称 + */ + private String componentName; + + /** + * 方法名 + */ + private String methodName; + + /** + * 参数 + */ + private String methodParams; + + /** + * 预览spel表达式 + */ + private String viewSpel; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 删除标志 + */ + @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/FlowSpelBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java new file mode 100644 index 000000000..ebad8eafb --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowSpelBo.java @@ -0,0 +1,60 @@ +package org.dromara.workflow.domain.bo; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import org.dromara.workflow.domain.FlowSpel; + +/** + * 流程spel达式定义业务对象 flow_spel + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = FlowSpel.class, reverseConvertGenerate = false) +public class FlowSpelBo extends BaseEntity { + + /** + * 主键id + */ + private Long id; + + /** + * 组件名称 + */ + private String componentName; + + /** + * 方法名 + */ + private String methodName; + + /** + * 参数 + */ + private String methodParams; + + /** + * 预览spel值 + */ + @NotBlank(message = "预览spel值不能为空", groups = { AddGroup.class, EditGroup.class }) + private String viewSpel; + + /** + * 状态(0正常 1停用) + */ + @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class }) + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java new file mode 100644 index 000000000..8e51b12e5 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowUrgeTaskBo.java @@ -0,0 +1,38 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 流程变量参数 + * + * @author may + */ +@Data +public class FlowUrgeTaskBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务id + */ + @NotNull(message = "任务id为空", groups = AddGroup.class) + private List taskIdList; + + /** + * 消息类型 + */ + private List messageType; + + /** + * 催办内容 + */ + @NotNull(message = "催办内容为空", groups = AddGroup.class) + private String message; +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java new file mode 100644 index 000000000..9361299fe --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowVariableBo.java @@ -0,0 +1,39 @@ +package org.dromara.workflow.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 流程变量参数 + * + * @author may + */ +@Data +public class FlowVariableBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 流程实例id + */ + @NotNull(message = "流程实例id为空", groups = AddGroup.class) + private Long instanceId; + + /** + * 流程变量key + */ + @NotNull(message = "流程变量key为空", groups = AddGroup.class) + private String key; + + /** + * 流程变量value + */ + @NotNull(message = "流程变量value为空", groups = AddGroup.class) + private String value; + +} 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 395f71d95..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 @@ -31,6 +31,16 @@ public class TestLeaveBo extends BaseEntity { @NotNull(message = "主键不能为空", groups = {EditGroup.class}) private Long id; + /** + * 流程code + */ + 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/FlowSpelVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java new file mode 100644 index 000000000..24f1437e8 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowSpelVo.java @@ -0,0 +1,79 @@ +package org.dromara.workflow.domain.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.workflow.domain.FlowSpel; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 流程spel达式定义视图对象 flow_spel + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = FlowSpel.class) +public class FlowSpelVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @ExcelProperty(value = "主键id") + private Long id; + + /** + * 组件名称 + */ + @ExcelProperty(value = "组件名称") + private String componentName; + + /** + * 方法名 + */ + @ExcelProperty(value = "方法名") + private String methodName; + + /** + * 参数 + */ + @ExcelProperty(value = "参数") + private String methodParams; + + /** + * 预览spel值 + */ + @ExcelProperty(value = "预览spel值") + private String viewSpel; + + /** + * 状态(0正常 1停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=正常,1=停用") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} 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 07a22c4f3..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; /** * 任务视图 @@ -145,6 +146,7 @@ public class FlowTaskVo implements Serializable { /** * 办理人名称 */ + @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "assigneeIds") private String assigneeNames; /** @@ -184,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/FlowProcessEventHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java index c465271ca..15cddabc5 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java @@ -39,6 +39,7 @@ public class FlowProcessEventHandler { ProcessEvent processEvent = new ProcessEvent(); processEvent.setTenantId(tenantId); processEvent.setFlowCode(flowCode); + processEvent.setInstanceId(instance.getId()); processEvent.setBusinessId(instance.getBusinessId()); processEvent.setNodeType(instance.getNodeType()); processEvent.setNodeCode(instance.getNodeCode()); @@ -55,20 +56,23 @@ public class FlowProcessEventHandler { * @param flowCode 流程定义编码 * @param instance 实例数据 * @param taskId 任务id + * @param params 上一个任务的办理参数 */ - public void processTaskHandler(String flowCode, Instance instance, Long taskId) { + public void processTaskHandler(String flowCode, Instance instance, Long taskId, Map params) { String tenantId = TenantHelper.getTenantId(); log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}", tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId); ProcessTaskEvent processTaskEvent = new ProcessTaskEvent(); processTaskEvent.setTenantId(tenantId); processTaskEvent.setFlowCode(flowCode); + processTaskEvent.setInstanceId(instance.getId()); processTaskEvent.setBusinessId(instance.getBusinessId()); processTaskEvent.setNodeType(instance.getNodeType()); processTaskEvent.setNodeCode(instance.getNodeCode()); processTaskEvent.setNodeName(instance.getNodeName()); processTaskEvent.setTaskId(taskId); processTaskEvent.setStatus(instance.getFlowStatus()); + processTaskEvent.setParams(params); SpringUtils.context().publishEvent(processTaskEvent); } 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 f9ede15ce..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,13 +1,17 @@ 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; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.warm.flow.core.dto.FlowParams; import org.dromara.warm.flow.core.handler.PermissionHandler; import org.dromara.workflow.common.ConditionalOnEnable; -import org.dromara.workflow.service.IFlwCommonService; +import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.springframework.stereotype.Component; import java.util.Collections; @@ -24,7 +28,7 @@ import java.util.List; @Slf4j public class WorkflowPermissionHandler implements PermissionHandler { - private final IFlwCommonService flwCommonService; + private final IFlwTaskAssigneeService flwTaskAssigneeService; /** * 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理任务 @@ -51,9 +55,11 @@ public class WorkflowPermissionHandler implements PermissionHandler { */ @Override public List convertPermissions(List permissions) { - if (CollUtil.isNotEmpty(permissions)) { - permissions = flwCommonService.buildUser(permissions); + if (CollUtil.isEmpty(permissions)) { + return permissions; } - return permissions; + String storageIds = CollUtil.join(permissions, StringUtils.SEPARATOR); + List users = flwTaskAssigneeService.fetchUsersByStorageIds(storageIds); + 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 c1ef2622a..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,11 +1,15 @@ 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; @@ -14,22 +18,22 @@ 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.warm.flow.orm.entity.FlowInstance; -import org.dromara.warm.flow.orm.entity.FlowTask; 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; /** * 全局任务办理监听 @@ -43,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; /** * 创建监听器,任务创建时执行 @@ -65,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()); + } + } } /** @@ -82,8 +106,7 @@ public class WorkflowGlobalListener implements GlobalListener { String applyNodeCode = flwCommonService.applyNodeCode(definition.getId()); for (Task flowTask : nextTasks) { // 如果办理或者退回并行存在需要指定办理人,则直接覆盖办理人 - if (variable.containsKey(flowTask.getNodeCode()) && (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus()) - || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus()))) { + if (variable.containsKey(flowTask.getNodeCode()) && TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) { String userIds = variable.get(flowTask.getNodeCode()).toString(); flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR))); variable.remove(flowTask.getNodeCode()); @@ -105,6 +128,7 @@ public class WorkflowGlobalListener implements GlobalListener { Instance instance = listenerVariable.getInstance(); Definition definition = listenerVariable.getDefinition(); Task task = listenerVariable.getTask(); + List nextTasks = listenerVariable.getNextTasks(); Map params = new HashMap<>(); FlowParams flowParams = listenerVariable.getFlowParams(); Map variable = new HashMap<>(); @@ -127,42 +151,50 @@ public class WorkflowGlobalListener implements GlobalListener { if (StringUtils.isNotBlank(status)) { flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false); } + if (!BusinessStatusEnum.initialState(instance.getFlowStatus())) { + if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1 + && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) { + // 如果为画线指定驳回 线条指定为驳回 驳回得节点为申请人节点 则修改流程状态为退回 + flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false); + // 修改流程实例状态 + instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus()); + FlowEngine.insService().updateById(instance); + } + } } //发布任务事件 - if (task != null) { - flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId()); + if (CollUtil.isNotEmpty(nextTasks)) { + for (Task nextTask : nextTasks) { + flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(), params); + } } if (ObjectUtil.isNull(flowParams)) { return; } // 只有办理或者退回的时候才执行消息通知和抄送 - if (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus()) - || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus())) { - if (variable != null) { - if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) { - List flowCopyList = (List) variable.get(FlowConstant.FLOW_COPY_LIST); - // 添加抄送人 - flwTaskService.setCopy(task, flowCopyList); - } - if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) { - List messageType = (List) variable.get(FlowConstant.MESSAGE_TYPE); - String notice = (String) variable.get(FlowConstant.MESSAGE_NOTICE); - // 消息通知 - if (CollUtil.isNotEmpty(messageType)) { - flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); - } - } - FlowInstance ins = new FlowInstance(); - Map variableMap = instance.getVariableMap(); - variableMap.remove(FlowConstant.FLOW_COPY_LIST); - variableMap.remove(FlowConstant.MESSAGE_TYPE); - variableMap.remove(FlowConstant.MESSAGE_NOTICE); - variableMap.remove(FlowConstant.SUBMIT); - ins.setId(instance.getId()); - ins.setVariable(FlowEngine.jsonConvert.objToStr(variableMap)); - insService.updateById(ins); - } + if (!TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) { + return; } + if (ObjectUtil.isNull(variable)) { + return; + } + + if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) { + List flowCopyList = MapUtil.get(variable, FlowConstant.FLOW_COPY_LIST, new TypeReference<>() {}); + // 添加抄送人 + flwTaskService.setCopy(task, flowCopyList); + } + if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) { + List messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {}); + String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE); + flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); + } + FlowEngine.insService().removeVariables(instance.getId(), + FlowConstant.FLOW_COPY_LIST, + FlowConstant.MESSAGE_TYPE, + FlowConstant.MESSAGE_NOTICE, + FlowConstant.SUBMIT + ); } /** @@ -178,11 +210,10 @@ public class WorkflowGlobalListener implements GlobalListener { return flowStatus; } else { Long instanceId = instance.getId(); - List flowTasks = flwTaskService.selectByInstId(instanceId); - if (CollUtil.isEmpty(flowTasks)) { + 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/FlwCategoryMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java index 4a59f258a..7199d5ceb 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java @@ -20,19 +20,6 @@ import java.util.stream.Stream; */ public interface FlwCategoryMapper extends BaseMapperPlus { - /** - * 统计指定流程分类ID的分类数量 - * - * @param categoryId 流程分类ID - * @return 该流程分类ID的分类数量 - */ - @DataPermission({ - @DataColumn(key = "deptName", value = "createDept") - }) - default long countCategoryById(Long categoryId) { - return this.selectCount(new LambdaQueryWrapper().eq(FlowCategory::getCategoryId, categoryId)); - } - /** * 根据父流程分类ID查询其所有子流程分类的列表 * 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/mapper/FlwSpelMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java new file mode 100644 index 000000000..d3204958f --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwSpelMapper.java @@ -0,0 +1,15 @@ +package org.dromara.workflow.mapper; + +import org.dromara.workflow.domain.FlowSpel; +import org.dromara.workflow.domain.vo.FlowSpelVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 流程spel达式定义Mapper接口 + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +public interface FlwSpelMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java index fd86c82de..0d0422d5a 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwTaskMapper.java @@ -9,8 +9,6 @@ import org.dromara.workflow.domain.bo.FlowTaskBo; import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowTaskVo; -import java.util.List; - /** * 任务信息Mapper接口 @@ -29,14 +27,6 @@ public interface FlwTaskMapper { */ Page getListRunTask(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); - /** - * 获取待办信息 - * - * @param queryWrapper 条件 - * @return 结果 - */ - List getListRunTask(@Param(Constants.WRAPPER) Wrapper queryWrapper); - /** * 获取已办 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java new file mode 100644 index 000000000..7498db56c --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/rule/SpelRuleComponent.java @@ -0,0 +1,38 @@ +package org.dromara.workflow.rule; + +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.service.DeptService; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.springframework.stereotype.Component; + +/** + * spel表达式规则组件 + *

+ * 通过该组件统一管理流程定义中的spel表达式 + *

+ * + * @author Michelle.Chung + */ +@ConditionalOnEnable +@Slf4j +@Component +@RequiredArgsConstructor +public class SpelRuleComponent { + + private final DeptService deptService; + + /** + * 通过发起人部门id获取部门负责人 + */ + public Long selectDeptLeaderById(Long initiatorDeptId) { + Long leaderId = deptService.selectDeptLeaderById(initiatorDeptId); + if (ObjectUtil.isNull(leaderId)) { + throw new ServiceException("当前部门未设置负责人,请联系管理员操作。"); + } + return leaderId; + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java index 91f173d4f..f66882b8d 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCategoryService.java @@ -45,13 +45,6 @@ public interface IFlwCategoryService { */ List> selectCategoryTreeList(FlowCategoryBo category); - /** - * 校验流程分类是否有数据权限 - * - * @param categoryId 流程分类ID - */ - void checkCategoryDataScope(Long categoryId); - /** * 校验流程分类名称是否唯一 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java index 662d599eb..f809f47b3 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java @@ -1,5 +1,7 @@ package org.dromara.workflow.service; +import org.dromara.common.core.domain.dto.UserDTO; + import java.util.List; /** @@ -9,14 +11,6 @@ import java.util.List; */ public interface IFlwCommonService { - /** - * 构建工作流用户 - * - * @param permissionList 办理用户 - * @return 用户 - */ - List buildUser(List permissionList); - /** * 发送消息 * @@ -27,6 +21,16 @@ public interface IFlwCommonService { */ void sendMessage(String flowName, Long instId, List messageType, String message); + /** + * 发送消息 + * + * @param messageType 消息类型 + * @param message 消息内容 + * @param subject 邮件标题 + * @param userList 接收用户 + */ + void sendMessage(List messageType, String message, String subject, List userList); + /** * 申请人节点编码 * 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 01e5124b4..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 @@ -6,6 +6,7 @@ import org.dromara.warm.flow.orm.entity.FlowInstance; import org.dromara.workflow.domain.bo.FlowCancelBo; import org.dromara.workflow.domain.bo.FlowInstanceBo; import org.dromara.workflow.domain.bo.FlowInvalidBo; +import org.dromara.workflow.domain.bo.FlowVariableBo; import org.dromara.workflow.domain.vo.FlowInstanceVo; import java.util.List; @@ -84,6 +85,14 @@ public interface IFlwInstanceService { */ boolean deleteByInstanceIds(List instanceIds); + /** + * 按照实例id删除已完成得流程实例 + * + * @param instanceIds 删除的实例id + * @return 删除结果 + */ + boolean deleteHisByInstanceIds(List instanceIds); + /** * 撤销流程 * @@ -125,6 +134,14 @@ public interface IFlwInstanceService { */ Map instanceVariable(Long instanceId); + /** + * 更新流程变量 + * + * @param bo 参数 + * @return 结果 + */ + boolean updateVariable(FlowVariableBo bo); + /** * 设置流程变量 * @@ -141,14 +158,6 @@ public interface IFlwInstanceService { */ FlowInstance selectByTaskId(Long taskId); - /** - * 按任务id查询实例 - * - * @param taskIdList 任务id - * @return 结果 - */ - List selectByTaskIdList(List taskIdList); - /** * 作废流程 * 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/IFlwSpelService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java new file mode 100644 index 000000000..02781682a --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwSpelService.java @@ -0,0 +1,88 @@ +package org.dromara.workflow.service; + +import org.dromara.common.core.domain.dto.TaskAssigneeDTO; +import org.dromara.common.core.domain.model.TaskAssigneeBody; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.domain.bo.FlowSpelBo; +import org.dromara.workflow.domain.vo.FlowSpelVo; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 流程spel达式定义Service接口 + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +public interface IFlwSpelService { + + /** + * 查询流程spel达式定义 + * + * @param id 主键 + * @return 流程spel达式定义 + */ + FlowSpelVo queryById(Long id); + + /** + * 分页查询流程spel达式定义列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 流程spel达式定义分页列表 + */ + TableDataInfo queryPageList(FlowSpelBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的流程spel达式定义列表 + * + * @param bo 查询条件 + * @return 流程spel达式定义列表 + */ + List queryList(FlowSpelBo bo); + + /** + * 新增流程spel达式定义 + * + * @param bo 流程spel达式定义 + * @return 是否新增成功 + */ + Boolean insertByBo(FlowSpelBo bo); + + /** + * 修改流程spel达式定义 + * + * @param bo 流程spel达式定义 + * @return 是否修改成功 + */ + Boolean updateByBo(FlowSpelBo bo); + + /** + * 校验并批量删除流程spel达式定义信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 查询spel并返回任务指派的列表,支持分页 + * + * @param taskQuery 查询条件 + * @return 办理人 + */ + TaskAssigneeDTO selectSpelByTaskAssigneeList(TaskAssigneeBody taskQuery); + + /** + * 根据视图 SpEL 表达式列表,查询对应的备注信息 + * + * @param viewSpels SpEL 表达式列表 + * @return 映射表:key 为 SpEL 表达式,value 为对应备注;若为空则返回空 Map + */ + Map selectRemarksBySpels(List viewSpels); + +} 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 e172c001a..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 @@ -14,7 +14,6 @@ import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowTaskVo; import java.util.List; -import java.util.Map; /** * 任务 服务层 @@ -112,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); /** * 终止任务 @@ -150,14 +149,6 @@ public interface IFlwTaskService { */ List getNextNodeList(FlowNextNodeBo bo); - /** - * 按照任务id查询任务 - * - * @param taskIdList 任务id - * @return 结果 - */ - List selectHisTaskByIdList(List taskIdList); - /** * 按照任务id查询任务 * @@ -166,14 +157,6 @@ public interface IFlwTaskService { */ FlowHisTask selectHisTaskById(Long taskId); - /** - * 按照实例id查询任务 - * - * @param instanceIdList 流程实例id - * @return 结果 - */ - List selectByInstIdList(List instanceIdList); - /** * 按照实例id查询任务 * @@ -182,6 +165,22 @@ public interface IFlwTaskService { */ List selectByInstId(Long instanceId); + /** + * 按照实例id查询任务 + * + * @param instanceIds 列表 + * @return 结果 + */ + List selectByInstIds(List instanceIds); + + /** + * 判断流程是否已结束(即该流程实例下是否还有未完成的任务) + * + * @param instanceId 流程实例ID + * @return true 表示任务已全部结束;false 表示仍有任务存在 + */ + boolean isTaskEnd(Long instanceId); + /** * 任务操作 * @@ -191,21 +190,13 @@ public interface IFlwTaskService { */ boolean taskOperation(TaskOperationBo bo, String taskOperation); - /** - * 获取任务所有办理人 - * - * @param taskIdList 任务id - * @return 结果 - */ - Map> currentTaskAllUser(List taskIdList); - /** * 获取当前任务的所有办理人 * - * @param taskId 任务id + * @param taskIds 任务id * @return 结果 */ - List currentTaskAllUser(Long taskId); + List currentTaskAllUser(List taskIds); /** * 按照节点编码查询节点 @@ -215,4 +206,12 @@ public interface IFlwTaskService { * @return 节点 */ FlowNode getByNodeCode(String nodeCode, Long definitionId); + + /** + * 催办任务 + * + * @param bo 参数 + * @return 结果 + */ + boolean urgeTask(FlowUrgeTaskBo bo); } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java index 67b50baf3..748a2b174 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/ITestLeaveService.java @@ -35,6 +35,11 @@ public interface ITestLeaveService { */ TestLeaveVo insertByBo(TestLeaveBo bo); + /** + * 提交请假并发起流程 + */ + TestLeaveVo submitAndFlowStart(TestLeaveBo bo); + /** * 修改请假 */ 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 d42a48a2f..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,14 +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.common.satoken.utils.LoginHelper; 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; @@ -26,6 +24,7 @@ import org.dromara.workflow.service.IFlwCategoryService; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @@ -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,32 +96,29 @@ 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()) ); } /** - * 校验流程分类是否有数据权限 + * 工作流查询分类 * - * @param categoryId 流程分类ID + * @return 分类树结构列表 */ @Override - public void checkCategoryDataScope(Long categoryId) { - if (ObjectUtil.isNull(categoryId)) { - return; - } - if (LoginHelper.isSuperAdmin()) { - return; - } - if (baseMapper.countCategoryById(categoryId) == 0) { - throw new ServiceException("没有权限访问流程分类数据!"); - } + 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())) + ); } /** @@ -187,6 +183,9 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { @Override public int insertByBo(FlowCategoryBo bo) { FlowCategory info = baseMapper.selectById(bo.getParentId()); + if (ObjectUtil.isNull(info)) { + throw new ServiceException("父级流程分类不存在!"); + } FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class); category.setAncestors(info.getAncestors() + StringUtils.SEPARATOR + category.getParentId()); return baseMapper.insert(category); @@ -200,6 +199,7 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { */ @CacheEvict(cacheNames = FlowConstant.FLOW_CATEGORY_NAME, key = "#bo.categoryId") @Override + @Transactional(rollbackFor = Exception.class) public int updateByBo(FlowCategoryBo bo) { FlowCategory category = MapstructUtils.convert(bo, FlowCategory.class); FlowCategory oldCategory = baseMapper.selectById(category.getCategoryId()); @@ -207,14 +207,14 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { throw new ServiceException("流程分类不存在,无法修改"); } if (!oldCategory.getParentId().equals(category.getParentId())) { - // 如果是新父流程分类 则校验是否具有新父流程分类权限 避免越权 - this.checkCategoryDataScope(category.getParentId()); FlowCategory newParentCategory = baseMapper.selectById(category.getParentId()); if (ObjectUtil.isNotNull(newParentCategory)) { String newAncestors = newParentCategory.getAncestors() + StringUtils.SEPARATOR + newParentCategory.getCategoryId(); String oldAncestors = oldCategory.getAncestors(); category.setAncestors(newAncestors); updateCategoryChildren(category.getCategoryId(), newAncestors, oldAncestors); + } else { + throw new ServiceException("父级流程分类不存在!"); } } else { category.setAncestors(oldCategory.getAncestors()); 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 d7c6f77eb..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; @@ -27,8 +26,12 @@ import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.constant.FlowConstant; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; /** * 流程图提示信息 @@ -53,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; } @@ -74,15 +73,26 @@ public class FlwChartExtServiceImpl implements ChartExtService { Map dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS); - // 遍历流程定义中的每个节点,调用处理方法,将对应节点的任务列表及用户信息传入,生成扩展提示内容 for (NodeJson nodeJson : defJson.getNodeList()) { - // 获取当前节点对应的历史任务列表,如果没有则返回空列表避免空指针 List taskList = groupedByNode.get(nodeJson.getNodeCode()); if (CollUtil.isEmpty(taskList)) { continue; } - // 处理当前节点的扩展信息,包括构建审批人提示内容等 - this.processNodeExtInfo(nodeJson, taskList, userMap, dictType); + + // 按审批人分组去重,保留最新处理记录,最终转换成 List + List latestPerApprover = taskList.stream() + .collect(Collectors.collectingAndThen( + Collectors.toMap( + FlowHisTask::getApprover, + Function.identity(), + (oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask, + LinkedHashMap::new + ), + map -> new ArrayList<>(map.values()) + )); + + // 处理当前节点的扩展信息 + this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, dictType); } } @@ -128,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", @@ -240,7 +251,7 @@ public class FlwChartExtServiceImpl implements ChartExtService { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(FlowHisTask::getInstanceId, instanceId) .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) - .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); + .orderByDesc(FlowHisTask::getUpdateTime); return flowHisTaskMapper.selectList(wrapper); } 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 e6dc8155d..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,21 +11,19 @@ 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.entity.Task; -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; import org.dromara.workflow.service.IFlwCommonService; -import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.dromara.workflow.service.IFlwTaskService; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; +import java.util.Set; /** @@ -38,70 +36,70 @@ import java.util.stream.Collectors; @RequiredArgsConstructor @Service public class FlwCommonServiceImpl implements IFlwCommonService { - private final NodeService nodeService; + + private static final String DEFAULT_SUBJECT = "单据审批提醒"; /** - * 构建工作流用户 - * - * @param permissionList 办理用户 - * @return 用户 - */ - @Override - public List buildUser(List permissionList) { - if (CollUtil.isEmpty(permissionList)) { - return List.of(); - } - IFlwTaskAssigneeService taskAssigneeService = SpringUtils.getBean(IFlwTaskAssigneeService.class); - String processedBys = CollUtil.join(permissionList, StringUtils.SEPARATOR); - // 根据 processedBy 前缀判断处理人类型,分别获取用户列表 - List users = taskAssigneeService.fetchUsersByStorageIds(processedBys); - - return StreamUtils.toList(users, userDTO -> String.valueOf(userDTO.getUserId())); - } - - - /** - * 发送消息 + * 根据流程实例发送消息给当前处理人 * * @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 userList = new ArrayList<>(); List list = flwTaskService.selectByInstId(instId); + if (CollUtil.isEmpty(list)) { + return; + } if (StringUtils.isBlank(message)) { message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。"; } - for (Task task : list) { - List users = flwTaskService.currentTaskAllUser(task.getId()); - if (CollUtil.isNotEmpty(users)) { - userList.addAll(users); - } + List userList = flwTaskService.currentTaskAllUser(StreamUtils.toList(list, FlowTask::getId)); + if (CollUtil.isEmpty(userList)) { + return; } - if (CollUtil.isNotEmpty(userList)) { - for (String code : messageType) { - MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code); - if (ObjectUtil.isNotEmpty(messageTypeEnum)) { - switch (messageTypeEnum) { - case SYSTEM_MESSAGE: - SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList())); - dto.setMessage(message); - SseMessageUtils.publishMessage(dto); - break; - case EMAIL_MESSAGE: - MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message); - break; - case SMS_MESSAGE: - //todo 短信发送 - break; - default: - throw new IllegalStateException("Unexpected value: " + messageTypeEnum); - } + sendMessage(messageType, message, DEFAULT_SUBJECT, userList); + } + + /** + * 发送消息给指定用户列表 + * + * @param messageType 消息类型列表 + * @param message 消息内容 + * @param subject 邮件标题 + * @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)) { + continue; + } + switch (messageTypeEnum) { + case SYSTEM_MESSAGE -> { + SseMessageDto dto = new SseMessageDto(); + dto.setUserIds(userIds); + dto.setMessage(message); + SseMessageUtils.publishMessage(dto); } + case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message); + case SMS_MESSAGE -> { + //todo 短信发送 + } + default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum); } } } @@ -115,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 026f9ae8e..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 @@ -121,14 +121,14 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { List flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper().eq(FlowNode::getDefinitionId, id)); List errorMsg = new ArrayList<>(); if (CollUtil.isNotEmpty(flowNodes)) { + String applyNodeCode = flwCommonService.applyNodeCode(id); for (FlowNode flowNode : flowNodes) { - String applyNodeCode = flwCommonService.applyNodeCode(id); if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) { errorMsg.add(flowNode.getNodeName()); } } 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 { @@ -213,7 +213,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { return; } FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper() - .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID)); + .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID) + .eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID)); flowCategory.setCategoryId(null); flowCategory.setTenantId(tenantId); flowCategory.setCreateDept(null); @@ -230,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 a3e272f7e..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 @@ -11,7 +11,6 @@ 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.UserDTO; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.StreamUtils; @@ -19,11 +18,13 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.warm.flow.core.FlowEngine; import org.dromara.warm.flow.core.constant.ExceptionCons; 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.entity.User; import org.dromara.warm.flow.core.enums.NodeType; import org.dromara.warm.flow.core.service.DefService; import org.dromara.warm.flow.core.service.InsService; @@ -38,6 +39,7 @@ import org.dromara.workflow.common.enums.TaskStatusEnum; import org.dromara.workflow.domain.bo.FlowCancelBo; import org.dromara.workflow.domain.bo.FlowInstanceBo; import org.dromara.workflow.domain.bo.FlowInvalidBo; +import org.dromara.workflow.domain.bo.FlowVariableBo; import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowInstanceVo; import org.dromara.workflow.handler.FlowProcessEventHandler; @@ -49,7 +51,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; -import java.util.stream.Collectors; +import java.util.function.Function; /** * 流程实例 服务层实现 @@ -108,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()); @@ -203,22 +205,77 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { return false; } // 获取定义信息 - Map definitionMap = defService.getByIds( - StreamUtils.toList(instances, Instance::getDefinitionId) - ).stream().collect(Collectors.toMap(Definition::getId, definition -> definition)); + Map definitionMap = StreamUtils.toMap( + defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)), + Definition::getId, + 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; } /** @@ -281,37 +338,47 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE); } Long instanceId = flowInstance.getId(); - //运行中的任务 - List list = new ArrayList<>(); - List flowTaskList = flwTaskService.selectByInstId(instanceId); - if (CollUtil.isNotEmpty(flowTaskList)) { - List flowHisTaskVos = BeanUtil.copyToList(flowTaskList, FlowHisTaskVo.class); - for (FlowHisTaskVo flowHisTaskVo : flowHisTaskVos) { - flowHisTaskVo.setFlowStatus(TaskStatusEnum.WAITING.getStatus()); - flowHisTaskVo.setUpdateTime(null); - flowHisTaskVo.setRunDuration(null); - List allUser = flwTaskService.currentTaskAllUser(flowHisTaskVo.getId()); - if (CollUtil.isNotEmpty(allUser)) { - String join = StreamUtils.join(allUser, e -> String.valueOf(e.getUserId())); - flowHisTaskVo.setApprover(join); - } - if (BusinessStatusEnum.isDraftOrCancelOrBack(flowInstance.getFlowStatus())) { - flowHisTaskVo.setApprover(LoginHelper.getUserIdStr()); - flowHisTaskVo.setApproveName(LoginHelper.getLoginUser().getNickname()); + + // 先组装待审批任务(运行中的任务) + List runningTaskVos = new ArrayList<>(); + List runningTasks = flwTaskService.selectByInstId(instanceId); + if (CollUtil.isNotEmpty(runningTasks)) { + runningTaskVos = BeanUtil.copyToList(runningTasks, FlowHisTaskVo.class); + + List associatedUsers = FlowEngine.userService() + .getByAssociateds(StreamUtils.toList(runningTasks, FlowTask::getId)); + Map> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated); + + for (FlowHisTaskVo vo : runningTaskVos) { + vo.setFlowStatus(TaskStatusEnum.WAITING.getStatus()); + vo.setUpdateTime(null); + vo.setRunDuration(null); + + List users = taskUserMap.get(vo.getId()); + if (CollUtil.isNotEmpty(users)) { + vo.setApprover(StreamUtils.join(users, User::getProcessedBy)); } } - list.addAll(flowHisTaskVos); } - //历史任务 - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(FlowHisTask::getInstanceId, instanceId) - .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) - .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); - List flowHisTasks = flowHisTaskMapper.selectList(wrapper); - if (CollUtil.isNotEmpty(flowHisTasks)) { - list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class)); + + // 再组装历史任务(已处理任务) + List hisTaskVos = new ArrayList<>(); + List hisTasks = flowHisTaskMapper.selectList( + new LambdaQueryWrapper() + .eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) + .orderByDesc(FlowHisTask::getUpdateTime) + ); + if (CollUtil.isNotEmpty(hisTasks)) { + hisTaskVos = BeanUtil.copyToList(hisTasks, FlowHisTaskVo.class); } - return Map.of("list", list, "instanceId", instanceId); + + // 结果列表,待审批任务在前,历史任务在后 + List combinedList = new ArrayList<>(); + combinedList.addAll(runningTaskVos); + combinedList.addAll(hisTaskVos); + + return Map.of("list", combinedList, "instanceId", instanceId); } /** @@ -343,6 +410,34 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { return Map.of("variableList", variableList, "variable", flowInstance.getVariable()); } + /** + * 设置流程变量 + * + * @param bo 参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateVariable(FlowVariableBo bo) { + FlowInstance flowInstance = flowInstanceMapper.selectById(bo.getInstanceId()); + if (flowInstance == null) { + throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE); + } + try { + Map variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap())); + if (!variableMap.containsKey(bo.getKey())) { + log.error("变量不存在: {}", bo.getKey()); + return false; + } + variableMap.put(bo.getKey(), bo.getValue()); + flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap)); + flowInstanceMapper.updateById(flowInstance); + } catch (Exception e) { + log.error("设置流程变量失败: {}", e.getMessage(), e); + throw new ServiceException(e.getMessage()); + } + return true; + } + /** * 设置流程变量 * @@ -377,31 +472,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { return null; } - /** - * 按任务id查询实例 - * - * @param taskIdList 任务id - */ - @Override - public List selectByTaskIdList(List taskIdList) { - if (CollUtil.isEmpty(taskIdList)) { - return Collections.emptyList(); - } - Set instanceIds = new HashSet<>(); - List flowTaskList = flwTaskService.selectByIdList(taskIdList); - for (FlowTask flowTask : flowTaskList) { - instanceIds.add(flowTask.getInstanceId()); - } - List flowHisTaskList = flwTaskService.selectHisTaskByIdList(taskIdList); - for (FlowHisTask flowHisTask : flowHisTaskList) { - instanceIds.add(flowHisTask.getInstanceId()); - } - if (!instanceIds.isEmpty()) { - return this.selectInstListByIdList(new ArrayList<>(instanceIds)); - } - return Collections.emptyList(); - } - /** * 作废流程 * 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 new file mode 100644 index 000000000..e06b31448 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwSpelServiceImpl.java @@ -0,0 +1,190 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +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.constant.SystemConstants; +import org.dromara.common.core.domain.dto.TaskAssigneeDTO; +import org.dromara.common.core.domain.model.TaskAssigneeBody; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.domain.FlowSpel; +import org.dromara.workflow.domain.bo.FlowSpelBo; +import org.dromara.workflow.domain.vo.FlowSpelVo; +import org.dromara.workflow.mapper.FlwSpelMapper; +import org.dromara.workflow.service.IFlwSpelService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 流程spel达式定义Service业务层处理 + * + * @author Michelle.Chung + * @date 2025-07-04 + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwSpelServiceImpl implements IFlwSpelService { + + private final FlwSpelMapper baseMapper; + + /** + * 查询流程spel达式定义 + * + * @param id 主键 + * @return 流程spel达式定义 + */ + @Override + public FlowSpelVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询流程spel达式定义列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 流程spel达式定义分页列表 + */ + @Override + public TableDataInfo queryPageList(FlowSpelBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的流程spel达式定义列表 + * + * @param bo 查询条件 + * @return 流程spel达式定义列表 + */ + @Override + public List queryList(FlowSpelBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(FlowSpelBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByAsc(FlowSpel::getId); + lqw.like(StringUtils.isNotBlank(bo.getComponentName()), FlowSpel::getComponentName, bo.getComponentName()); + lqw.like(StringUtils.isNotBlank(bo.getMethodName()), FlowSpel::getMethodName, bo.getMethodName()); + lqw.eq(StringUtils.isNotBlank(bo.getMethodParams()), FlowSpel::getMethodParams, bo.getMethodParams()); + lqw.eq(StringUtils.isNotBlank(bo.getViewSpel()), FlowSpel::getViewSpel, bo.getViewSpel()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), FlowSpel::getStatus, bo.getStatus()); + lqw.like(StringUtils.isNotBlank(bo.getRemark()), FlowSpel::getRemark, bo.getRemark()); + return lqw; + } + + /** + * 新增流程spel达式定义 + * + * @param bo 流程spel达式定义 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(FlowSpelBo bo) { + FlowSpel add = MapstructUtils.convert(bo, FlowSpel.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改流程spel达式定义 + * + * @param bo 流程spel达式定义 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(FlowSpelBo bo) { + FlowSpel update = MapstructUtils.convert(bo, FlowSpel.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(FlowSpel entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除流程spel达式定义信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + /** + * 查询spel并返回任务指派的列表,支持分页 + * + * @param taskQuery 查询条件 + * @return 办理人 + */ + @Override + public TaskAssigneeDTO selectSpelByTaskAssigneeList(TaskAssigneeBody taskQuery) { + PageQuery pageQuery = new PageQuery(taskQuery.getPageSize(), taskQuery.getPageNum()); + FlowSpelBo bo = new FlowSpelBo(); + bo.setViewSpel(taskQuery.getHandlerCode()); + bo.setRemark(taskQuery.getHandlerName()); + bo.setStatus(SystemConstants.NORMAL); + Map params = bo.getParams(); + params.put("beginTime", taskQuery.getBeginTime()); + params.put("endTime", taskQuery.getEndTime()); + TableDataInfo page = this.queryPageList(bo, pageQuery); + // 使用封装的字段映射方法进行转换 + List handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(), + FlowSpelVo::getViewSpel, item -> "", FlowSpelVo::getRemark, item -> "", FlowSpelVo::getCreateTime); + return new TaskAssigneeDTO(page.getTotal(), handlers); + } + + /** + * 根据视图 SpEL 表达式列表,查询对应的备注信息 + * + * @param viewSpels SpEL 表达式列表 + * @return 映射表:key 为 SpEL 表达式,value 为对应备注;若为空则返回空 Map + */ + @Override + public Map selectRemarksBySpels(List viewSpels) { + if (CollUtil.isEmpty(viewSpels)) { + return Collections.emptyMap(); + } + List list = baseMapper.selectList( + new LambdaQueryWrapper() + .select(FlowSpel::getViewSpel, FlowSpel::getRemark) + .in(FlowSpel::getViewSpel, viewSpels) + ); + 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 ceefa8032..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; @@ -12,10 +13,9 @@ import org.dromara.common.core.domain.dto.TaskAssigneeDTO; import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.domain.model.TaskAssigneeBody; import org.dromara.common.core.enums.FormatsType; -import org.dromara.common.core.service.DeptService; -import org.dromara.common.core.service.TaskAssigneeService; -import org.dromara.common.core.service.UserService; +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; @@ -25,10 +25,12 @@ import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo; import org.dromara.warm.flow.ui.vo.HandlerSelectVo; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.enums.TaskAssigneeEnum; +import org.dromara.workflow.service.IFlwSpelService; import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.springframework.stereotype.Service; import java.util.*; +import java.util.stream.Collectors; /** * 流程设计器-获取办理人权限设置列表 @@ -45,6 +47,9 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand private final TaskAssigneeService taskAssigneeService; private final UserService userService; private final DeptService deptService; + private final RoleService roleService; + private final PostService postService; + private final IFlwSpelService spelService; /** * 获取办理人权限设置列表tabs页签 @@ -88,10 +93,10 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand return Collections.emptyList(); } // 解析并归类 ID,同时记录原始顺序和对应解析结果 - Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); - Map> parsedMap = new LinkedHashMap<>(); + 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()); @@ -99,14 +104,13 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand } // 查询所有类型对应的 ID 名称映射 - Map> nameMap = new EnumMap<>(TaskAssigneeEnum.class); + Map> nameMap = new EnumMap<>(TaskAssigneeEnum.class); typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids))); - // 组装返回结果,保持原始顺序 return parsedMap.entrySet().stream() .map(entry -> { String storageId = entry.getKey(); - Pair parsed = entry.getValue(); + Pair parsed = entry.getValue(); String handlerName = (parsed == null) ? null : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap()) .get(parsed.getValue()); @@ -123,6 +127,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand case ROLE -> taskAssigneeService.selectRolesByTaskAssigneeList(taskQuery); case DEPT -> taskAssigneeService.selectDeptsByTaskAssigneeList(taskQuery); case POST -> taskAssigneeService.selectPostsByTaskAssigneeList(taskQuery); + case SPEL -> spelService.selectSpelByTaskAssigneeList(taskQuery); }; } @@ -130,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())); } /** @@ -154,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())); } @@ -174,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()); } @@ -197,12 +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 USER -> userService.selectListByIds(longIds); + case ROLE -> userService.selectUsersByRoleIds(longIds); + case DEPT -> userService.selectUsersByDeptIds(longIds); + case POST -> userService.selectUsersByPostIds(longIds); + default -> new ArrayList<>(); }; } @@ -213,13 +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 -> userService.selectRoleNamesByIds(ids); - case DEPT -> userService.selectDeptNamesByIds(ids); - case POST -> userService.selectPostNamesByIds(ids); + 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 + )); } /** @@ -228,22 +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, "#")) { - 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 f96a75732..d41bfdf04 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; @@ -55,7 +62,6 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; -import java.util.stream.Collectors; import static org.dromara.workflow.common.constant.FlowConstant.*; @@ -86,6 +92,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { private final IFlwTaskAssigneeService flwTaskAssigneeService; private final IFlwCommonService flwCommonService; private final IFlwNodeExtService flwNodeExtService; + private final FlwInstanceBizExtMapper flwInstanceBizExtMapper; /** * 启动任务 @@ -99,15 +106,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (StringUtils.isBlank(businessId)) { throw new ServiceException("启动工作流时必须包含业务ID"); } + // 启动流程实例(提交申请) Map variables = startProcessBo.getVariables(); // 流程发起人 variables.put(INITIATOR, LoginHelper.getUserIdStr()); + // 发起人部门id + 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); @@ -115,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()); @@ -127,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) { @@ -138,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); + } + /** * 办理任务 * @@ -154,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); @@ -168,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); @@ -192,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); + } + } + } + /** * 设置弹窗处理人 * @@ -214,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()); @@ -235,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()); @@ -252,14 +345,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService { hisTask.setCreateTime(updateTime); hisTask.setUpdateTime(updateTime); hisTaskService.save(hisTask); - List userList = flowCopyList.stream() - .map(flowCopy -> { - FlowUser flowUser = new FlowUser(); - flowUser.setType(TaskAssigneeType.COPY.getCode()); - flowUser.setProcessedBy(String.valueOf(flowCopy.getUserId())); - flowUser.setAssociated(taskId); - return flowUser; - }).collect(Collectors.toList()); + List userList = StreamUtils.toList(flowCopyList, x -> + new FlowUser() + .setType(TaskAssigneeType.COPY.getCode()) + .setProcessedBy(Convert.toStr(x.getUserId())) + .setAssociated(taskId)); // 批量保存抄送人员 FlowEngine.userService().saveBatch(userList); } @@ -276,7 +366,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService { queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey()); queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr()); queryWrapper.in("t.flow_status", BusinessStatusEnum.WAITING.getStatus()); - Page page = this.getFlowTaskVoPage(pageQuery, queryWrapper); + Page page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper); + this.wrapAssigneeInfo(page.getRecords()); return TableDataInfo.build(page); } @@ -306,25 +397,27 @@ public class FlwTaskServiceImpl implements IFlwTaskService { public TableDataInfo pageByAllTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) { QueryWrapper queryWrapper = buildQueryWrapper(flowTaskBo); queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey()); - Page page = getFlowTaskVoPage(pageQuery, queryWrapper); + Page page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper); + this.wrapAssigneeInfo(page.getRecords()); return TableDataInfo.build(page); } - private Page getFlowTaskVoPage(PageQuery pageQuery, QueryWrapper queryWrapper) { - Page page = flwTaskMapper.getListRunTask(pageQuery.build(), queryWrapper); - List records = page.getRecords(); - if (CollUtil.isNotEmpty(records)) { - List taskIds = StreamUtils.toList(records, FlowTaskVo::getId); - Map> listMap = currentTaskAllUser(taskIds); - records.forEach(t -> { - List userList = listMap.getOrDefault(t.getId(), Collections.emptyList()); - if (CollUtil.isNotEmpty(userList)) { - t.setAssigneeIds(StreamUtils.join(userList, e -> String.valueOf(e.getUserId()))); - t.setAssigneeNames(StreamUtils.join(userList, UserDTO::getNickName)); - } - }); + /** + * 为流程任务列表封装处理人 ID(assigneeIds) + * + * @param taskList 流程任务列表 + */ + private void wrapAssigneeInfo(List taskList) { + if (CollUtil.isEmpty(taskList)) { + return; + } + List associatedUsers = FlowEngine.userService().getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId)); + Map> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated); + // 组装用户数据回任务列表 + for (FlowTaskVo task : taskList) { + List users = taskUserMap.get(task.getId()); + task.setAssigneeIds(StreamUtils.join(users, User::getProcessedBy)); } - return page; } /** @@ -415,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())); } @@ -474,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)); } /** @@ -501,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; @@ -527,39 +633,24 @@ public class FlwTaskServiceImpl implements IFlwTaskService { // 只获取中间节点 nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType())); if (CollUtil.isNotEmpty(nextNodeList)) { - // 构建以下节点数据 + //构建以下节点数据 List buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, FlowParams.build())); - // 办理人变量替换 - ExpressionUtil.evalVariable(buildNextTaskList, - FlowParams.build() - .variable(mergeVariable) - ); + //办理人变量替换 + ExpressionUtil.evalVariable(buildNextTaskList, FlowParams.build().variable(mergeVariable)); for (FlowNode flowNode : nextFlowNodes) { - buildNextTaskList.stream().filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())).findFirst().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()))); - } + Task first = StreamUtils.findFirst(buildNextTaskList, t -> t.getNodeCode().equals(flowNode.getNodeCode())); + if (ObjectUtil.isNotNull(first) && CollUtil.isNotEmpty(first.getPermissionList())) { + List users = flwTaskAssigneeService.fetchUsersByStorageIds(StringUtils.joinComma(first.getPermissionList())); + if (CollUtil.isNotEmpty(users)) { + flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId()))); } - }); + } + } } return nextFlowNodes; } - /** - * 按照任务id查询任务 - * - * @param taskIdList 任务id - * @return 结果 - */ - @Override - public List selectHisTaskByIdList(List taskIdList) { - return flowHisTaskMapper.selectList(new LambdaQueryWrapper<>(FlowHisTask.class) - .in(FlowHisTask::getId, taskIdList)); - } - /** * 按照任务id查询任务 * @@ -568,19 +659,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { */ @Override public FlowHisTask selectHisTaskById(Long taskId) { - return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class) - .eq(FlowHisTask::getId, taskId)); - } - - /** - * 按照实例id查询任务 - * - * @param instanceIdList 流程实例id - */ - @Override - public List selectByInstIdList(List instanceIdList) { - return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class) - .in(FlowTask::getInstanceId, instanceIdList)); + return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getId, taskId)); } /** @@ -590,8 +669,29 @@ 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)); + } + + /** + * 判断流程是否已结束(即该流程实例下是否还有未完成的任务) + * + * @param instanceId 流程实例ID + * @return true 表示任务已全部结束;false 表示仍有任务存在 + */ + @Override + public boolean isTaskEnd(Long instanceId) { + boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper().eq(FlowTask::getInstanceId, instanceId)); + return !exists; } /** @@ -603,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); } @@ -683,15 +782,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService { // 批量删除现有任务的办理人记录 if (CollUtil.isNotEmpty(flowTasks)) { FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId)); - List userList = flowTasks.stream() - .map(flowTask -> { - FlowUser flowUser = new FlowUser(); - flowUser.setType(TaskAssigneeType.APPROVER.getCode()); - flowUser.setProcessedBy(userId); - flowUser.setAssociated(flowTask.getId()); - return flowUser; - }) - .collect(Collectors.toList()); + List userList = StreamUtils.toList(flowTasks, flowTask -> + new FlowUser() + .setType(TaskAssigneeType.APPROVER.getCode()) + .setProcessedBy(userId) + .setAssociated(flowTask.getId())); if (CollUtil.isNotEmpty(userList)) { FlowEngine.userService().saveBatch(userList); } @@ -703,36 +798,15 @@ public class FlwTaskServiceImpl implements IFlwTaskService { return true; } - /** - * 获取任务所有办理人 - * - * @param taskIdList 任务id - */ - @Override - public Map> currentTaskAllUser(List taskIdList) { - Map> map = new HashMap<>(); - // 获取与当前任务关联的用户列表 - List associatedUsers = FlowEngine.userService().getByAssociateds(taskIdList); - Map> listMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated); - for (Map.Entry> entry : listMap.entrySet()) { - List value = entry.getValue(); - if (CollUtil.isNotEmpty(value)) { - List userDtoList = userService.selectListByIds(StreamUtils.toList(value, e -> Convert.toLong(e.getProcessedBy()))); - map.put(entry.getKey(), userDtoList); - } - } - return map; - } - /** * 获取当前任务的所有办理人 * - * @param taskId 任务id + * @param taskIds 任务id */ @Override - public List currentTaskAllUser(Long taskId) { + public List currentTaskAllUser(List taskIds) { // 获取与当前任务关联的用户列表 - List userList = FlowEngine.userService().getByAssociateds(Collections.singletonList(taskId)); + List userList = FlowEngine.userService().getByAssociateds(taskIds); if (CollUtil.isEmpty(userList)) { return Collections.emptyList(); } @@ -752,4 +826,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService { .eq(FlowNode::getDefinitionId, definitionId)); } + /** + * 催办任务 + * + * @param bo 参数 + */ + @Override + public boolean urgeTask(FlowUrgeTaskBo bo) { + try { + if (CollUtil.isEmpty(bo.getTaskIdList())) { + return false; + } + List userList = this.currentTaskAllUser(bo.getTaskIdList()); + if (CollUtil.isEmpty(userList)) { + return false; + } + List messageType = bo.getMessageType(); + String message = bo.getMessage(); + flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new ServiceException(e.getMessage()); + } + return true; + } } 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 17d1c7ca3..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,15 +4,18 @@ 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.event.ProcessTaskEvent; +import org.dromara.common.core.domain.dto.StartProcessDTO; 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; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; @@ -104,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()); @@ -115,6 +119,37 @@ public class TestLeaveServiceImpl implements ITestLeaveService { return MapstructUtils.convert(add, TestLeaveVo.class); } + @Transactional(rollbackFor = Exception.class) + @Override + public TestLeaveVo submitAndFlowStart(TestLeaveBo bo) { + long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true); + // 截止日期也算一天 + bo.setLeaveDays((int) day + 1); + 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); + + 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("流程发起异常"); + } + } + return MapstructUtils.convert(leave, TestLeaveVo.class); + } + /** * 修改请假 */ @@ -136,7 +171,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { } /** - * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) + * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等) * 正常使用只需#processEvent.flowCode=='leave1' * 示例为了方便则使用startsWith匹配了全部示例key * @@ -158,13 +193,17 @@ 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); } /** - * 执行任务创建监听 + * 执行任务创建监听(也代表上一条任务完成事件) * 示例:也可通过 @EventListener(condition = "#processTaskEvent.flowCode=='leave1'")进行判断 * 在方法中判断流程节点key * if ("xxx".equals(processTaskEvent.getNodeCode())) { 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/FlwSpelMapper.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml new file mode 100644 index 000000000..03355f6a3 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwSpelMapper.xml @@ -0,0 +1,7 @@ + + + + + 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_job.sql b/script/sql/oracle/oracle_ry_job.sql index 66943a934..84d31ec02 100644 --- a/script/sql/oracle/oracle_ry_job.sql +++ b/script/sql/oracle/oracle_ry_job.sql @@ -1,9 +1,8 @@ - /* SnailJob Database Transfer Tool Source Server Type : MySQL Target Server Type : Oracle - Date: 2025-04-26 10:01:54 + Date: 2025-06-21 23:33:11 */ @@ -146,18 +145,19 @@ COMMENT ON TABLE sj_notify_recipient IS '告警通知接收人'; -- sj_retry_dead_letter CREATE TABLE sj_retry_dead_letter ( - id number GENERATED ALWAYS AS IDENTITY, - namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL, - group_name varchar2(64) NULL, - group_id number NOT NULL, - scene_name varchar2(64) NULL, - scene_id number NOT NULL, - idempotent_id varchar2(64) NULL, - biz_no varchar2(64) DEFAULT '' NULL, - executor_name varchar2(512) DEFAULT '' NULL, - args_str clob NULL, - ext_attrs clob NULL, - create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL + id number GENERATED ALWAYS AS IDENTITY, + namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL, + group_name varchar2(64) NULL, + group_id number NOT NULL, + scene_name varchar2(64) NULL, + scene_id number NOT NULL, + idempotent_id varchar2(64) NULL, + biz_no varchar2(64) DEFAULT '' NULL, + executor_name varchar2(512) DEFAULT '' NULL, + serializer_name varchar2(32) DEFAULT 'jackson' NULL, + args_str clob NULL, + ext_attrs clob NULL, + create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL ); ALTER TABLE sj_retry_dead_letter @@ -177,6 +177,7 @@ COMMENT ON COLUMN sj_retry_dead_letter.scene_id IS '场景ID'; COMMENT ON COLUMN sj_retry_dead_letter.idempotent_id IS '幂等id'; COMMENT ON COLUMN sj_retry_dead_letter.biz_no IS '业务编号'; COMMENT ON COLUMN sj_retry_dead_letter.executor_name IS '执行器名称'; +COMMENT ON COLUMN sj_retry_dead_letter.serializer_name IS '执行方法参数序列化器名称'; COMMENT ON COLUMN sj_retry_dead_letter.args_str IS '执行方法参数'; COMMENT ON COLUMN sj_retry_dead_letter.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_retry_dead_letter.create_dt IS '创建时间'; @@ -196,6 +197,7 @@ CREATE TABLE sj_retry executor_name varchar2(512) DEFAULT '' NULL, args_str clob NULL, ext_attrs clob NULL, + serializer_name varchar2(32) DEFAULT 'jackson' NULL, next_trigger_at number NOT NULL, retry_count number DEFAULT 0 NOT NULL, retry_status smallint DEFAULT 0 NOT NULL, @@ -213,10 +215,10 @@ ALTER TABLE sj_retry CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted); CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no); -CREATE INDEX idx_sj_retry_02 ON sj_retry (retry_status, bucket_index); -CREATE INDEX idx_sj_retry_03 ON sj_retry (parent_id); -CREATE INDEX idx_sj_retry_04 ON sj_retry (create_dt); -CREATE INDEX idx_sj_retry_05 ON sj_retry (idempotent_id); +CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id); +CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index); +CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id); +CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt); COMMENT ON COLUMN sj_retry.id IS '主键'; COMMENT ON COLUMN sj_retry.namespace_id IS '命名空间id'; @@ -229,6 +231,7 @@ COMMENT ON COLUMN sj_retry.biz_no IS '业务编号'; COMMENT ON COLUMN sj_retry.executor_name IS '执行器名称'; COMMENT ON COLUMN sj_retry.args_str IS '执行方法参数'; COMMENT ON COLUMN sj_retry.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_retry.serializer_name IS '执行方法参数序列化器名称'; COMMENT ON COLUMN sj_retry.next_trigger_at IS '下次触发时间'; COMMENT ON COLUMN sj_retry.retry_count IS '重试次数'; COMMENT ON COLUMN sj_retry.retry_status IS '重试状态 0、重试中 1、成功 2、最大重试次数'; @@ -296,8 +299,8 @@ CREATE TABLE sj_retry_task_log_message ALTER TABLE sj_retry_task_log_message ADD CONSTRAINT pk_sj_retry_task_log_message PRIMARY KEY (id); -CREATE INDEX idx_sj_rt_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, retry_task_id); -CREATE INDEX idx_sj_rt_log_message_02 ON sj_retry_task_log_message (create_dt); +CREATE INDEX idx_sj_retry_task_log_message_01 ON sj_retry_task_log_message (namespace_id, group_name, retry_task_id); +CREATE INDEX idx_sj_retry_task_log_message_02 ON sj_retry_task_log_message (create_dt); COMMENT ON COLUMN sj_retry_task_log_message.id IS '主键'; COMMENT ON COLUMN sj_retry_task_log_message.namespace_id IS '命名空间id'; @@ -330,6 +333,8 @@ CREATE TABLE sj_retry_scene_config cb_trigger_type smallint DEFAULT 1 NOT NULL, cb_max_count number DEFAULT 16 NOT NULL, cb_trigger_interval varchar2(16) DEFAULT '' NULL, + owner_id number DEFAULT NULL NULL, + labels varchar2(512) DEFAULT '' NULL, description varchar2(256) DEFAULT '' NULL, create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL, update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL @@ -357,6 +362,8 @@ COMMENT ON COLUMN sj_retry_scene_config.cb_status IS '回调状态 0、不开启 COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_type IS '1、默认等级 2、固定间隔时间 3、CRON 表达式'; COMMENT ON COLUMN sj_retry_scene_config.cb_max_count IS '回调的最大执行次数'; COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_interval IS '回调的最大执行次数'; +COMMENT ON COLUMN sj_retry_scene_config.owner_id IS '负责人id'; +COMMENT ON COLUMN sj_retry_scene_config.labels IS '标签'; COMMENT ON COLUMN sj_retry_scene_config.description IS '描述'; COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '创建时间'; COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '修改时间'; @@ -374,6 +381,7 @@ CREATE TABLE sj_server_node expire_at date NOT NULL, node_type smallint NOT NULL, ext_attrs varchar2(256) DEFAULT '' NULL, + labels varchar2(512) DEFAULT '' NULL, create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL, update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL ); @@ -395,6 +403,7 @@ COMMENT ON COLUMN sj_server_node.host_port IS '机器端口'; COMMENT ON COLUMN sj_server_node.expire_at IS '过期时间'; COMMENT ON COLUMN sj_server_node.node_type IS '节点类型 1、客户端 2、是服务端'; COMMENT ON COLUMN sj_server_node.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_server_node.labels IS '标签'; COMMENT ON COLUMN sj_server_node.create_dt IS '创建时间'; COMMENT ON COLUMN sj_server_node.update_dt IS '修改时间'; COMMENT ON TABLE sj_server_node IS '服务器节点'; @@ -402,7 +411,7 @@ COMMENT ON TABLE sj_server_node IS '服务器节点'; -- sj_distributed_lock CREATE TABLE sj_distributed_lock ( - name varchar2(64) NULL, + name varchar2(64) NOT NULL, lock_until timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL, locked_at timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL, locked_by varchar2(255) NULL, @@ -443,8 +452,8 @@ COMMENT ON COLUMN sj_system_user.create_dt IS '创建时间'; COMMENT ON COLUMN sj_system_user.update_dt IS '修改时间'; COMMENT ON TABLE sj_system_user IS '系统用户表'; --- pwd: admin -INSERT INTO sj_system_user(username, password, role, create_dt, update_dt) VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2, sysdate, sysdate); +INSERT INTO sj_system_user (username, password, role) +VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2); -- sj_system_user_permission CREATE TABLE sj_system_user_permission @@ -460,7 +469,7 @@ CREATE TABLE sj_system_user_permission ALTER TABLE sj_system_user_permission ADD CONSTRAINT pk_sj_system_user_permission PRIMARY KEY (id); -CREATE UNIQUE INDEX uk_sj_su_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id); +CREATE UNIQUE INDEX uk_sj_system_user_permission_01 ON sj_system_user_permission (namespace_id, group_name, system_user_id); COMMENT ON COLUMN sj_system_user_permission.id IS '主键'; COMMENT ON COLUMN sj_system_user_permission.group_name IS '组名称'; @@ -470,30 +479,6 @@ COMMENT ON COLUMN sj_system_user_permission.create_dt IS '创建时间'; COMMENT ON COLUMN sj_system_user_permission.update_dt IS '修改时间'; COMMENT ON TABLE sj_system_user_permission IS '系统用户权限表'; --- sj_sequence_alloc -CREATE TABLE sj_sequence_alloc -( - id number GENERATED ALWAYS AS IDENTITY, - namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL, - group_name varchar2(64) DEFAULT '' NULL, - max_id number DEFAULT 1 NOT NULL, - step number DEFAULT 100 NOT NULL, - update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL -); - -ALTER TABLE sj_sequence_alloc - ADD CONSTRAINT pk_sj_sequence_alloc PRIMARY KEY (id); - -CREATE UNIQUE INDEX uk_sj_sequence_alloc_01 ON sj_sequence_alloc (namespace_id, group_name); - -COMMENT ON COLUMN sj_sequence_alloc.id IS '主键'; -COMMENT ON COLUMN sj_sequence_alloc.namespace_id IS '命名空间id'; -COMMENT ON COLUMN sj_sequence_alloc.group_name IS '组名称'; -COMMENT ON COLUMN sj_sequence_alloc.max_id IS '最大id'; -COMMENT ON COLUMN sj_sequence_alloc.step IS '步长'; -COMMENT ON COLUMN sj_sequence_alloc.update_dt IS '更新时间'; -COMMENT ON TABLE sj_sequence_alloc IS '号段模式序号ID分配表'; - -- sj_job CREATE TABLE sj_job ( @@ -519,7 +504,8 @@ CREATE TABLE sj_job bucket_index number DEFAULT 0 NOT NULL, resident smallint DEFAULT 0 NOT NULL, notify_ids varchar2(128) DEFAULT '' NULL, - owner_id number NULL, + owner_id number DEFAULT NULL NULL, + labels varchar2(512) DEFAULT '' NULL, description varchar2(256) DEFAULT '' NULL, ext_attrs varchar2(256) DEFAULT '' NULL, deleted smallint DEFAULT 0 NOT NULL, @@ -557,6 +543,7 @@ COMMENT ON COLUMN sj_job.bucket_index IS 'bucket'; COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务'; COMMENT ON COLUMN sj_job.notify_ids IS '通知告警场景配置id列表'; COMMENT ON COLUMN sj_job.owner_id IS '负责人id'; +COMMENT ON COLUMN sj_job.labels IS '标签'; COMMENT ON COLUMN sj_job.description IS '描述'; COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除'; @@ -564,7 +551,7 @@ COMMENT ON COLUMN sj_job.create_dt IS '创建时间'; COMMENT ON COLUMN sj_job.update_dt IS '修改时间'; COMMENT ON TABLE sj_job IS '任务信息'; -INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1,'', '', 0, sysdate, sysdate); +INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0, sysdate, sysdate); -- sj_job_log_message CREATE TABLE sj_job_log_message @@ -800,6 +787,7 @@ CREATE TABLE sj_workflow notify_ids varchar2(128) DEFAULT '' NULL, bucket_index number DEFAULT 0 NOT NULL, version number NOT NULL, + owner_id number DEFAULT NULL NULL, ext_attrs varchar2(256) DEFAULT '' NULL, deleted smallint DEFAULT 0 NOT NULL, create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -828,6 +816,7 @@ COMMENT ON COLUMN sj_workflow.wf_context IS '上下文'; COMMENT ON COLUMN sj_workflow.notify_ids IS '通知告警场景配置id列表'; COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket'; COMMENT ON COLUMN sj_workflow.version IS '版本号'; +COMMENT ON COLUMN sj_workflow.owner_id IS '负责人id'; COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_workflow.deleted IS '逻辑删除 1、删除'; COMMENT ON COLUMN sj_workflow.create_dt IS '创建时间'; @@ -922,3 +911,30 @@ COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除'; COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间'; COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间'; COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次'; + +-- sj_job_executor +CREATE TABLE sj_job_executor +( + id number GENERATED ALWAYS AS IDENTITY, + namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL, + group_name varchar2(64) NULL, + executor_info varchar2(256) NULL, + executor_type varchar2(3) NULL, + create_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_dt date DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +ALTER TABLE sj_job_executor + ADD CONSTRAINT pk_sj_job_executor PRIMARY KEY (id); + +CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name); +CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt); + +COMMENT ON COLUMN sj_job_executor.id IS '主键'; +COMMENT ON COLUMN sj_job_executor.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_executor.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_executor.executor_info IS '任务执行器名称'; +COMMENT ON COLUMN sj_job_executor.executor_type IS '1:java 2:python 3:go'; +COMMENT ON COLUMN sj_job_executor.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job_executor.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job_executor IS '任务执行器信息'; diff --git a/script/sql/oracle/oracle_ry_workflow.sql b/script/sql/oracle/oracle_ry_workflow.sql index 4367b24d4..92c6b546f 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', @@ -338,6 +340,78 @@ INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '外出', 4, INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, SYSDATE, NULL, NULL); INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, SYSDATE, NULL, NULL); +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id NUMBER(20) NOT NULL, + component_name VARCHAR2(255), + method_name VARCHAR2(255), + method_params VARCHAR2(255), + view_spel VARCHAR2(255), + remark VARCHAR2(255), + status CHAR(1) DEFAULT '0', + del_flag CHAR(1) DEFAULT '0', + create_dept NUMBER(20), + create_by NUMBER(20), + create_time DATE, + update_by NUMBER(20), + update_time DATE +); + +alter table flow_spel add constraint pk_flow_spel primary key (id); + +COMMENT ON TABLE flow_spel IS '流程spel表达式定义表'; +COMMENT ON COLUMN flow_spel.id IS '主键id'; +COMMENT ON COLUMN flow_spel.component_name IS '组件名称'; +COMMENT ON COLUMN flow_spel.method_name IS '方法名'; +COMMENT ON COLUMN flow_spel.method_params IS '参数'; +COMMENT ON COLUMN flow_spel.view_spel IS '预览spel表达式'; +COMMENT ON COLUMN flow_spel.remark IS '备注'; +COMMENT ON COLUMN flow_spel.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN flow_spel.del_flag IS '删除标志'; +COMMENT ON COLUMN flow_spel.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_spel.create_by IS '创建者'; +COMMENT ON COLUMN flow_spel.create_time IS '创建时间'; +COMMENT ON COLUMN flow_spel.update_by IS '更新者'; +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); + +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +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'; -- ---------------------------- -- 请假单信息 @@ -346,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, @@ -364,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 '结束时间'; @@ -396,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_job.sql b/script/sql/postgres/postgres_ry_job.sql index 10612e65c..8cd32a850 100644 --- a/script/sql/postgres/postgres_ry_job.sql +++ b/script/sql/postgres/postgres_ry_job.sql @@ -2,7 +2,7 @@ SnailJob Database Transfer Tool Source Server Type : MySQL Target Server Type : PostgreSQL - Date: 2025-04-26 09:56:45 + Date: 2025-06-21 23:23:10 */ @@ -133,18 +133,19 @@ COMMENT ON TABLE sj_notify_recipient IS '告警通知接收人'; -- sj_retry_dead_letter CREATE TABLE sj_retry_dead_letter ( - id bigserial PRIMARY KEY, - namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', - group_name varchar(64) NOT NULL, - group_id bigint NOT NULL, - scene_name varchar(64) NOT NULL, - scene_id bigint NOT NULL, - idempotent_id varchar(64) NOT NULL, - biz_no varchar(64) NOT NULL DEFAULT '', - executor_name varchar(512) NOT NULL DEFAULT '', - args_str text NOT NULL, - ext_attrs text NOT NULL, - create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + group_id bigint NOT NULL, + scene_name varchar(64) NOT NULL, + scene_id bigint NOT NULL, + idempotent_id varchar(64) NOT NULL, + biz_no varchar(64) NOT NULL DEFAULT '', + executor_name varchar(512) NOT NULL DEFAULT '', + serializer_name varchar(32) NOT NULL DEFAULT 'jackson', + args_str text NOT NULL, + ext_attrs text NOT NULL, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_sj_retry_dead_letter_01 ON sj_retry_dead_letter (namespace_id, group_name, scene_name); @@ -161,6 +162,7 @@ COMMENT ON COLUMN sj_retry_dead_letter.scene_id IS '场景ID'; COMMENT ON COLUMN sj_retry_dead_letter.idempotent_id IS '幂等id'; COMMENT ON COLUMN sj_retry_dead_letter.biz_no IS '业务编号'; COMMENT ON COLUMN sj_retry_dead_letter.executor_name IS '执行器名称'; +COMMENT ON COLUMN sj_retry_dead_letter.serializer_name IS '执行方法参数序列化器名称'; COMMENT ON COLUMN sj_retry_dead_letter.args_str IS '执行方法参数'; COMMENT ON COLUMN sj_retry_dead_letter.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_retry_dead_letter.create_dt IS '创建时间'; @@ -180,6 +182,7 @@ CREATE TABLE sj_retry executor_name varchar(512) NOT NULL DEFAULT '', args_str text NOT NULL, ext_attrs text NOT NULL, + serializer_name varchar(32) NOT NULL DEFAULT 'jackson', next_trigger_at bigint NOT NULL, retry_count int NOT NULL DEFAULT 0, retry_status smallint NOT NULL DEFAULT 0, @@ -194,11 +197,10 @@ CREATE TABLE sj_retry CREATE UNIQUE INDEX uk_sj_retry_01 ON sj_retry (scene_id, task_type, idempotent_id, deleted); CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no); -CREATE INDEX idx_sj_retry_02 ON sj_retry (retry_status, bucket_index); -CREATE INDEX idx_sj_retry_03 ON sj_retry (parent_id); -CREATE INDEX idx_sj_retry_04 ON sj_retry (create_dt); -CREATE INDEX idx_sj_retry_05 ON sj_retry (idempotent_id); - +CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id); +CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index); +CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id); +CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt); COMMENT ON COLUMN sj_retry.id IS '主键'; COMMENT ON COLUMN sj_retry.namespace_id IS '命名空间id'; @@ -211,6 +213,7 @@ COMMENT ON COLUMN sj_retry.biz_no IS '业务编号'; COMMENT ON COLUMN sj_retry.executor_name IS '执行器名称'; COMMENT ON COLUMN sj_retry.args_str IS '执行方法参数'; COMMENT ON COLUMN sj_retry.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_retry.serializer_name IS '执行方法参数序列化器名称'; COMMENT ON COLUMN sj_retry.next_trigger_at IS '下次触发时间'; COMMENT ON COLUMN sj_retry.retry_count IS '重试次数'; COMMENT ON COLUMN sj_retry.retry_status IS '重试状态 0、重试中 1、成功 2、最大重试次数'; @@ -306,6 +309,8 @@ CREATE TABLE sj_retry_scene_config cb_trigger_type smallint NOT NULL DEFAULT 1, cb_max_count int NOT NULL DEFAULT 16, cb_trigger_interval varchar(16) NOT NULL DEFAULT '', + owner_id bigint NULL DEFAULT NULL, + labels varchar(512) NULL DEFAULT '', description varchar(256) NOT NULL DEFAULT '', create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP @@ -330,6 +335,8 @@ COMMENT ON COLUMN sj_retry_scene_config.cb_status IS '回调状态 0、不开启 COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_type IS '1、默认等级 2、固定间隔时间 3、CRON 表达式'; COMMENT ON COLUMN sj_retry_scene_config.cb_max_count IS '回调的最大执行次数'; COMMENT ON COLUMN sj_retry_scene_config.cb_trigger_interval IS '回调的最大执行次数'; +COMMENT ON COLUMN sj_retry_scene_config.owner_id IS '负责人id'; +COMMENT ON COLUMN sj_retry_scene_config.labels IS '标签'; COMMENT ON COLUMN sj_retry_scene_config.description IS '描述'; COMMENT ON COLUMN sj_retry_scene_config.create_dt IS '创建时间'; COMMENT ON COLUMN sj_retry_scene_config.update_dt IS '修改时间'; @@ -347,6 +354,7 @@ CREATE TABLE sj_server_node expire_at timestamp NOT NULL, node_type smallint NOT NULL, ext_attrs varchar(256) NULL DEFAULT '', + labels varchar(512) NULL DEFAULT '', create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -365,6 +373,7 @@ COMMENT ON COLUMN sj_server_node.host_port IS '机器端口'; COMMENT ON COLUMN sj_server_node.expire_at IS '过期时间'; COMMENT ON COLUMN sj_server_node.node_type IS '节点类型 1、客户端 2、是服务端'; COMMENT ON COLUMN sj_server_node.ext_attrs IS '扩展字段'; +COMMENT ON COLUMN sj_server_node.labels IS '标签'; COMMENT ON COLUMN sj_server_node.create_dt IS '创建时间'; COMMENT ON COLUMN sj_server_node.update_dt IS '修改时间'; COMMENT ON TABLE sj_server_node IS '服务器节点'; @@ -372,7 +381,7 @@ COMMENT ON TABLE sj_server_node IS '服务器节点'; -- sj_distributed_lock CREATE TABLE sj_distributed_lock ( - name varchar(64) NOT NULL, + name varchar(64) NOT NULL PRIMARY KEY, lock_until timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_at timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by varchar(255) NOT NULL, @@ -407,8 +416,8 @@ COMMENT ON COLUMN sj_system_user.create_dt IS '创建时间'; COMMENT ON COLUMN sj_system_user.update_dt IS '修改时间'; COMMENT ON TABLE sj_system_user IS '系统用户表'; --- pwd: admin -INSERT INTO sj_system_user VALUES (1, 'admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2, now(), now()); +INSERT INTO sj_system_user (username, password, role) +VALUES ('admin', '465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2); -- sj_system_user_permission CREATE TABLE sj_system_user_permission @@ -431,27 +440,6 @@ COMMENT ON COLUMN sj_system_user_permission.create_dt IS '创建时间'; COMMENT ON COLUMN sj_system_user_permission.update_dt IS '修改时间'; COMMENT ON TABLE sj_system_user_permission IS '系统用户权限表'; --- sj_sequence_alloc -CREATE TABLE sj_sequence_alloc -( - id bigserial PRIMARY KEY, - namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', - group_name varchar(64) NOT NULL DEFAULT '', - max_id bigint NOT NULL DEFAULT 1, - step int NOT NULL DEFAULT 100, - update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE UNIQUE INDEX uk_sj_sequence_alloc_01 ON sj_sequence_alloc (namespace_id, group_name); - -COMMENT ON COLUMN sj_sequence_alloc.id IS '主键'; -COMMENT ON COLUMN sj_sequence_alloc.namespace_id IS '命名空间id'; -COMMENT ON COLUMN sj_sequence_alloc.group_name IS '组名称'; -COMMENT ON COLUMN sj_sequence_alloc.max_id IS '最大id'; -COMMENT ON COLUMN sj_sequence_alloc.step IS '步长'; -COMMENT ON COLUMN sj_sequence_alloc.update_dt IS '更新时间'; -COMMENT ON TABLE sj_sequence_alloc IS '号段模式序号ID分配表'; - -- sj_job CREATE TABLE sj_job ( @@ -477,7 +465,8 @@ CREATE TABLE sj_job bucket_index int NOT NULL DEFAULT 0, resident smallint NOT NULL DEFAULT 0, notify_ids varchar(128) NOT NULL DEFAULT '', - owner_id bigint NULL, + owner_id bigint NULL DEFAULT NULL, + labels varchar(512) NULL DEFAULT '', description varchar(256) NOT NULL DEFAULT '', ext_attrs varchar(256) NULL DEFAULT '', deleted smallint NOT NULL DEFAULT 0, @@ -512,6 +501,7 @@ COMMENT ON COLUMN sj_job.bucket_index IS 'bucket'; COMMENT ON COLUMN sj_job.resident IS '是否是常驻任务'; COMMENT ON COLUMN sj_job.notify_ids IS '通知告警场景配置id列表'; COMMENT ON COLUMN sj_job.owner_id IS '负责人id'; +COMMENT ON COLUMN sj_job.labels IS '标签'; COMMENT ON COLUMN sj_job.description IS '描述'; COMMENT ON COLUMN sj_job.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_job.deleted IS '逻辑删除 1、删除'; @@ -519,7 +509,7 @@ COMMENT ON COLUMN sj_job.create_dt IS '创建时间'; COMMENT ON COLUMN sj_job.update_dt IS '修改时间'; COMMENT ON TABLE sj_job IS '任务信息'; -INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', 0, now(), now()); +INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', '', 0, now(), now()); -- sj_job_log_message CREATE TABLE sj_job_log_message @@ -740,6 +730,7 @@ CREATE TABLE sj_workflow notify_ids varchar(128) NOT NULL DEFAULT '', bucket_index int NOT NULL DEFAULT 0, version int NOT NULL, + owner_id bigint NULL DEFAULT NULL, ext_attrs varchar(256) NULL DEFAULT '', deleted smallint NOT NULL DEFAULT 0, create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -765,6 +756,7 @@ COMMENT ON COLUMN sj_workflow.wf_context IS '上下文'; COMMENT ON COLUMN sj_workflow.notify_ids IS '通知告警场景配置id列表'; COMMENT ON COLUMN sj_workflow.bucket_index IS 'bucket'; COMMENT ON COLUMN sj_workflow.version IS '版本号'; +COMMENT ON COLUMN sj_workflow.owner_id IS '负责人id'; COMMENT ON COLUMN sj_workflow.ext_attrs IS '扩展字段'; COMMENT ON COLUMN sj_workflow.deleted IS '逻辑删除 1、删除'; COMMENT ON COLUMN sj_workflow.create_dt IS '创建时间'; @@ -853,3 +845,27 @@ COMMENT ON COLUMN sj_workflow_task_batch.deleted IS '逻辑删除 1、删除'; COMMENT ON COLUMN sj_workflow_task_batch.create_dt IS '创建时间'; COMMENT ON COLUMN sj_workflow_task_batch.update_dt IS '修改时间'; COMMENT ON TABLE sj_workflow_task_batch IS '工作流批次'; + +-- sj_job_executor +CREATE TABLE sj_job_executor +( + id bigserial PRIMARY KEY, + namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name varchar(64) NOT NULL, + executor_info varchar(256) NOT NULL, + executor_type varchar(3) NOT NULL, + create_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name); +CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt); + +COMMENT ON COLUMN sj_job_executor.id IS '主键'; +COMMENT ON COLUMN sj_job_executor.namespace_id IS '命名空间id'; +COMMENT ON COLUMN sj_job_executor.group_name IS '组名称'; +COMMENT ON COLUMN sj_job_executor.executor_info IS '任务执行器名称'; +COMMENT ON COLUMN sj_job_executor.executor_type IS '1:java 2:python 3:go'; +COMMENT ON COLUMN sj_job_executor.create_dt IS '创建时间'; +COMMENT ON COLUMN sj_job_executor.update_dt IS '修改时间'; +COMMENT ON TABLE sj_job_executor IS '任务执行器信息'; diff --git a/script/sql/postgres/postgres_ry_workflow.sql b/script/sql/postgres/postgres_ry_workflow.sql index 6662b794a..8454c8f7b 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); @@ -322,6 +321,77 @@ INSERT INTO flow_category VALUES (107, '000000', 101, '0,100,101', '外出', 4, INSERT INTO flow_category VALUES (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, now(), NULL, NULL); INSERT INTO flow_category VALUES (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, now(), NULL, NULL); +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id int8 NOT NULL, + component_name VARCHAR(255), + method_name VARCHAR(255), + method_params VARCHAR(255), + view_spel VARCHAR(255), + remark VARCHAR(255), + status CHAR(1) DEFAULT '0', + del_flag CHAR(1) DEFAULT '0', + create_dept int8, + create_by int8, + create_time TIMESTAMP, + update_by int8, + update_time TIMESTAMP, + PRIMARY KEY (id) +); + +COMMENT ON TABLE flow_spel IS '流程spel表达式定义表'; +COMMENT ON COLUMN flow_spel.id IS '主键id'; +COMMENT ON COLUMN flow_spel.component_name IS '组件名称'; +COMMENT ON COLUMN flow_spel.method_name IS '方法名'; +COMMENT ON COLUMN flow_spel.method_params IS '参数'; +COMMENT ON COLUMN flow_spel.view_spel IS '预览spel表达式'; +COMMENT ON COLUMN flow_spel.remark IS '备注'; +COMMENT ON COLUMN flow_spel.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN flow_spel.del_flag IS '删除标志'; +COMMENT ON COLUMN flow_spel.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_spel.create_by IS '创建者'; +COMMENT ON COLUMN flow_spel.create_time IS '创建时间'; +COMMENT ON COLUMN flow_spel.update_by IS '更新者'; +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()); + +-- ---------------------------- +-- 流程实例业务扩展表 +-- ---------------------------- +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'; + -- ---------------------------- -- 请假单信息 -- ---------------------------- @@ -329,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, @@ -346,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 '结束时间'; @@ -377,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_job.sql b/script/sql/ry_job.sql index dd86deb45..1e75571d1 100644 --- a/script/sql/ry_job.sql +++ b/script/sql/ry_job.sql @@ -81,18 +81,20 @@ CREATE TABLE `sj_notify_recipient` CREATE TABLE `sj_retry_dead_letter` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', - `group_name` varchar(64) NOT NULL COMMENT '组名称', - `group_id` bigint(20) NOT NULL COMMENT '组Id', - `scene_name` varchar(64) NOT NULL COMMENT '场景名称', - `scene_id` bigint(20) NOT NULL COMMENT '场景ID', - `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', - `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', - `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', - `args_str` text NOT NULL COMMENT '执行方法参数', - `ext_attrs` text NOT NULL COMMENT '扩展字段', - `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `group_id` bigint(20) NOT NULL COMMENT '组Id', + `scene_name` varchar(64) NOT NULL COMMENT '场景名称', + `scene_id` bigint(20) NOT NULL COMMENT '场景ID', + `idempotent_id` varchar(64) NOT NULL COMMENT '幂等id', + `biz_no` varchar(64) NOT NULL DEFAULT '' COMMENT '业务编号', + `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', + -- jackson 兼容历史数据 预计1.8.0默认改为fury + `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称', + `args_str` text NOT NULL COMMENT '执行方法参数', + `ext_attrs` text NOT NULL COMMENT '扩展字段', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_namespace_id_group_name_scene_name` (`namespace_id`, `group_name`, `scene_name`), KEY `idx_idempotent_id` (`idempotent_id`), @@ -115,6 +117,8 @@ CREATE TABLE `sj_retry` `executor_name` varchar(512) NOT NULL DEFAULT '' COMMENT '执行器名称', `args_str` text NOT NULL COMMENT '执行方法参数', `ext_attrs` text NOT NULL COMMENT '扩展字段', + -- jackson 兼容历史数据 预计1.8.0默认改为fury + `serializer_name` varchar(32) NOT NULL DEFAULT 'jackson' COMMENT '执行方法参数序列化器名称', `next_trigger_at` bigint(13) NOT NULL COMMENT '下次触发时间', `retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '重试次数', `retry_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '重试状态 0、重试中 1、成功 2、最大重试次数', @@ -195,6 +199,8 @@ CREATE TABLE `sj_retry_scene_config` `cb_trigger_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式', `cb_max_count` int(11) NOT NULL DEFAULT 16 COMMENT '回调的最大执行次数', `cb_trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '回调的最大执行次数', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', @@ -215,6 +221,7 @@ CREATE TABLE `sj_server_node` `expire_at` datetime NOT NULL COMMENT '过期时间', `node_type` tinyint(4) NOT NULL COMMENT '节点类型 1、客户端 2、是服务端', `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), @@ -268,19 +275,6 @@ CREATE TABLE `sj_system_user_permission` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='系统用户权限表'; -CREATE TABLE `sj_sequence_alloc` -( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', - `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '组名称', - `max_id` bigint(20) NOT NULL DEFAULT 1 COMMENT '最大id', - `step` int(11) NOT NULL DEFAULT 100 COMMENT '步长', - `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_namespace_id_group_name` (`namespace_id`, `group_name`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 COMMENT ='号段模式序号ID分配表'; - -- 分布式调度DDL CREATE TABLE `sj_job` ( @@ -306,7 +300,8 @@ CREATE TABLE `sj_job` `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', `resident` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否是常驻任务', `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表', - `owner_id` bigint(20) NULL COMMENT '负责人id', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', + `labels` varchar(512) NULL DEFAULT '' COMMENT '标签', `description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述', `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', @@ -320,7 +315,7 @@ CREATE TABLE `sj_job` AUTO_INCREMENT = 0 DEFAULT CHARSET = utf8mb4 COMMENT ='任务信息'; -INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', 0 , now(), now()); +INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0 , now(), now()); CREATE TABLE `sj_job_log_message` ( @@ -461,6 +456,7 @@ CREATE TABLE `sj_workflow` `notify_ids` varchar(128) NOT NULL DEFAULT '' COMMENT '通知告警场景配置id列表', `bucket_index` int(11) NOT NULL DEFAULT 0 COMMENT 'bucket', `version` int(11) NOT NULL COMMENT '版本号', + `owner_id` bigint(20) NULL DEFAULT NULL COMMENT '负责人id', `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', @@ -510,7 +506,7 @@ CREATE TABLE `sj_workflow_task_batch` `wf_context` text DEFAULT NULL COMMENT '全局上下文', `execution_at` bigint(13) NOT NULL DEFAULT 0 COMMENT '任务执行时间', `ext_attrs` varchar(256) NULL DEFAULT '' COMMENT '扩展字段', - `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号', + `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本号', `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '逻辑删除 1、删除', `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', @@ -521,3 +517,19 @@ CREATE TABLE `sj_workflow_task_batch` ) ENGINE = InnoDB AUTO_INCREMENT = 0 DEFAULT CHARSET = utf8mb4 COMMENT ='工作流批次'; + +CREATE TABLE `sj_job_executor` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id', + `group_name` varchar(64) NOT NULL COMMENT '组名称', + `executor_info` varchar(256) NOT NULL COMMENT '任务执行器名称', + `executor_type` varchar(3) NOT NULL COMMENT '1:java 2:python 3:go', + `create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`), + KEY `idx_create_dt` (`create_dt`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT ='任务执行器信息'; diff --git a/script/sql/ry_workflow.sql b/script/sql/ry_workflow.sql index bb3d76e68..dd1380a79 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否)', @@ -182,13 +183,59 @@ INSERT INTO flow_category values (107, '000000', 101, '0,100,101', '外出', 4, INSERT INTO flow_category values (108, '000000', 102, '0,100,102', '转正', 1, '0', 103, 1, sysdate(), null, null); INSERT INTO flow_category values (109, '000000', 102, '0,100,102', '离职', 2, '0', 103, 1, sysdate(), null, null); +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- + +CREATE TABLE flow_spel ( + id bigint(20) NOT NULL COMMENT '主键id', + component_name varchar(255) DEFAULT NULL COMMENT '组件名称', + method_name varchar(255) DEFAULT NULL COMMENT '方法名', + method_params varchar(255) DEFAULT NULL COMMENT '参数', + view_spel varchar(255) DEFAULT NULL COMMENT '预览spel表达式', + remark varchar(255) DEFAULT NULL COMMENT '备注', + status char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', + del_flag char(1) DEFAULT '0' COMMENT '删除标志', + create_dept bigint(20) DEFAULT NULL COMMENT '创建部门', + create_by bigint(20) DEFAULT NULL COMMENT '创建者', + create_time datetime DEFAULT NULL COMMENT '创建时间', + update_by bigint(20) DEFAULT NULL COMMENT '更新者', + update_time datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (id) +) ENGINE = InnoDB COMMENT='流程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 '结束时间', @@ -211,6 +258,7 @@ insert into sys_menu values ('11633', '我的抄送', '11618', '4', 'taskCopyLis insert into sys_menu values ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 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 ('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, ''); @@ -222,13 +270,19 @@ 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,sysdate(), null, null, ''); 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 ('11802', '流程达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11803', '流程达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11804', '流程达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11805', '流程达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, sysdate(), NULL, NULL, ''); +INSERT INTO sys_menu VALUES ('11806', '流程达式定义导出', '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, ''); -insert into sys_menu VALUES (11641, '请假申请修改', 11638, 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, ''); -insert into sys_menu VALUES (11642, '请假申请删除', 11638, 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, ''); -insert into sys_menu VALUES (11643, '请假申请导出', 11638, 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave: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, ''); +insert into sys_menu VALUES ('11641', '请假申请修改', '11638', 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu VALUES ('11642', '请假申请删除', '11638', 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:remove', '#', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu VALUES ('11643', '请假申请导出', '11638', 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, sysdate(), NULL, NULL, ''); INSERT INTO sys_dict_type VALUES (13, '000000', '业务状态', 'wf_business_status', 103, 1, sysdate(), NULL, NULL, '业务状态列表'); INSERT INTO sys_dict_type VALUES (14, '000000', '表单类型', 'wf_form_type', 103, 1, sysdate(), NULL, NULL, '表单类型列表'); diff --git a/script/sql/sqlserver/sqlserver_ry_job.sql b/script/sql/sqlserver/sqlserver_ry_job.sql index 9268e9f0d..48451d039 100644 --- a/script/sql/sqlserver/sqlserver_ry_job.sql +++ b/script/sql/sqlserver/sqlserver_ry_job.sql @@ -2,7 +2,7 @@ SnailJob Database Transfer Tool Source Server Type : MySQL Target Server Type : Microsoft SQL Server - Date: 2025-04-26 10:03:23 + Date: 2025-06-21 23:34:44 */ @@ -407,18 +407,19 @@ GO -- sj_retry_dead_letter CREATE TABLE sj_retry_dead_letter ( - id bigint NOT NULL PRIMARY KEY IDENTITY, - namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', - group_name nvarchar(64) NOT NULL, - group_id bigint NOT NULL, - scene_name nvarchar(64) NOT NULL, - scene_id bigint NOT NULL, - idempotent_id nvarchar(64) NOT NULL, - biz_no nvarchar(64) NOT NULL DEFAULT '', - executor_name nvarchar(512) NOT NULL DEFAULT '', - args_str nvarchar(max) NOT NULL, - ext_attrs nvarchar(max) NOT NULL, - create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP + id bigint NOT NULL PRIMARY KEY IDENTITY, + namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name nvarchar(64) NOT NULL, + group_id bigint NOT NULL, + scene_name nvarchar(64) NOT NULL, + scene_id bigint NOT NULL, + idempotent_id nvarchar(64) NOT NULL, + biz_no nvarchar(64) NOT NULL DEFAULT '', + executor_name nvarchar(512) NOT NULL DEFAULT '', + serializer_name nvarchar(32) NOT NULL DEFAULT 'jackson', + args_str nvarchar(max) NOT NULL, + ext_attrs nvarchar(max) NOT NULL, + create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP ) GO @@ -494,6 +495,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'executor_name' GO +EXEC sp_addextendedproperty + 'MS_Description', N'执行方法参数序列化器名称', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_retry_dead_letter', + 'COLUMN', N'serializer_name' +GO + EXEC sp_addextendedproperty 'MS_Description', N'执行方法参数', 'SCHEMA', N'dbo', @@ -535,6 +543,7 @@ CREATE TABLE sj_retry executor_name nvarchar(512) NOT NULL DEFAULT '', args_str nvarchar(max) NOT NULL, ext_attrs nvarchar(max) NOT NULL, + serializer_name nvarchar(32) NOT NULL DEFAULT 'jackson', next_trigger_at bigint NOT NULL, retry_count int NOT NULL DEFAULT 0, retry_status tinyint NOT NULL DEFAULT 0, @@ -552,13 +561,13 @@ GO CREATE INDEX idx_sj_retry_01 ON sj_retry (biz_no) GO -CREATE INDEX idx_sj_retry_02 ON sj_retry (retry_status, bucket_index) +CREATE INDEX idx_sj_retry_02 ON sj_retry (idempotent_id) GO -CREATE INDEX idx_sj_retry_03 ON sj_retry (parent_id) +CREATE INDEX idx_sj_retry_03 ON sj_retry (retry_status, bucket_index) GO -CREATE INDEX idx_sj_retry_04 ON sj_retry (create_dt) +CREATE INDEX idx_sj_retry_04 ON sj_retry (parent_id) GO -CREATE INDEX idx_sj_retry_05 ON sj_retry (idempotent_id) +CREATE INDEX idx_sj_retry_05 ON sj_retry (create_dt) GO EXEC sp_addextendedproperty @@ -638,6 +647,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'ext_attrs' GO +EXEC sp_addextendedproperty + 'MS_Description', N'执行方法参数序列化器名称', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_retry', + 'COLUMN', N'serializer_name' +GO + EXEC sp_addextendedproperty 'MS_Description', N'下次触发时间', 'SCHEMA', N'dbo', @@ -933,6 +949,8 @@ CREATE TABLE sj_retry_scene_config cb_trigger_type tinyint NOT NULL DEFAULT 1, cb_max_count int NOT NULL DEFAULT 16, cb_trigger_interval nvarchar(16) NOT NULL DEFAULT '', + owner_id bigint NULL DEFAULT NULL, + labels nvarchar(512) NULL DEFAULT '', description nvarchar(256) NOT NULL DEFAULT '', create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP @@ -1061,6 +1079,20 @@ EXEC sp_addextendedproperty 'COLUMN', N'cb_trigger_interval' GO +EXEC sp_addextendedproperty + 'MS_Description', N'负责人id', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_retry_scene_config', + 'COLUMN', N'owner_id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'标签', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_retry_scene_config', + 'COLUMN', N'labels' +GO + EXEC sp_addextendedproperty 'MS_Description', N'描述', 'SCHEMA', N'dbo', @@ -1100,6 +1132,7 @@ CREATE TABLE sj_server_node expire_at datetime2 NOT NULL, node_type tinyint NOT NULL, ext_attrs nvarchar(256) NULL DEFAULT '', + labels nvarchar(512) NULL DEFAULT '', create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP ) @@ -1176,6 +1209,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'ext_attrs' GO +EXEC sp_addextendedproperty + 'MS_Description', N'标签', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_server_node', + 'COLUMN', N'labels' +GO + EXEC sp_addextendedproperty 'MS_Description', N'创建时间', 'SCHEMA', N'dbo', @@ -1199,7 +1239,7 @@ GO -- sj_distributed_lock CREATE TABLE sj_distributed_lock ( - name nvarchar(64) NOT NULL, + name nvarchar(64) NOT NULL PRIMARY KEY, lock_until datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, locked_at datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, locked_by nvarchar(255) NOT NULL, @@ -1316,8 +1356,8 @@ EXEC sp_addextendedproperty 'TABLE', N'sj_system_user' GO --- pwd: admin -INSERT INTO sj_system_user(username, password, role, create_dt, update_dt) VALUES (N'admin', N'465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', N'2', getdate(), getdate()) +INSERT INTO sj_system_user (username, password, role) +VALUES (N'admin', N'465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac', 2) GO -- sj_system_user_permission @@ -1383,69 +1423,6 @@ EXEC sp_addextendedproperty 'TABLE', N'sj_system_user_permission' GO --- sj_sequence_alloc -CREATE TABLE sj_sequence_alloc -( - id bigint NOT NULL PRIMARY KEY IDENTITY, - namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', - group_name nvarchar(64) NOT NULL DEFAULT '', - max_id bigint NOT NULL DEFAULT 1, - step int NOT NULL DEFAULT 100, - update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP -) -GO - -CREATE UNIQUE INDEX uk_sj_sequence_alloc_01 ON sj_sequence_alloc (namespace_id, group_name) -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'主键', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'id' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'命名空间id', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'namespace_id' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'组名称', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'group_name' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'最大id', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'max_id' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'步长', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'step' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'更新时间', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc', - 'COLUMN', N'update_dt' -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'号段模式序号ID分配表', - 'SCHEMA', N'dbo', - 'TABLE', N'sj_sequence_alloc' -GO - -- sj_job CREATE TABLE sj_job ( @@ -1471,7 +1448,8 @@ CREATE TABLE sj_job bucket_index int NOT NULL DEFAULT 0, resident tinyint NOT NULL DEFAULT 0, notify_ids nvarchar(128) NOT NULL DEFAULT '', - owner_id bigint NULL, + owner_id bigint NULL DEFAULT NULL, + labels nvarchar(512) NULL DEFAULT '', description nvarchar(256) NOT NULL DEFAULT '', ext_attrs nvarchar(256) NULL DEFAULT '', deleted tinyint NOT NULL DEFAULT 0, @@ -1648,6 +1626,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'owner_id' GO +EXEC sp_addextendedproperty + 'MS_Description', N'标签', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job', + 'COLUMN', N'labels' +GO + EXEC sp_addextendedproperty 'MS_Description', N'描述', 'SCHEMA', N'dbo', @@ -1689,7 +1674,7 @@ EXEC sp_addextendedproperty 'TABLE', N'sj_job' GO -INSERT INTO sj_job (namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, description, ext_attrs, deleted, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, N'testJobExecutor', 2, N'60', 1, 60, 3, 1, 1, 116, 0, N'', 1, N'', N'', 0, getdate(), getdate()) +INSERT INTO sj_job (namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES (N'dev', N'ruoyi_group', N'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, N'testJobExecutor', 2, N'60', 1, 60, 3, 1, 1, 116, 0, N'', 1, N'', N'', N'', 0, getdate(), getdate()) GO -- sj_job_log_message @@ -2382,6 +2367,7 @@ CREATE TABLE sj_workflow notify_ids nvarchar(128) NOT NULL DEFAULT '', bucket_index int NOT NULL DEFAULT 0, version int NOT NULL, + owner_id bigint NULL DEFAULT NULL, ext_attrs nvarchar(256) NULL DEFAULT '', deleted tinyint NOT NULL DEFAULT 0, create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -2506,6 +2492,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'version' GO +EXEC sp_addextendedproperty + 'MS_Description', N'负责人id', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_workflow', + 'COLUMN', N'owner_id' +GO + EXEC sp_addextendedproperty 'MS_Description', N'扩展字段', 'SCHEMA', N'dbo', @@ -2823,3 +2816,76 @@ EXEC sp_addextendedproperty 'SCHEMA', N'dbo', 'TABLE', N'sj_workflow_task_batch' GO + +-- sj_job_executor +CREATE TABLE sj_job_executor +( + id bigint NOT NULL PRIMARY KEY IDENTITY, + namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a', + group_name nvarchar(64) NOT NULL, + executor_info nvarchar(256) NOT NULL, + executor_type nvarchar(3) NOT NULL, + create_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_dt datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP +) +GO + +CREATE INDEX idx_sj_job_executor_01 ON sj_job_executor (namespace_id, group_name) +GO +CREATE INDEX idx_sj_job_executor_02 ON sj_job_executor (create_dt) +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'主键', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'命名空间id', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'namespace_id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'组名称', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'group_name' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'任务执行器名称', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'executor_info' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'1:java 2:python 3:go', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'executor_type' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建时间', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'create_dt' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'修改时间', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor', + 'COLUMN', N'update_dt' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'任务执行器信息', + 'SCHEMA', N'dbo', + 'TABLE', N'sj_job_executor' +GO diff --git a/script/sql/sqlserver/sqlserver_ry_workflow.sql b/script/sql/sqlserver/sqlserver_ry_workflow.sql index 6d6370165..4f3cb6cf3 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, @@ -1133,9 +1141,240 @@ GO INSERT flow_category VALUES (109, N'000000', 102, N'0,100,102', N'离职', 2, N'0', 103, 1, getdate(), NULL, NULL); GO +CREATE TABLE flow_spel ( + id BIGINT NOT NULL, + component_name VARCHAR(255), + method_name VARCHAR(255), + method_params VARCHAR(255), + view_spel VARCHAR(255), + remark VARCHAR(255), + status CHAR(1) DEFAULT ('0'), + del_flag CHAR(1) DEFAULT ('0'), + create_dept BIGINT, + create_by BIGINT, + create_time DATETIME, + update_by BIGINT, + update_time DATETIME, + 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 + +EXEC sp_addextendedproperty + 'MS_Description', N'流程spel表达式定义表', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'主键id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'组件名称', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'component_name' +GO + +-- method_name 字段注释 + 'MS_Description', N'方法名', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'method_name' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'参数', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'method_params' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'预览spel表达式', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'view_spel' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'备注', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'remark' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'状态(0正常 1停用)', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'status' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'删除标志', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'del_flag' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建部门', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_dept' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'update_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'update_time' +GO + +INSERT flow_spel VALUES (1, N'spelRuleComponent', N'selectDeptLeaderById', N'initiatorDeptId', N'#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', N'根据部门id获取部门负责人', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE()); +GO +INSERT flow_spel VALUES (2, NULL, NULL, N'initiator', N'${initiator}', N'流程发起人', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE()); +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 + 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, @@ -1168,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', @@ -1290,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 new file mode 100644 index 000000000..a4c2e5caa --- /dev/null +++ b/script/sql/update/oracle/update_5.4.1-5.5.0.sql @@ -0,0 +1,90 @@ +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id NUMBER(20) NOT NULL, + component_name VARCHAR2(255), + method_name VARCHAR2(255), + method_params VARCHAR2(255), + view_spel VARCHAR2(255), + remark VARCHAR2(255), + status CHAR(1) DEFAULT '0', + del_flag CHAR(1) DEFAULT '0', + create_dept NUMBER(20), + create_by NUMBER(20), + create_time DATE, + update_by NUMBER(20), + update_time DATE +); + +alter table flow_spel add constraint pk_flow_spel primary key (id); + +COMMENT ON TABLE flow_spel IS '流程spel表达式定义表'; +COMMENT ON COLUMN flow_spel.id IS '主键id'; +COMMENT ON COLUMN flow_spel.component_name IS '组件名称'; +COMMENT ON COLUMN flow_spel.method_name IS '方法名'; +COMMENT ON COLUMN flow_spel.method_params IS '参数'; +COMMENT ON COLUMN flow_spel.view_spel IS '预览spel表达式'; +COMMENT ON COLUMN flow_spel.remark IS '备注'; +COMMENT ON COLUMN flow_spel.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN flow_spel.del_flag IS '删除标志'; +COMMENT ON COLUMN flow_spel.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_spel.create_by IS '创建者'; +COMMENT ON COLUMN flow_spel.create_time IS '创建时间'; +COMMENT ON COLUMN flow_spel.update_by IS '更新者'; +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, ''); + +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 '申请编号'; 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 new file mode 100644 index 000000000..9b2ad0eb3 --- /dev/null +++ b/script/sql/update/postgres/update_5.4.1-5.5.0.sql @@ -0,0 +1,92 @@ +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id BIGINT NOT NULL, + component_name VARCHAR(255), + method_name VARCHAR(255), + method_params VARCHAR(255), + view_spel VARCHAR(255), + remark VARCHAR(255), + status CHAR(1) DEFAULT '0', + del_flag CHAR(1) DEFAULT '0', + create_dept BIGINT, + create_by BIGINT, + create_time TIMESTAMP, + update_by BIGINT, + update_time TIMESTAMP, + PRIMARY KEY (id) +); + +COMMENT ON TABLE flow_spel IS '流程spel表达式定义表'; +COMMENT ON COLUMN flow_spel.id IS '主键id'; +COMMENT ON COLUMN flow_spel.component_name IS '组件名称'; +COMMENT ON COLUMN flow_spel.method_name IS '方法名'; +COMMENT ON COLUMN flow_spel.method_params IS '参数'; +COMMENT ON COLUMN flow_spel.view_spel IS '预览spel表达式'; +COMMENT ON COLUMN flow_spel.remark IS '备注'; +COMMENT ON COLUMN flow_spel.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN flow_spel.del_flag IS '删除标志'; +COMMENT ON COLUMN flow_spel.create_dept IS '创建部门'; +COMMENT ON COLUMN flow_spel.create_by IS '创建者'; +COMMENT ON COLUMN flow_spel.create_time IS '创建时间'; +COMMENT ON COLUMN flow_spel.update_by IS '更新者'; +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, ''); + +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 '申请编号'; 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 new file mode 100644 index 000000000..068283b2a --- /dev/null +++ b/script/sql/update/sqlserver/update_5.4.1-5.5.0.sql @@ -0,0 +1,281 @@ +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id BIGINT NOT NULL, + component_name VARCHAR(255), + method_name VARCHAR(255), + method_params VARCHAR(255), + view_spel VARCHAR(255), + remark VARCHAR(255), + status CHAR(1) DEFAULT ('0'), + del_flag CHAR(1) DEFAULT ('0'), + create_dept BIGINT, + create_by BIGINT, + create_time DATETIME, + update_by BIGINT, + update_time DATETIME, + CONSTRAINT PK_flow_spel PRIMARY KEY (id) +); +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'流程spel表达式定义表', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'主键id', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'id' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'组件名称', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'component_name' +GO + +-- method_name 字段注释 + 'MS_Description', N'方法名', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'method_name' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'参数', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'method_params' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'预览spel表达式', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'view_spel' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'备注', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'remark' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'状态(0正常 1停用)', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'status' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'删除标志', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'del_flag' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建部门', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_dept' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'创建时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'create_time' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新者', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'update_by' +GO + +EXEC sp_addextendedproperty + 'MS_Description', N'更新时间', + 'SCHEMA', N'dbo', + 'TABLE', N'flow_spel', + 'COLUMN', N'update_time' +GO + +INSERT flow_spel VALUES (1, N'spelRuleComponent', N'selectDeptLeaderById', N'initiatorDeptId', N'#{@spelRuleComponent.selectDeptLeaderById(#initiatorDeptId)}', N'根据部门id获取部门负责人', N'0', N'0', 103, 1, GETDATE(), 1, GETDATE()); +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 (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 + +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 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 new file mode 100644 index 000000000..5a1a685f2 --- /dev/null +++ b/script/sql/update/update_5.4.1-5.5.0.sql @@ -0,0 +1,60 @@ +-- ---------------------------- +-- 流程spel表达式定义表 +-- ---------------------------- +CREATE TABLE flow_spel ( + id bigint(20) NOT NULL COMMENT '主键id', + component_name varchar(255) DEFAULT NULL COMMENT '组件名称', + method_name varchar(255) DEFAULT NULL COMMENT '方法名', + method_params varchar(255) DEFAULT NULL COMMENT '参数', + view_spel varchar(255) DEFAULT NULL COMMENT '预览spel表达式', + remark varchar(255) DEFAULT NULL COMMENT '备注', + status char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', + del_flag char(1) DEFAULT '0' COMMENT '删除标志', + create_dept bigint(20) DEFAULT NULL COMMENT '创建部门', + create_by bigint(20) DEFAULT NULL COMMENT '创建者', + create_time datetime DEFAULT NULL COMMENT '创建时间', + update_by bigint(20) DEFAULT NULL COMMENT '更新者', + update_time datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (id) +) ENGINE = InnoDB COMMENT='流程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()); + +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, ''); + +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`;