疯狂的狮子Li
2024-07-09 d6688a367dc8dee7c489790daa5389de08e0531a
!562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
已修改77个文件
942 ■■■■■ 文件已修改
.run/ruoyi-monitor-admin.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-server.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-snailjob-server.run.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/Dockerfile 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-bom/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/Dockerfile 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/Dockerfile 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfDefinitionConfigServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfFormManageServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfNodeConfigServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfTaskBackNodeServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/docker-compose.yml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/docker/nginx/conf/nginx.conf 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
script/sql/postgres/flowable.sql 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.run/ruoyi-monitor-admin.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.0" />
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.1" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
      </settings>
.run/ruoyi-server.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.0" />
        <option name="imageTag" value="ruoyi/ruoyi-server:5.2.1" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
      </settings>
.run/ruoyi-snailjob-server.run.xml
@@ -2,7 +2,7 @@
  <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
    <deployment type="dockerfile">
      <settings>
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.0" />
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.1" />
        <option name="buildOnly" value="true" />
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
      </settings>
README.md
@@ -9,7 +9,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -56,7 +56,7 @@
| 数据加解密       | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等                                              | 无                                                                                  |
| 接口传输加密      | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性                                                                     | 无                                                                                  |
| 数据翻译        | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现                   | 无                                                                                  |
| 多数据源框架      | 采用 dynamic-datasource 支持世面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     |
| 多数据源框架      | 采用 dynamic-datasource 支持市面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源            | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差                                                     |
| 多数据源事务      | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚                                                                          | 不支持                                                                                |
| 数据库连接池      | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下                                                                        | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般                                               |
| 数据库主键       | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁                                                                  | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一                                                     |
pom.xml
@@ -13,7 +13,7 @@
    <description>RuoYi-Vue-Plus多租户管理系统</description>
    <properties>
        <revision>5.2.0</revision>
        <revision>5.2.1</revision>
        <spring-boot.version>3.2.6</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
ruoyi-admin/Dockerfile
@@ -1,7 +1,9 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-alpine:17.0.11-cds
#FROM bellsoft/liberica-openjdk-alpine:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/server/logs \
    /ruoyi/server/temp \
@@ -9,6 +11,10 @@
WORKDIR /ruoyi/server
# 补全alpine镜像缺失字体问题
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add fontconfig && apk add --update ttf-dejavu && fc-cache --force
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT}
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -1,6 +1,7 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -194,8 +195,26 @@
     */
    @GetMapping("/tenant/list")
    public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
        // 返回对象
        LoginTenantVo result = new LoginTenantVo();
        boolean enable = TenantHelper.isEnable();
        result.setTenantEnabled(enable);
        // 如果未开启租户这直接返回
        if (!enable) {
            return R.ok(result);
        }
        List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
        List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
        try {
            // 如果只超管返回所有租户
            if (LoginHelper.isSuperAdmin()) {
                result.setVoList(voList);
                return R.ok(result);
            }
        } catch (NotLoginException ignored) {
        }
        // 获取域名
        String host;
        String referer = request.getHeader("referer");
@@ -208,11 +227,8 @@
        // 根据域名进行筛选
        List<TenantListVo> list = StreamUtils.filter(voList, vo ->
                StringUtils.equals(vo.getDomain(), host));
        // 返回对象
        LoginTenantVo vo = new LoginTenantVo();
        vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
        vo.setTenantEnabled(TenantHelper.isEnable());
        return R.ok(vo);
        result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
        return R.ok(result);
    }
}
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -24,7 +24,6 @@
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -95,7 +94,7 @@
     * @param uuid     唯一标识
     */
    private void validateCaptcha(String tenantId, String username, String code, String uuid) {
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -16,6 +16,7 @@
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -83,7 +84,7 @@
        }
        SysSocialVo social;
        if (TenantHelper.isEnable()) {
            Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
            Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
            if (opt.isEmpty()) {
                throw new ServiceException("对不起,你没有权限登录当前租户!");
            }
ruoyi-admin/src/main/resources/application-dev.yml
@@ -17,7 +17,7 @@
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
  server:
    host: 127.0.0.1
    port: 1788
    port: 17888
  # 详见 script/sql/snail_job.sql `sj_namespace` 表
  namespace: ${spring.profiles.active}
  # 随主应用端口飘逸
@@ -96,8 +96,8 @@
    port: 6379
    # 数据库索引
    database: 0
    # 密码(如没有密码请注释掉)
    # password:
    # redis 密码必须配置
    password: ruoyi123
    # 连接超时时间
    timeout: 10s
    # 是否开启ssl
ruoyi-admin/src/main/resources/application-prod.yml
@@ -13,14 +13,14 @@
--- # snail-job 配置
snail-job:
  enabled: false
  enabled: true
  # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
  group: "ruoyi_group"
  # SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
  server:
    host: 127.0.0.1
    port: 1788
    port: 17888
  # 详见 script/sql/snail_job.sql `sj_namespace` 表
  namespace: ${spring.profiles.active}
  # 随主应用端口飘逸
@@ -99,8 +99,8 @@
    port: 6379
    # 数据库索引
    database: 0
    # 密码(如没有密码请注释掉)
    # password:
    # redis 密码必须配置
    password: ruoyi123
    # 连接超时时间
    timeout: 10s
    # 是否开启ssl
ruoyi-common/ruoyi-common-bom/pom.xml
@@ -14,7 +14,7 @@
    </description>
    <properties>
        <revision>5.2.0</revision>
        <revision>5.2.1</revision>
    </properties>
    <dependencyManagement>
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
@@ -7,6 +7,7 @@
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;
@@ -35,6 +36,34 @@
    }
    /**
     * 找到流中满足条件的第一个元素
     *
     * @param collection 需要查询的集合
     * @param function   过滤方法
     * @return 找到符合条件的第一个元素,没有则返回null
     */
    public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
        if (CollUtil.isEmpty(collection)) {
            return null;
        }
        return collection.stream().filter(function).findFirst().orElse(null);
    }
    /**
     * 找到流中任意一个满足条件的元素
     *
     * @param collection 需要查询的集合
     * @param function   过滤方法
     * @return 找到符合条件的任意一个元素,没有则返回null
     */
    public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
        if (CollUtil.isEmpty(collection)) {
            return Optional.empty();
        }
        return collection.stream().filter(function).findAny();
    }
    /**
     * 将collection拼接
     *
     * @param collection 需要转化的集合
ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
@@ -11,6 +11,7 @@
import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
@@ -230,7 +231,7 @@
            .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
        if (!CollectionUtils.isEmpty(methodTags)) {
            tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
            tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
            addTags(allTags, tags, locale);
        }
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
@@ -3,9 +3,10 @@
import java.lang.annotation.*;
/**
 * 数据权限
 *
 * 数据权限注解,用于标记数据权限的占位符关键字和替换值
 * <p>
 * 一个注解只能对应一个模板
 * </p>
 *
 * @author Lion Li
 * @version 3.5.0
@@ -16,12 +17,16 @@
public @interface DataColumn {
    /**
     * 占位符关键字
     * 数据权限模板的占位符关键字,默认为 "deptName"
     *
     * @return 占位符关键字数组
     */
    String[] key() default "deptName";
    /**
     * 占位符替换值
     * 数据权限模板的占位符替换值,默认为 "dept_id"
     *
     * @return 占位符替换值数组
     */
    String[] value() default "dept_id";
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
@@ -3,7 +3,7 @@
import java.lang.annotation.*;
/**
 * 数据权限组
 * 数据权限组注解,用于标记数据权限配置数组
 *
 * @author Lion Li
 * @version 3.5.0
@@ -13,6 +13,11 @@
@Documented
public @interface DataPermission {
    /**
     * 数据权限配置数组,用于指定数据权限的占位符关键字和替换值
     *
     * @return 数据权限配置数组
     */
    DataColumn[] value();
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
@@ -17,7 +17,6 @@
 *
 * @author Lion Li
 */
@Data
public class BaseEntity implements Serializable {
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -12,6 +12,7 @@
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import java.io.Serializable;
import java.util.Collection;
@@ -34,20 +35,38 @@
    Log log = LogFactory.getLog(BaseMapperPlus.class);
    /**
     * 获取当前实例对象关联的泛型类型 V 的 Class 对象
     *
     * @return 返回当前实例对象关联的泛型类型 V 的 Class 对象
     */
    default Class<V> currentVoClass() {
        return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
    }
    /**
     * 获取当前实例对象关联的泛型类型 T 的 Class 对象
     *
     * @return 返回当前实例对象关联的泛型类型 T 的 Class 对象
     */
    default Class<T> currentModelClass() {
        return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0];
    }
    /**
     * 使用默认的查询条件查询并返回结果列表
     *
     * @return 返回查询结果的列表
     */
    default List<T> selectList() {
        return this.selectList(new QueryWrapper<>());
    }
    /**
     * 批量插入
     * 批量插入实体对象集合
     *
     * @param entityList 实体对象集合
     * @return 插入操作是否成功的布尔值
     */
    default boolean insertBatch(Collection<T> entityList) {
        Db.saveBatch(entityList);
@@ -56,7 +75,10 @@
    }
    /**
     * 批量更新
     * 批量根据ID更新实体对象集合
     *
     * @param entityList 实体对象集合
     * @return 更新操作是否成功的布尔值
     */
    default boolean updateBatchById(Collection<T> entityList) {
        Db.updateBatchById(entityList);
@@ -65,7 +87,10 @@
    }
    /**
     * 批量插入或更新
     * 批量插入或更新实体对象集合
     *
     * @param entityList 实体对象集合
     * @return 插入或更新操作是否成功的布尔值
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList) {
        Db.saveOrUpdateBatch(entityList);
@@ -74,7 +99,11 @@
    }
    /**
     * 批量插入(包含限制条数)
     * 批量插入实体对象集合并指定批处理大小
     *
     * @param entityList 实体对象集合
     * @param batchSize  批处理大小
     * @return 插入操作是否成功的布尔值
     */
    default boolean insertBatch(Collection<T> entityList, int batchSize) {
        Db.saveBatch(entityList, batchSize);
@@ -83,7 +112,11 @@
    }
    /**
     * 批量更新(包含限制条数)
     * 批量根据ID更新实体对象集合并指定批处理大小
     *
     * @param entityList 实体对象集合
     * @param batchSize  批处理大小
     * @return 更新操作是否成功的布尔值
     */
    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
        Db.updateBatchById(entityList, batchSize);
@@ -92,7 +125,11 @@
    }
    /**
     * 批量插入或更新(包含限制条数)
     * 批量插入或更新实体对象集合并指定批处理大小
     *
     * @param entityList 实体对象集合
     * @param batchSize  批处理大小
     * @return 插入或更新操作是否成功的布尔值
     */
    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
        Db.saveOrUpdateBatch(entityList, batchSize);
@@ -100,12 +137,23 @@
        return true;
    }
    /**
     * 根据ID查询单个VO对象
     *
     * @param id 主键ID
     * @return 查询到的单个VO对象
     */
    default V selectVoById(Serializable id) {
        return selectVoById(id, this.currentVoClass());
    }
    /**
     * 根据 ID 查询
     * 根据ID查询单个VO对象并将其转换为指定的VO类
     *
     * @param id      主键ID
     * @param voClass 要转换的VO类的Class对象
     * @param <C>     VO类的类型
     * @return 查询到的单个VO对象,经过转换为指定的VO类后返回
     */
    default <C> C selectVoById(Serializable id, Class<C> voClass) {
        T obj = this.selectById(id);
@@ -115,12 +163,23 @@
        return MapstructUtils.convert(obj, voClass);
    }
    /**
     * 根据ID集合批量查询VO对象列表
     *
     * @param idList 主键ID集合
     * @return 查询到的VO对象列表
     */
    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
        return selectVoBatchIds(idList, this.currentVoClass());
    }
    /**
     * 查询(根据ID 批量查询)
     * 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表
     *
     * @param idList  主键ID集合
     * @param voClass 要转换的VO类的Class对象
     * @param <C>     VO类的类型
     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
     */
    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
        List<T> list = this.selectBatchIds(idList);
@@ -130,12 +189,23 @@
        return MapstructUtils.convert(list, voClass);
    }
    /**
     * 根据查询条件Map查询VO对象列表
     *
     * @param map 查询条件Map
     * @return 查询到的VO对象列表
     */
    default List<V> selectVoByMap(Map<String, Object> map) {
        return selectVoByMap(map, this.currentVoClass());
    }
    /**
     * 查询(根据 columnMap 条件)
     * 根据查询条件Map查询实体对象列表,并将其转换为指定的VO对象列表
     *
     * @param map     查询条件Map
     * @param voClass 要转换的VO类的Class对象
     * @param <C>     VO类的类型
     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
     */
    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
        List<T> list = this.selectByMap(map);
@@ -145,23 +215,47 @@
        return MapstructUtils.convert(list, voClass);
    }
    /**
     * 根据条件查询单个VO对象
     *
     * @param wrapper 查询条件Wrapper
     * @return 查询到的单个VO对象
     */
    default V selectVoOne(Wrapper<T> wrapper) {
        return selectVoOne(wrapper, this.currentVoClass());
    }
    /**
     * 根据条件查询单个VO对象,并根据需要决定是否抛出异常
     *
     * @param wrapper 查询条件Wrapper
     * @param throwEx 是否抛出异常的标志
     * @return 查询到的单个VO对象
     */
    default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
        return selectVoOne(wrapper, this.currentVoClass(), throwEx);
    }
    /**
     * 根据 entity 条件,查询一条记录
     * 根据条件查询单个VO对象,并指定返回的VO对象的类型
     *
     * @param wrapper 查询条件Wrapper
     * @param voClass 返回的VO对象的Class对象
     * @param <C>     返回的VO对象的类型
     * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
     */
    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
        return selectVoOne(wrapper, voClass, true);
    }
    /**
     * 根据 entity 条件,查询一条记录
     * 根据条件查询单个实体对象,并将其转换为指定的VO对象
     *
     * @param wrapper 查询条件Wrapper
     * @param voClass 要转换的VO类的Class对象
     * @param throwEx 是否抛出异常的标志
     * @param <C>     VO类的类型
     * @return 查询到的单个VO对象,经过转换为指定的VO类后返回
     */
    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
        T obj = this.selectOne(wrapper, throwEx);
@@ -171,16 +265,32 @@
        return MapstructUtils.convert(obj, voClass);
    }
    /**
     * 查询所有VO对象列表
     *
     * @return 查询到的VO对象列表
     */
    default List<V> selectVoList() {
        return selectVoList(new QueryWrapper<>(), this.currentVoClass());
    }
    /**
     * 根据条件查询VO对象列表
     *
     * @param wrapper 查询条件Wrapper
     * @return 查询到的VO对象列表
     */
    default List<V> selectVoList(Wrapper<T> wrapper) {
        return selectVoList(wrapper, this.currentVoClass());
    }
    /**
     * 根据 entity 条件,查询全部记录
     * 根据条件查询实体对象列表,并将其转换为指定的VO对象列表
     *
     * @param wrapper 查询条件Wrapper
     * @param voClass 要转换的VO类的Class对象
     * @param <C>     VO类的类型
     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
     */
    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
        List<T> list = this.selectList(wrapper);
@@ -190,15 +300,31 @@
        return MapstructUtils.convert(list, voClass);
    }
    /**
     * 根据条件分页查询VO对象列表
     *
     * @param page    分页信息
     * @param wrapper 查询条件Wrapper
     * @return 查询到的VO对象分页列表
     */
    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
        return selectVoPage(page, wrapper, this.currentVoClass());
    }
    /**
     * 分页查询VO
     * 根据条件分页查询实体对象列表,并将其转换为指定的VO对象分页列表
     *
     * @param page    分页信息
     * @param wrapper 查询条件Wrapper
     * @param voClass 要转换的VO类的Class对象
     * @param <C>     VO类的类型
     * @param <P>     VO对象分页列表的类型
     * @return 查询到的VO对象分页列表,经过转换为指定的VO类后返回
     */
    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
        // 根据条件分页查询实体对象列表
        List<T> list = this.selectList(page, wrapper);
        // 创建一个新的VO对象分页列表,并设置分页信息
        IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        if (CollUtil.isEmpty(list)) {
            return (P) voPage;
@@ -207,8 +333,16 @@
        return (P) voPage;
    }
    /**
     * 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表
     *
     * @param wrapper 查询条件Wrapper
     * @param mapper  转换函数,用于将查询到的对象转换为指定类型的对象
     * @param <C>     要转换的对象的类型
     * @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回
     */
    default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
        return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
        return StreamUtils.toList(this.selectObjs(wrapper), mapper);
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -4,10 +4,10 @@
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.sql.SqlUtil;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@@ -19,7 +19,6 @@
 *
 * @author Lion Li
 */
@Data
public class PageQuery implements Serializable {
@@ -56,6 +55,9 @@
     */
    public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
    /**
     * 构建分页对象
     */
    public <T> Page<T> build() {
        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
@@ -14,7 +14,6 @@
 *
 * @author Lion Li
 */
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
@@ -53,6 +52,9 @@
        this.total = total;
    }
    /**
     * 根据分页对象构建表格分页数据对象
     */
    public static <T> TableDataInfo<T> build(IPage<T> page) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
@@ -62,6 +64,9 @@
        return rspData;
    }
    /**
     * 根据数据列表构建表格分页数据对象
     */
    public static <T> TableDataInfo<T> build(List<T> list) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
@@ -71,6 +76,9 @@
        return rspData;
    }
    /**
     * 构建表格分页数据对象
     */
    public static <T> TableDataInfo<T> build() {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
@@ -1,8 +1,8 @@
package org.dromara.common.mybatis.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/**
 * 数据库类型
@@ -33,8 +33,17 @@
     */
    SQL_SERVER("Microsoft SQL Server");
    /**
     * 数据库类型
     */
    private final String type;
    /**
     * 根据数据库产品名称查找对应的数据库类型
     *
     * @param databaseProductName 数据库产品名称
     * @return 对应的数据库类型枚举值,如果未找到则返回 null
     */
    public static DataBaseType find(String databaseProductName) {
        if (StringUtils.isBlank(databaseProductName)) {
            return null;
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -1,19 +1,22 @@
package org.dromara.common.mybatis.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
 * 数据权限类型
 * 数据权限类型枚举
 * <p>
 * 语法支持 spel 模板表达式
 * <p>
 * 内置数据 user 当前用户 内容参考 LoginUser
 * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作
 * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService
 * 如需扩展更多自定义服务 可以参考 sdss 自行编写
 * 支持使用 SpEL 模板表达式定义 SQL 查询条件
 * 内置数据:
 * - {@code user}: 当前登录用户信息,参考 {@link LoginUser}
 * 内置服务:
 * - {@code sdss}: 系统数据权限服务,参考 {@link ISysDataScopeService}
 * 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
 * 如需扩展服务,可以通过 {@link ISysDataScopeService} 自行编写
 * </p>
 *
 * @author Lion Li
 * @version 3.5.0
@@ -29,36 +32,50 @@
    /**
     * 自定数据权限
     * 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )`
     * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
     */
    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
    /**
     * 部门数据权限
     * 使用 SpEL 表达式:`#{#deptName} = #{#user.deptId}`
     * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
     */
    DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
    /**
     * 部门及以下数据权限
     * 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )}`
     * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
     */
    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
    /**
     * 仅本人数据权限
     * 使用 SpEL 表达式:`#{#userName} = #{#user.userId}`
     * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
     */
    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
    private final String code;
    /**
     * 语法 采用 spel 模板表达式
     * SpEL 模板表达式,用于构建 SQL 查询条件
     */
    private final String sqlTemplate;
    /**
     * 不满足 sqlTemplate 则填充
     * 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式
     */
    private final String elseSql;
    /**
     * 根据枚举代码查找对应的枚举值
     *
     * @param code 枚举代码
     * @return 对应的枚举值,如果未找到则返回 null
     */
    public static DataScopeType findCode(String code) {
        if (StringUtils.isBlank(code)) {
            return null;
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -3,12 +3,12 @@
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.satoken.utils.LoginHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
@@ -21,21 +21,28 @@
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {
    /**
     * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
     *
     * @param metaObject 元对象,用于获取原始对象并进行填充
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        try {
            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
                // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
                Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
                    ? baseEntity.getCreateTime() : new Date();
                baseEntity.setCreateTime(current);
                baseEntity.setUpdateTime(current);
                // 如果创建人为空,则填充当前登录用户的信息
                if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
                    LoginUser loginUser = getLoginUser();
                    if (ObjectUtil.isNotNull(loginUser)) {
                        Long userId = loginUser.getUserId();
                        // 当前已登录 且 创建人为空 则填充
                        // 填充创建人、更新人和创建部门信息
                        baseEntity.setCreateBy(userId);
                        // 当前已登录 且 更新人为空 则填充
                        baseEntity.setUpdateBy(userId);
                        baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
                            ? baseEntity.getCreateDept() : loginUser.getDeptId());
@@ -47,19 +54,24 @@
        }
    }
    /**
     * 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
     *
     * @param metaObject 元对象,用于获取原始对象并进行填充
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        try {
            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
                // 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
                Date current = new Date();
                // 更新时间填充(不管为不为空)
                baseEntity.setUpdateTime(current);
                // 当前已登录 更新人填充(不管为不为空)
                // 获取当前登录用户的ID,并填充更新人信息
                Long userId = LoginHelper.getUserId();
                if (ObjectUtil.isNotNull(userId)) {
                    baseEntity.setUpdateBy(userId);
                }
            }
        } catch (Exception e) {
            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
@@ -67,7 +79,9 @@
    }
    /**
     * 获取登录用户名
     * 获取当前登录用户信息
     *
     * @return 当前登录用户的信息,如果用户未登录则返回 null
     */
    private LoginUser getLoginUser() {
        LoginUser loginUser;
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
@@ -1,14 +1,13 @@
package org.dromara.common.mybatis.handler;
import org.dromara.common.core.domain.R;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
/**
 * Mybatis异常处理器
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -68,13 +68,27 @@
     */
    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) {
        // 获取数据权限配置
        DataPermission dataPermission = getDataPermission(mappedStatementId);
        // 获取当前登录用户信息
        LoginUser currentUser = DataPermissionHelper.getVariable("user");
        if (ObjectUtil.isNull(currentUser)) {
            currentUser = LoginHelper.getLoginUser();
@@ -84,6 +98,7 @@
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
            return where;
        }
        // 构造数据过滤条件的 SQL 片段
        String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect);
        if (StringUtils.isBlank(dataFilterSql)) {
            return where;
@@ -103,7 +118,12 @@
    }
    /**
     * 构造数据过滤sql
     * 构建数据过滤条件的 SQL 语句
     *
     * @param dataColumns 数据权限注解中的列信息
     * @param isSelect    标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
     * @return 构建的数据过滤条件的 SQL 语句
     * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常
     */
    private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
        // 更新或删除需满足所有条件
@@ -159,20 +179,29 @@
    }
    /**
     * 通过 mapperPackage 设置的扫描包 扫描缓存有注解的方法与类
     * 扫描指定包下的 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);
                }
            }
@@ -181,9 +210,13 @@
        }
    }
    /**
     * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
     *
     * @param clazz 要查找的类
     */
    private void findAnnotation(Class<?> clazz) {
        DataPermission dataPermission;
        // 获取方法注解
        for (Method method : clazz.getMethods()) {
            if (method.isDefault() || method.isVarArgs()) {
                continue;
@@ -194,17 +227,24 @@
                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 (dataPermissionCacheMap.containsKey(mapperId)) {
            return dataPermissionCacheMap.get(mapperId);
        }
        // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
        if (dataPermissionCacheMap.containsKey(clazzName)) {
            return dataPermissionCacheMap.get(clazzName);
@@ -213,7 +253,10 @@
    }
    /**
     * 是否无效
     * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
     *
     * @param mapperId 映射语句 ID
     * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
     */
    public boolean invalid(String mapperId) {
        return getDataPermission(mapperId) == null;
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
@@ -2,11 +2,11 @@
import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.enums.DataBaseType;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -14,7 +14,6 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
 * 数据库助手
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -24,17 +24,35 @@
    private static final String DATA_PERMISSION_KEY = "data:permission";
    /**
     * 从上下文中获取指定键的变量值,并将其转换为指定的类型
     *
     * @param key 变量的键
     * @param <T> 变量值的类型
     * @return 指定键的变量值,如果不存在则返回 null
     */
    public static <T> T getVariable(String key) {
        Map<String, Object> context = getContext();
        return (T) context.get(key);
    }
    /**
     * 向上下文中设置指定键的变量值
     *
     * @param key   要设置的变量的键
     * @param value 要设置的变量值
     */
    public static void setVariable(String key, Object value) {
        Map<String, Object> context = getContext();
        context.put(key, value);
    }
    /**
     * 获取数据权限上下文
     *
     * @return 存储在SaStorage中的Map对象,用于存储数据权限相关的上下文信息
     * @throws NullPointerException 如果数据权限上下文类型异常,则抛出NullPointerException
     */
    public static Map<String, Object> getContext() {
        SaStorage saStorage = SaHolder.getStorage();
        Object attribute = saStorage.get(DATA_PERMISSION_KEY);
@@ -64,6 +82,7 @@
    /**
     * 在忽略数据权限中执行
     * <p>禁止在忽略数据权限中执行忽略数据权限</p>
     *
     * @param handle 处理执行方法
     */
@@ -78,6 +97,7 @@
    /**
     * 在忽略数据权限中执行
     * <p>禁止在忽略数据权限中执行忽略数据权限</p>
     *
     * @param handle 处理执行方法
     */
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -37,17 +37,33 @@
    private final PlusDataPermissionHandler dataPermissionHandler;
    /**
     * 构造函数,初始化 PlusDataPermissionHandler 实例
     *
     * @param mapperPackage 扫描的映射器包
     */
    public PlusDataPermissionInterceptor(String mapperPackage) {
        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
    }
    /**
     * 在执行查询之前,检查并处理数据权限相关逻辑
     *
     * @param executor      MyBatis 执行器对象
     * @param ms            映射语句对象
     * @param parameter     方法参数
     * @param rowBounds     分页对象
     * @param resultHandler 结果处理器
     * @param boundSql      绑定的 SQL 对象
     * @throws SQLException 如果发生 SQL 异常
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 检查忽略注解
        // 检查是否需要忽略数据权限处理
        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        // 检查是否无效 无数据权限注解
        // 检查是否缺少有效的数据权限注解
        if (dataPermissionHandler.invalid(ms.getId())) {
            return;
        }
@@ -56,16 +72,26 @@
        mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
    }
    /**
     * 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑
     *
     * @param sh                 MyBatis StatementHandler 对象
     * @param connection         数据库连接对象
     * @param transactionTimeout 事务超时时间
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        // 获取 SQL 命令类型(增、删、改、查)
        SqlCommandType sct = ms.getSqlCommandType();
        // 只处理更新和删除操作的 SQL 语句
        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
                return;
            }
            // 检查是否无效 无数据权限注解
            // 检查是否缺少有效的数据权限注解
            if (dataPermissionHandler.invalid(ms.getId())) {
                return;
            }
@@ -74,6 +100,14 @@
        }
    }
    /**
     * 处理 SELECT 查询语句中的 WHERE 条件
     *
     * @param select SELECT 查询对象
     * @param index  查询语句的索引
     * @param sql    查询语句
     * @param obj    WHERE 条件参数
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        if (select instanceof PlainSelect) {
@@ -84,6 +118,14 @@
        }
    }
    /**
     * 处理 UPDATE 语句中的 WHERE 条件
     *
     * @param update UPDATE 查询对象
     * @param index  查询语句的索引
     * @param sql    查询语句
     * @param obj    WHERE 条件参数
     */
    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
@@ -92,6 +134,14 @@
        }
    }
    /**
     * 处理 DELETE 语句中的 WHERE 条件
     *
     * @param delete DELETE 查询对象
     * @param index  查询语句的索引
     * @param sql    查询语句
     * @param obj    WHERE 条件参数
     */
    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
@@ -101,10 +151,10 @@
    }
    /**
     * 设置 where 条件
     * 设置 SELECT 语句的 WHERE 条件
     *
     * @param plainSelect       查询对象
     * @param mappedStatementId 执行方法id
     * @param plainSelect       SELECT 查询对象
     * @param mappedStatementId 映射语句的 ID
     */
    protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
@@ -113,6 +163,14 @@
        }
    }
    /**
     * 构建表达式,用于处理表的数据权限
     *
     * @param table        表对象
     * @param where        WHERE 条件表达式
     * @param whereSegment WHERE 条件片段
     * @return 构建的表达式
     */
    @Override
    public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
        // 只有新版数据权限处理器才会执行到这里
ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties
@@ -17,4 +17,4 @@
# 是否过滤 Log
filter=true
# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
exclude=SELECT 1
exclude=
ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -178,6 +178,7 @@
                            .key(key)
                            .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .addTransferListener(LoggingTransferListener.create())
                    .source(filePath).build());
@@ -223,6 +224,7 @@
                        y -> y.bucket(properties.getBucketName())
                            .key(key)
                            .contentType(contentType)
                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .build());
ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/QueueUtils.java
@@ -1,12 +1,13 @@
package org.dromara.common.redis.utils;
import org.dromara.common.core.utils.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
 * 分布式队列工具
@@ -224,7 +225,7 @@
    /**
     * 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
     */
    public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
    public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
        RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
        if (isDelayed) {
            // 订阅延迟队列
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -3,6 +3,7 @@
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
@@ -88,6 +89,13 @@
    }
    /**
     * 获取用户账户
     */
    public static String getUsername() {
        return Convert.toStr(getExtra(USER_NAME_KEY));
    }
    /**
     * 获取租户ID
     */
    public static String getTenantId() {
@@ -130,13 +138,6 @@
    }
    /**
     * 获取用户账户
     */
    public static String getUsername() {
        return getLoginUser().getUsername();
    }
    /**
     * 获取用户类型
     */
    public static UserType getUserType() {
@@ -170,6 +171,9 @@
     * @return 结果
     */
    public static boolean isTenantAdmin(Set<String> rolePermission) {
        if (CollUtil.isEmpty(rolePermission)) {
            return false;
        }
        return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
    }
@@ -188,7 +192,11 @@
     * @return 结果
     */
    public static boolean isLogin() {
        return getLoginUser() != null;
        try {
            return getLoginUser() != null;
        } catch (Exception e) {
            return false;
        }
    }
}
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
@@ -16,15 +16,11 @@
@AutoConfiguration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
    /**
     * 设置 Undertow 的 websocket 缓冲池
     */
    @Override
    public void customize(UndertowServletWebServerFactory factory) {
        // 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配
        factory.addDeploymentInfoCustomizers(deploymentInfo -> {
            WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
            deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
            // 使用虚拟线程
            if (SpringUtils.isVirtual()) {
ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java
@@ -35,23 +35,28 @@
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        // 检查是否登录 是否有token
        LoginUser loginUser = LoginHelper.getLoginUser();
        try {
            // 检查是否登录 是否有token
            LoginUser loginUser = LoginHelper.getLoginUser();
        // 解决 ws 不走 mvc 拦截器问题(cloud 版本不受影响)
        // 检查 header 与 param 里的 clientid 与 token 里的是否一致
        String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
        String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
        String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
        if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
            // token 无效
            throw NotLoginException.newInstance(StpUtil.getLoginType(),
                "-100", "客户端ID与Token不匹配",
                StpUtil.getTokenValue());
            // 解决 ws 不走 mvc 拦截器问题(cloud 版本不受影响)
            // 检查 header 与 param 里的 clientid 与 token 里的是否一致
            String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
            String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
            String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
            if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
                // token 无效
                throw NotLoginException.newInstance(StpUtil.getLoginType(),
                    "-100", "客户端ID与Token不匹配",
                    StpUtil.getTokenValue());
            }
            attributes.put(LOGIN_USER_KEY, loginUser);
            return true;
        } catch (NotLoginException e) {
            log.error("WebSocket 认证失败'{}',无法访问系统资源", e.getMessage());
            return false;
        }
        attributes.put(LOGIN_USER_KEY, loginUser);
        return true;
    }
    /**
ruoyi-extend/ruoyi-monitor-admin/Dockerfile
@@ -1,7 +1,9 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-alpine:17.0.11-cds
#FROM bellsoft/liberica-openjdk-alpine:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/monitor/logs
ruoyi-extend/ruoyi-snailjob-server/Dockerfile
@@ -1,7 +1,9 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-alpine:17.0.11-cds
#FROM bellsoft/liberica-openjdk-alpine:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/snailjob/logs
@@ -10,7 +12,7 @@
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8800
EXPOSE 1788
EXPOSE 17888
ADD ./target/ruoyi-snailjob-server.jar ./app.jar
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-dev.yml
@@ -21,7 +21,7 @@
  # 拉取重试数据的每批次的大小
  job-pull-page-size: 1000
  # 服务端netty端口
  netty-port: 1788
  netty-port: 17888
  # 一个客户端每秒最多接收的重试数量指令
  limiter: 1000
  # 号段模式下步长配置
ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml
@@ -21,7 +21,7 @@
  # 拉取重试数据的每批次的大小
  job-pull-page-size: 1000
  # 服务端 netty 端口
  netty-port: 1788
  netty-port: 17888
  # 一个客户端每秒最多接收的重试数量指令
  limiter: 1000
  # 号段模式下步长配置
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/queue/DelayedQueueController.java
@@ -1,14 +1,15 @@
package org.dromara.demo.controller.queue;
import cn.dev33.satoken.annotation.SaIgnore;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.QueueUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.QueueUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -42,6 +43,10 @@
        QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> {
            // 观察接收时间
            log.info("通道: {}, 收到数据: {}", queueName, orderNum);
            return CompletableFuture.runAsync(() -> {
                // 异步处理数据逻辑 不要在上方处理业务逻辑
                log.info("数据处理: {}", orderNum);
            });
        }, true);
        return R.ok("操作成功");
    }
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
@@ -55,5 +55,5 @@
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);
    int deleteByIds(@Param(Constants.COLL) Collection<?> idList);
}
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestDemoServiceImpl.java
@@ -101,7 +101,7 @@
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    @Override
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/TestTreeServiceImpl.java
@@ -83,6 +83,6 @@
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-generator/src/main/java/org/dromara/generator/service/GenTableServiceImpl.java
@@ -165,7 +165,7 @@
    @Override
    public void deleteGenTableByIds(Long[] tableIds) {
        List<Long> ids = Arrays.asList(tableIds);
        baseMapper.deleteBatchIds(ids);
        baseMapper.deleteByIds(ids);
        genTableColumnMapper.delete(new LambdaQueryWrapper<GenTableColumn>().in(GenTableColumn::getTableId, ids));
    }
@@ -332,7 +332,7 @@
        if (CollUtil.isNotEmpty(delColumns)) {
            List<Long> ids = StreamUtils.toList(delColumns, GenTableColumn::getColumnId);
            if (CollUtil.isNotEmpty(ids)) {
                genTableColumnMapper.deleteBatchIds(ids);
                genTableColumnMapper.deleteByIds(ids);
            }
        }
    }
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
@@ -149,6 +149,6 @@
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDeptController.java
@@ -75,8 +75,6 @@
    public R<Void> add(@Validated @RequestBody SysDeptBo dept) {
        if (!deptService.checkDeptNameUnique(dept)) {
            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
        } else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
            return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
        }
        return toAjax(deptService.insertDept(dept));
    }
@@ -92,8 +90,6 @@
        deptService.checkDeptDataScope(deptId);
        if (!deptService.checkDeptNameUnique(dept)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
        } else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
        } else if (dept.getParentId().equals(deptId)) {
            return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
        } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())) {
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysMenuMapper.java
@@ -19,13 +19,6 @@
public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
    /**
     * 根据用户所有权限
     *
     * @return 权限列表
     */
    List<String> selectMenuPerms();
    /**
     * 根据用户查询系统菜单列表
     *
     * @param queryWrapper 查询条件
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysDeptService.java
@@ -93,14 +93,6 @@
    boolean checkDeptNameUnique(SysDeptBo dept);
    /**
     * 校验部门类别编码是否唯一
     *
     * @param dept 部门信息
     * @return 结果
     */
    boolean checkDeptCategoryUnique(SysDeptBo dept);
    /**
     * 校验部门是否有数据权限
     *
     * @param deptId 部门id
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
@@ -146,6 +146,6 @@
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysConfigServiceImpl.java
@@ -178,7 +178,7 @@
            }
            CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
        }
        baseMapper.deleteBatchIds(Arrays.asList(configIds));
        baseMapper.deleteByIds(Arrays.asList(configIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
@@ -3,14 +3,14 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.system.domain.SysDept;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysRoleDept;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysRoleDeptMapper;
import org.dromara.system.service.ISysDataScopeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -30,6 +30,12 @@
    private final SysRoleDeptMapper roleDeptMapper;
    private final SysDeptMapper deptMapper;
    /**
     * 获取角色自定义权限
     *
     * @param roleId 角色Id
     * @return 部门Id组
     */
    @Override
    public String getRoleCustom(Long roleId) {
        List<SysRoleDept> list = roleDeptMapper.selectList(
@@ -42,6 +48,12 @@
        return null;
    }
    /**
     * 获取部门及以下权限
     *
     * @param deptId 部门Id
     * @return 部门Id组
     */
    @Override
    public String getDeptAndChild(Long deptId) {
        List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
@@ -219,20 +219,6 @@
    }
    /**
     * 校验部门类别编码是否唯一
     *
     * @param dept 部门信息
     * @return 结果
     */
    @Override
    public boolean checkDeptCategoryUnique(SysDeptBo dept) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDept>()
            .eq(SysDept::getDeptCategory, dept.getDeptCategory())
            .ne(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId()));
        return !exist;
    }
    /**
     * 校验部门是否有数据权限
     *
     * @param deptId 部门id
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
@@ -141,7 +141,7 @@
            }
            CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType());
        }
        baseMapper.deleteBatchIds(Arrays.asList(dictIds));
        baseMapper.deleteByIds(Arrays.asList(dictIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysLogininforServiceImpl.java
@@ -163,7 +163,7 @@
     */
    @Override
    public int deleteLogininforByIds(Long[] infoIds) {
        return baseMapper.deleteBatchIds(Arrays.asList(infoIds));
        return baseMapper.deleteByIds(Arrays.asList(infoIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMenuServiceImpl.java
@@ -75,7 +75,7 @@
                .orderByAsc(SysMenu::getOrderNum));
        } else {
            QueryWrapper<SysMenu> wrapper = Wrappers.query();
            wrapper.eq("sur.user_id", userId)
            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())
@@ -178,6 +178,7 @@
    /**
     * 构建前端路由所需要的菜单
     * 路由name命名规则 path首字母转大写 + id
     *
     * @param menus 菜单列表
     * @return 路由列表
@@ -186,9 +187,10 @@
    public List<RouterVo> buildMenus(List<SysMenu> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (SysMenu menu : menus) {
            String name = menu.getRouteName() + menu.getMenuId();
            RouterVo router = new RouterVo();
            router.setHidden("1".equals(menu.getVisible()));
            router.setName(menu.getRouteName());
            router.setName(name);
            router.setPath(menu.getRouterPath());
            router.setComponent(menu.getComponentInfo());
            router.setQuery(menu.getQueryParam());
@@ -199,12 +201,13 @@
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (menu.isMenuFrame()) {
                String frameName = StringUtils.capitalize(menu.getPath()) + menu.getMenuId();
                router.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setName(frameName);
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
                children.setQuery(menu.getQueryParam());
                childrenList.add(children);
@@ -215,9 +218,10 @@
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                String routerPath = SysMenu.innerLinkReplaceEach(menu.getPath());
                String innerLinkName = StringUtils.capitalize(routerPath) + menu.getMenuId();
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setName(innerLinkName);
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysNoticeServiceImpl.java
@@ -119,6 +119,6 @@
     */
    @Override
    public int deleteNoticeByIds(Long[] noticeIds) {
        return baseMapper.deleteBatchIds(Arrays.asList(noticeIds));
        return baseMapper.deleteByIds(Arrays.asList(noticeIds));
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java
@@ -122,7 +122,7 @@
     */
    @Override
    public int deleteOperLogByIds(Long[] operIds) {
        return baseMapper.deleteBatchIds(Arrays.asList(operIds));
        return baseMapper.deleteByIds(Arrays.asList(operIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssConfigServiceImpl.java
@@ -135,7 +135,7 @@
            SysOssConfig config = baseMapper.selectById(configId);
            list.add(config);
        }
        boolean flag = baseMapper.deleteBatchIds(ids) > 0;
        boolean flag = baseMapper.deleteByIds(ids) > 0;
        if (flag) {
            list.forEach(sysOssConfig ->
                CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey()));
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -247,7 +247,7 @@
            OssClient storage = OssFactory.instance(sysOss.getService());
            storage.delete(sysOss.getUrl());
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
@@ -202,7 +202,7 @@
                throw new ServiceException(String.format("%1$s已分配,不能删除!", post.getPostName()));
            }
        }
        return baseMapper.deleteBatchIds(Arrays.asList(postIds));
        return baseMapper.deleteByIds(Arrays.asList(postIds));
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -102,14 +102,13 @@
     */
    @Override
    public List<SysRoleVo> selectRolesAuthByUserId(Long userId) {
        List<SysRoleVo> userRoles = baseMapper.selectRolePermissionByUserId(userId);
        List<SysRoleVo> userRoles = baseMapper.selectRolesByUserId(userId);
        List<SysRoleVo> roles = selectRoleAll();
        // 使用HashSet提高查找效率
        Set<Long> userRoleIds = StreamUtils.toSet(userRoles, SysRoleVo::getRoleId);
        for (SysRoleVo role : roles) {
            for (SysRoleVo userRole : userRoles) {
                if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) {
                    role.setFlag(true);
                    break;
                }
            if (userRoleIds.contains(role.getRoleId())) {
                role.setFlag(true);
            }
        }
        return roles;
@@ -123,7 +122,7 @@
     */
    @Override
    public Set<String> selectRolePermissionByUserId(Long userId) {
        List<SysRoleVo> perms = baseMapper.selectRolePermissionByUserId(userId);
        List<SysRoleVo> perms = baseMapper.selectRolesByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (SysRoleVo perm : perms) {
            if (ObjectUtil.isNotNull(perm)) {
@@ -417,7 +416,7 @@
        roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().in(SysRoleMenu::getRoleId, ids));
        // 删除角色与部门关联
        roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().in(SysRoleDept::getRoleId, ids));
        return baseMapper.deleteBatchIds(ids);
        return baseMapper.deleteByIds(ids);
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantPackageServiceImpl.java
@@ -140,6 +140,6 @@
                throw new ServiceException("租户套餐已被使用");
            }
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
@@ -294,7 +294,7 @@
                throw new ServiceException("超管租户不能删除");
            }
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    /**
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -99,7 +99,7 @@
                w.in("u.dept_id", ids);
            }).orderByAsc("u.user_id");
        if (StringUtils.isNotBlank(user.getExcludeUserIds())) {
            wrapper.notIn("u.user_id", StringUtils.splitList(user.getExcludeUserIds()));
            wrapper.notIn("u.user_id", StringUtils.splitTo(user.getExcludeUserIds(), Convert::toLong));
        }
        return wrapper;
    }
@@ -541,7 +541,7 @@
        // 删除用户与岗位表
        userPostMapper.delete(new LambdaQueryWrapper<SysUserPost>().in(SysUserPost::getUserId, ids));
        // 防止更新失败导致的数据删除
        int flag = baseMapper.deleteBatchIds(ids);
        int flag = baseMapper.deleteByIds(ids);
        if (flag < 1) {
            throw new ServiceException("删除用户失败!");
        }
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
@@ -11,9 +11,8 @@
        select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query_param, m.visible, m.status,
        m.perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
        from sys_menu m
        left join sys_role_menu rm on m.menu_id = rm.menu_id
        left join sys_user_role sur on rm.role_id = sur.role_id
        left join sys_role ro on sur.role_id = ro.role_id
            left join sys_role_menu rm on m.menu_id = rm.menu_id
            left join sys_role r on rm.role_id = r.role_id
        ${ew.getCustomSqlSegment}
    </select>
@@ -34,14 +33,10 @@
                        m.order_num,
                        m.create_time
        from sys_menu m
                 left join sys_role_menu rm on m.menu_id = rm.menu_id
                 left join sys_user_role sur on rm.role_id = sur.role_id
                 left join sys_role ro on sur.role_id = ro.role_id
                 left join sys_user u on sur.user_id = u.user_id
        where u.user_id = #{userId}
          and m.menu_type in ('M', 'C')
          and m.status = '0'
          and ro.status = '0'
                 left join sys_role_menu rm on m.menu_id = rm.menu_id and m.status = '0'
                 left join sys_role r on rm.role_id = r.role_id and r.status = '0'
        where m.menu_type in ('M', 'C')
          and r.role_id in (select role_id from sys_user_role where user_id = #{userId})
        order by m.parent_id, m.order_num
    </select>
@@ -57,22 +52,12 @@
        order by m.parent_id, m.order_num
    </select>
    <select id="selectMenuPerms" resultType="String">
        select distinct m.perms
        from sys_menu m
                 left join sys_role_menu rm on m.menu_id = rm.menu_id
                 left join sys_user_role sur on rm.role_id = sur.role_id
    </select>
    <select id="selectMenuPermsByUserId" parameterType="Long" resultType="String">
        select distinct m.perms
        from sys_menu m
                 left join sys_role_menu rm on m.menu_id = rm.menu_id
                 left join sys_user_role sur on rm.role_id = sur.role_id
                 left join sys_role r on r.role_id = sur.role_id
        where m.status = '0'
          and r.status = '0'
          and sur.user_id = #{userId}
                 left join sys_role_menu rm on m.menu_id = rm.menu_id and m.status = '0'
                 left join sys_role r on r.role_id = rm.role_id and r.status = '0'
        where r.role_id in (select role_id from sys_user_role where user_id = #{userId})
    </select>
    <select id="selectMenuPermsByRoleId" parameterType="Long" resultType="String">
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
@@ -48,9 +48,7 @@
               r.data_scope,
               r.status
        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
        WHERE r.del_flag = '0' and sur.user_id = #{userId}
        WHERE r.del_flag = '0' and r.role_id in (select role_id from sys_user_role where user_id = #{userId})
    </select>
    <select id="selectRoleById" resultMap="SysRoleResult">
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/flowable/cmd/DeleteSequenceMultiInstanceCmd.java
@@ -2,6 +2,7 @@
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
@@ -59,7 +60,7 @@
        }
        List<Long> userIdList = new ArrayList<>();
        userIds.forEach(e -> {
            Long userId = assignees.stream().filter(id -> ObjectUtil.equals(id, e)).findFirst().orElse(null);
            Long userId = StreamUtils.findFirst(assignees, id -> ObjectUtil.equals(id, e));
            if (userId == null) {
                userIdList.add(e);
            }
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActProcessInstanceServiceImpl.java
@@ -9,6 +9,7 @@
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.StreamUtils;
@@ -17,7 +18,6 @@
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiProcinst;
import org.dromara.workflow.domain.bo.ProcessInstanceBo;
@@ -34,7 +34,7 @@
import org.dromara.workflow.service.IWfTaskBackNodeService;
import org.dromara.workflow.utils.QueryUtils;
import org.dromara.workflow.utils.WorkflowUtils;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
@@ -57,7 +57,6 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 流程实例 服务层实现
@@ -257,23 +256,23 @@
        List<HistoricActivityInstance> highLightedFlowList = QueryUtils.hisActivityInstanceQuery(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        for (HistoricActivityInstance tempActivity : highLightedFlowList) {
            Map<String, Object> task = new HashMap<>();
            if (!FlowConstant.SEQUENCE_FLOW.equals(tempActivity.getActivityType()) &&
                !FlowConstant.PARALLEL_GATEWAY.equals(tempActivity.getActivityType()) &&
                !FlowConstant.EXCLUSIVE_GATEWAY.equals(tempActivity.getActivityType()) &&
                !FlowConstant.INCLUSIVE_GATEWAY.equals(tempActivity.getActivityType())
            ) {
                task.put("key", tempActivity.getActivityId());
                task.put("completed", tempActivity.getEndTime() != null);
                task.put("activityType", tempActivity.getActivityType());
                taskList.add(task);
            switch (tempActivity.getActivityType()) {
                case FlowConstant.SEQUENCE_FLOW, FlowConstant.PARALLEL_GATEWAY,
                     FlowConstant.EXCLUSIVE_GATEWAY, FlowConstant.INCLUSIVE_GATEWAY -> {}
                default -> {
                    task.put("key", tempActivity.getActivityId());
                    task.put("completed", tempActivity.getEndTime() != null);
                    task.put("activityType", tempActivity.getActivityType());
                    taskList.add(task);
                }
            }
        }
        ProcessInstance processInstance = QueryUtils.instanceQuery(processInstanceId).singleResult();
        if (processInstance != null) {
            taskList = taskList.stream().filter(e -> !e.get("activityType").equals(FlowConstant.END_EVENT)).collect(Collectors.toList());
            taskList = StreamUtils.filter(taskList, e -> !e.get("activityType").equals(FlowConstant.END_EVENT));
        }
        //查询出运行中节点
        List<Map<String, Object>> runtimeNodeList = taskList.stream().filter(e -> !(Boolean) e.get("completed")).collect(Collectors.toList());
        List<Map<String, Object>> runtimeNodeList = StreamUtils.filter(taskList, e -> !(Boolean) e.get("completed"));
        if (CollUtil.isNotEmpty(runtimeNodeList)) {
            Iterator<Map<String, Object>> iterator = taskList.iterator();
            while (iterator.hasNext()) {
@@ -389,7 +388,7 @@
            }
            //附件
            if (CollUtil.isNotEmpty(attachmentList)) {
                List<Attachment> attachments = attachmentList.stream().filter(e -> e.getTaskId().equals(historicTaskInstance.getId())).collect(Collectors.toList());
                List<Attachment> attachments = StreamUtils.filter(attachmentList, e -> e.getTaskId().equals(historicTaskInstance.getId()));
                if (CollUtil.isNotEmpty(attachments)) {
                    actHistoryInfoVo.setAttachmentList(attachments);
                }
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActTaskServiceImpl.java
@@ -260,7 +260,8 @@
        String userId = String.valueOf(LoginHelper.getUserId());
        queryWrapper.eq("t.business_status_", BusinessStatusEnum.WAITING.getStatus());
        queryWrapper.eq(TenantHelper.isEnable(), "t.tenant_id_", TenantHelper.getTenantId());
        queryWrapper.and(w1 -> w1.eq("t.assignee_", userId).or(w2 -> w2.isNull("t.assignee_").apply("exists ( select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TASK_ID_ = t.ID_ and LINK.TYPE_ = 'candidate' " + "and (LINK.USER_ID_ = {0} or ( LINK.GROUP_ID_ IN " + getInParam(roleIds) + " ) ))", userId)));
        String ids = StreamUtils.join(roleIds, x -> "'" + x + "'");
        queryWrapper.and(w1 -> w1.eq("t.assignee_", userId).or(w2 -> w2.isNull("t.assignee_").apply("exists ( select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TASK_ID_ = t.ID_ and LINK.TYPE_ = 'candidate' and (LINK.USER_ID_ = {0} or ( LINK.GROUP_ID_ IN ({1}) ) ))", userId, ids)));
        if (StringUtils.isNotBlank(taskBo.getName())) {
            queryWrapper.like("t.name_", taskBo.getName());
        }
@@ -287,19 +288,6 @@
            }
        }
        return TableDataInfo.build(page);
    }
    private String getInParam(List<String> param) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < param.size(); i++) {
            sb.append("'").append(param.get(i)).append("'");
            if (i != param.size() - 1) {
                sb.append(",");
            }
        }
        sb.append(")");
        return sb.toString();
    }
    /**
@@ -696,7 +684,7 @@
            if (multiInstance == null && taskList.size() > 1) {
                List<Task> tasks = StreamUtils.filter(taskList, e -> !e.getTaskDefinitionKey().equals(task.getTaskDefinitionKey()));
                if (CollUtil.isNotEmpty(tasks)) {
                    actHiTaskinstMapper.deleteBatchIds(StreamUtils.toList(tasks, Task::getId));
                    actHiTaskinstMapper.deleteByIds(StreamUtils.toList(tasks, Task::getId));
                }
            }
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
@@ -111,7 +111,7 @@
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        List<String> idList = StreamUtils.toList(ids, String::valueOf);
        workflowService.deleteRunAndHisInstance(idList);
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    /**
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfCategoryServiceImpl.java
@@ -114,7 +114,7 @@
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    /**
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfDefinitionConfigServiceImpl.java
@@ -107,7 +107,7 @@
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
    @Override
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfFormManageServiceImpl.java
@@ -106,6 +106,6 @@
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfNodeConfigServiceImpl.java
@@ -50,7 +50,7 @@
     */
    @Override
    public Boolean deleteByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
        return baseMapper.deleteByIds(ids) > 0;
    }
ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WfTaskBackNodeServiceImpl.java
@@ -6,6 +6,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.workflow.domain.WfTaskBackNode;
@@ -56,7 +57,7 @@
            wfTaskBackNode.setOrderNo(0);
            wfTaskBackNodeMapper.insert(wfTaskBackNode);
        } else {
            WfTaskBackNode taskNode = list.stream().filter(e -> e.getNodeId().equals(wfTaskBackNode.getNodeId()) && e.getOrderNo() == 0).findFirst().orElse(null);
            WfTaskBackNode taskNode = StreamUtils.findFirst(list, e -> e.getNodeId().equals(wfTaskBackNode.getNodeId()) && e.getOrderNo() == 0);
            if (ObjectUtil.isEmpty(taskNode)) {
                wfTaskBackNode.setOrderNo(list.get(0).getOrderNo() + 1);
                WfTaskBackNode node = getListByInstanceIdAndNodeId(wfTaskBackNode.getInstanceId(), wfTaskBackNode.getNodeId());
@@ -106,7 +107,7 @@
                    }
                }
                if (CollUtil.isNotEmpty(ids)) {
                    wfTaskBackNodeMapper.deleteBatchIds(ids);
                    wfTaskBackNodeMapper.deleteByIds(ids);
                }
            }
            return true;
script/docker/docker-compose.yml
@@ -100,7 +100,7 @@
    network_mode: "host"
  ruoyi-server1:
    image: ruoyi/ruoyi-server:5.2.0
    image: ruoyi/ruoyi-server:5.2.1
    container_name: ruoyi-server1
    environment:
      # 时区上海
@@ -115,7 +115,7 @@
    network_mode: "host"
  ruoyi-server2:
    image: ruoyi/ruoyi-server:5.2.0
    image: ruoyi/ruoyi-server:5.2.1
    container_name: ruoyi-server2
    environment:
      # 时区上海
@@ -130,7 +130,7 @@
    network_mode: "host"
  ruoyi-monitor-admin:
    image: ruoyi/ruoyi-monitor-admin:5.2.0
    image: ruoyi/ruoyi-monitor-admin:5.2.1
    container_name: ruoyi-monitor-admin
    environment:
      # 时区上海
@@ -142,14 +142,14 @@
    network_mode: "host"
  ruoyi-snailjob-server:
    image: ruoyi/ruoyi-snailjob-server:5.2.0
    image: ruoyi/ruoyi-snailjob-server:5.2.1
    container_name: ruoyi-snailjob-server
    environment:
      # 时区上海
      TZ: Asia/Shanghai
    ports:
      - "8800:8800"
      - "1788:1788"
      - "17888:17888"
    volumes:
      - /docker/snailjob/logs/:/ruoyi/snailjob/logs
    privileged: true
script/docker/nginx/conf/nginx.conf
@@ -63,7 +63,7 @@
        # }
        # 限制外网访问内网 actuator 相关路径
        location ~ ^(/[^/]*)?/actuator(/.*)?$ {
        location ~ ^(/[^/]*)?/actuator.*(/.*)?$ {
            return 403;
        }
script/sql/postgres/flowable.sql
@@ -39,29 +39,17 @@
);
comment on table test_leave is '请假申请表';
comment on column test_leave.id 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 '结束时间';
comment on column test_leave.remark is '请假原因';
comment on column test_leave.status is '状态';
comment on column test_leave.create_dept is '创建部门';
comment on column test_leave.create_by is '创建者';
comment on column test_leave.create_time is '创建时间';
comment on column test_leave.update_by is '更新者';
comment on column test_leave.update_time is '更新时间';
comment on column test_leave.tenant_id is '租户编码';
alter table test_leave
@@ -86,27 +74,16 @@
);
comment on table wf_category is '流程分类';
comment on column wf_category.id is '主键';
comment on column wf_category.category_name is '分类名称';
comment on column wf_category.category_code is '分类编码';
comment on column wf_category.parent_id is '父级id';
comment on column wf_category.sort_num is '排序';
comment on column wf_category.tenant_id is '租户id';
comment on column wf_category.create_dept is '创建部门';
comment on column wf_category.create_by is '创建者';
comment on column wf_category.create_time is '创建时间';
comment on column wf_category.update_by is '修改者';
comment on column wf_category.update_time is '修改时间';
alter table wf_category
@@ -137,31 +114,18 @@
);
comment on table wf_task_back_node is '节点审批记录';
comment on column wf_task_back_node.id is '主键';
comment on column wf_task_back_node.node_id is '节点id';
comment on column wf_task_back_node.node_name is '节点名称';
comment on column wf_task_back_node.order_no is '排序';
comment on column wf_task_back_node.instance_id is '流程实例id';
comment on column wf_task_back_node.task_type is '节点类型';
comment on column wf_task_back_node.assignee is '审批人';
comment on column wf_task_back_node.tenant_id is '租户id';
comment on column wf_task_back_node.create_dept is '创建部门';
comment on column wf_task_back_node.create_by is '创建者';
comment on column wf_task_back_node.create_time is '创建时间';
comment on column wf_task_back_node.update_by is '修改者';
comment on column wf_task_back_node.update_time is '修改时间';
alter table wf_task_back_node
@@ -177,6 +141,7 @@
    process_key   varchar(255) not null,
    version       bigint       not null,
    tenant_id     varchar(20),
    remark        varchar(500),
    create_dept   bigint,
    create_by     bigint,
    create_time   timestamp,
@@ -185,27 +150,17 @@
);
comment on table wf_definition_config is '流程定义配置';
comment on column wf_definition_config.id is '主键';
comment on column wf_definition_config.table_name is '表名';
comment on column wf_definition_config.definition_id is '流程定义ID';
comment on column wf_definition_config.process_key is '流程KEY';
comment on column wf_definition_config.version is '流程版本';
comment on column wf_definition_config.tenant_id is '租户id';
comment on column wf_definition_config.remark is '备注';
comment on column wf_definition_config.create_dept is '创建部门';
comment on column wf_definition_config.create_by is '创建者';
comment on column wf_definition_config.create_time is '创建时间';
comment on column wf_definition_config.update_by is '修改者';
comment on column wf_definition_config.update_time is '修改时间';
alter table wf_definition_config
@@ -231,27 +186,16 @@
);
comment on table wf_form_manage is '表单管理';
comment on column wf_form_manage.id is '主键';
comment on column wf_form_manage.form_name is '表单名称';
comment on column wf_form_manage.form_type is '表单类型';
comment on column wf_form_manage.router is '路由地址/表单ID';
comment on column wf_form_manage.remark is '备注';
comment on column wf_form_manage.tenant_id is '租户id';
comment on column wf_form_manage.create_dept is '创建部门';
comment on column wf_form_manage.create_by is '创建者';
comment on column wf_form_manage.create_time is '创建时间';
comment on column wf_form_manage.update_by is '修改者';
comment on column wf_form_manage.update_time is '修改时间';
insert into wf_form_manage(id, form_name, form_type, router, remark, tenant_id, create_dept, create_by, create_time, update_by, update_time) VALUES (1, '请假申请', 'static', '/workflow/leaveEdit/index', NULL, '000000', 103, 1, now(), 1, now());
@@ -276,31 +220,18 @@
);
comment on table wf_node_config is '节点配置';
comment on column wf_node_config.id is '主键';
comment on column wf_node_config.form_id is '表单id';
comment on column wf_node_config.form_type is '表单类型';
comment on column wf_node_config.node_id is '节点id';
comment on column wf_node_config.node_name is '节点名称';
comment on column wf_node_config.definition_id is '流程定义id';
comment on column wf_node_config.apply_user_task is '是否为申请人节点 (0是 1否)';
comment on column wf_node_config.tenant_id is '租户id';
comment on column wf_node_config.create_dept is '创建部门';
comment on column wf_node_config.create_by is '创建者';
comment on column wf_node_config.create_time is '创建时间';
comment on column wf_node_config.update_by is '修改者';
comment on column wf_node_config.update_time is '修改时间';
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark) VALUES (11638, '请假申请', 5, 1, 'leave', 'workflow/leave/index', 1, 0, 'C', '0', '0', 'workflow:leave:list', '#', 103, 1, now(), NULL, NULL, '请假申请菜单');