Pārlūkot izejas kodu

!84 租户优化
Merge pull request !84 from 芋道源码/feature/tenant_op

芋道源码 3 gadi atpakaļ
vecāks
revīzija
2af0e40fe7
100 mainītis faili ar 1106 papildinājumiem un 585 dzēšanām
  1. 5 5
      README.md
  2. 2 1
      sql/ruoyi-vue-pro.sql
  3. 1 1
      yudao-dependencies/pom.xml
  4. 20 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java
  5. 1 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
  6. 22 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  7. 19 13
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
  8. 106 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
  9. 0 30
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantDatabaseAutoConfiguration.java
  10. 0 43
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java
  11. 0 20
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantMQAutoConfiguration.java
  12. 0 25
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantSecurityAutoConfiguration.java
  13. 0 25
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java
  14. 18 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnore.java
  15. 32 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnoreAspect.java
  16. 20 12
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
  17. 4 11
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java
  18. 0 14
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerInvoker.java
  19. 78 14
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
  20. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkService.java
  21. 36 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/util/TenantUtils.java
  22. 1 5
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories
  23. 22 0
      yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java
  24. 8 4
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  25. 7 2
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java
  26. 52 0
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java
  27. 17 9
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
  28. 27 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/ApiRequestFilter.java
  29. 3 3
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
  30. 2 3
      yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/test/BaseDbUnitTest.java
  31. 3 0
      yudao-module-bpm/yudao-module-bpm-base/src/test/resources/application-unit-test.yaml
  32. 0 1
      yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/clean.sql
  33. 1 2
      yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/create_tables.sql
  34. 0 1
      yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/java/cn/iocoder/yudao/module/bpm/test/BaseDbUnitTest.java
  35. 3 0
      yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/application-unit-test.yaml
  36. 0 2
      yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/sql/clean.sql
  37. 0 28
      yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/sql/create_tables.sql
  38. 1 0
      yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
  39. 6 4
      yudao-module-infra/yudao-module-infra-impl/pom.xml
  40. 0 2
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
  41. 2 2
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java
  42. 2 2
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiAccessLogDO.java
  43. 2 2
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java
  44. 1 4
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java
  45. 4 1
      yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/BaseDbAndRedisUnitTest.java
  46. 2 1
      yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/BaseDbUnitTest.java
  47. 0 30
      yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/RedisTestConfiguration.java
  48. 3 0
      yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml
  49. 0 3
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  50. 1 1
      yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppUserController.java
  51. 4 1
      yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/BaseDbAndRedisUnitTest.java
  52. 2 1
      yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/BaseDbUnitTest.java
  53. 0 30
      yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/RedisTestConfiguration.java
  54. 3 0
      yudao-module-member/yudao-module-member-impl/src/test/resources/application-unit-test.yaml
  55. 4 0
      yudao-module-pay/yudao-module-pay-impl/pom.xml
  56. 1 3
      yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java
  57. 15 9
      yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
  58. 4 1
      yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseDbAndRedisUnitTest.java
  59. 2 1
      yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseDbUnitTest.java
  60. 1 0
      yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseRedisUnitTest.java
  61. 0 30
      yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/RedisTestConfiguration.java
  62. 3 0
      yudao-module-pay/yudao-module-pay-impl/src/test/resources/application-unit-test.yaml
  63. 2 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/LoginLogCreateReqDTO.java
  64. 78 73
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  65. 8 2
      yudao-module-system/yudao-module-system-impl/pom.xml
  66. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  67. 1 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptBaseVO.java
  68. 8 4
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java
  69. 15 7
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java
  70. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java
  71. 6 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java
  72. 0 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleBaseVO.java
  73. 2 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java
  74. 18 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.http
  75. 4 14
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java
  76. 81 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantPackageController.java
  77. 0 29
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantBaseVO.java
  78. 0 12
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantCreateReqVO.java
  79. 31 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageBaseVO.java
  80. 14 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageCreateReqVO.java
  81. 38 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java
  82. 23 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java
  83. 21 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java
  84. 18 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageUpdateReqVO.java
  85. 48 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantBaseVO.java
  86. 29 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantCreateReqVO.java
  87. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantExcelVO.java
  88. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantExportReqVO.java
  89. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java
  90. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java
  91. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantUpdateReqVO.java
  92. 7 7
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
  93. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserBaseVO.java
  94. 3 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/permission/RoleConvert.java
  95. 14 5
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/tenant/TenantConvert.java
  96. 37 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/tenant/TenantPackageConvert.java
  97. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java
  98. 3 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/DeptDO.java
  99. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/PostDO.java
  100. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogDO.java

+ 5 - 5
README.md

@@ -153,16 +153,16 @@ ps:核心功能已经实现,正在对接微信小程序中...
 
 | 框架 | 说明 | 版本       | 学习指南 |
 | --- | --- |----------| --- |
-| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.9    | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
+| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.10    | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
 | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7      |  |
 | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
-| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.3.4  | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
+| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.1  | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
 | [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
 | [Redis](https://redis.io/) | key-value 数据库 | 5.0      |  |
 | [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.16.8   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
-| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架  | 5.3.15   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
-| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.4    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
-| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.0    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
+| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架  | 5.3.16   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
+| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
+| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.2    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
 | [Activiti](https://github.com/Activiti/Activiti) | 工作流引擎 | 7.1.0.M6 | [文档](TODO)  |
 | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2    | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
 | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 1
sql/ruoyi-vue-pro.sql


+ 1 - 1
yudao-dependencies/pom.xml

@@ -16,7 +16,7 @@
     <properties>
         <revision>1.5.0-snapshot</revision>
         <!-- 统一依赖管理 -->
-        <spring.boot.version>2.5.9</spring.boot.version>
+        <spring.boot.version>2.5.10</spring.boot.version>
         <!-- Web 相关 -->
         <knife4j.version>3.0.2</knife4j.version>
         <swagger-annotations.version>1.5.22</swagger-annotations.version>

+ 20 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文档地址
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum DocumentEnum {
+
+    REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
+
+    private final String url;
+    private final String memo;
+
+}

+ 1 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.common.enums;
 /**
  * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
  *
- * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
+ *  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
  *
  * @author 芋道源码
  */

+ 22 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import com.google.common.collect.ImmutableMap;
 
 import java.util.*;
 import java.util.function.BinaryOperator;
@@ -125,6 +126,15 @@ public class CollectionUtils {
         return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
     }
 
+    public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
+        if (CollUtil.isEmpty(from)) {
+            return Collections.emptyMap();
+        }
+        ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
+        from.forEach(item -> builder.put(keyFunc.apply(item), item));
+        return builder.build();
+    }
+
     public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
         return org.springframework.util.CollectionUtils.containsAny(source, candidates);
     }
@@ -140,6 +150,15 @@ public class CollectionUtils {
         return from.stream().filter(predicate).findFirst().orElse(null);
     }
 
+    public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) {
+        if (CollUtil.isEmpty(from)) {
+            return null;
+        }
+        assert from.size() > 0; // 断言,避免告警
+        T t = from.stream().max(Comparator.comparing(valueFunc)).get();
+        return valueFunc.apply(t);
+    }
+
     public static <T> void addIfNotNull(Collection<T> coll, T item) {
         if (item == null) {
             return;
@@ -147,4 +166,7 @@ public class CollectionUtils {
         coll.add(item);
     }
 
+    public static <T> Collection<T> singleton(T deptId) {
+        return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
+    }
 }

+ 19 - 13
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java

@@ -14,22 +14,28 @@ import java.util.Set;
 @Data
 public class TenantProperties {
 
-//    /**
-//     * 租户是否开启
-//     */
-//    private static final Boolean ENABLE_DEFAULT = true;
-//
-//    /**
-//     * 是否开启
-//     */
-//    private Boolean enable = ENABLE_DEFAULT;
+    /**
+     * 租户是否开启
+     */
+    private static final Boolean ENABLE_DEFAULT = true;
+
+    /**
+     * 是否开启
+     */
+    private Boolean enable = ENABLE_DEFAULT;
+
+    /**
+     * 需要忽略多租户的请求
+     *
+     * 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API!
+     */
+    private Set<String> ignoreUrls;
 
     /**
-     * 需要多租户的表
+     * 需要忽略多租户的表
      *
-     * 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。
-     * 如果需要,你可以改成 ignoreTables 来取消部分不需要的表
+     * 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟
      */
-    private Set<String> tables;
+    private Set<String> ignoreTables;
 
 }

+ 106 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java

@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.framework.tenant.config;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
+import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
+import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
+import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
+import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
+import cn.iocoder.yudao.framework.web.config.WebProperties;
+import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
+@EnableConfigurationProperties(TenantProperties.class)
+public class YudaoTenantAutoConfiguration {
+
+    // ========== AOP ==========
+
+    @Bean
+    public TenantIgnoreAspect tenantIgnoreAspect() {
+        return new TenantIgnoreAspect();
+    }
+
+    // ========== DB ==========
+
+    @Bean
+    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties,
+                                                                 MybatisPlusInterceptor interceptor) {
+        TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));
+        // 添加到 interceptor 中
+        // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
+        MyBatisUtils.addInterceptor(interceptor, inner, 0);
+        return inner;
+    }
+
+    // ========== WEB ==========
+
+    @Bean
+    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
+        FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(new TenantContextWebFilter());
+        registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
+        return registrationBean;
+    }
+
+    // ========== Security ==========
+
+    @Bean
+    public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties,
+                                                                                   WebProperties webProperties,
+                                                                                   GlobalExceptionHandler globalExceptionHandler,
+                                                                                   TenantFrameworkService tenantFrameworkService) {
+        FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
+        registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties,
+                globalExceptionHandler, tenantFrameworkService));
+        registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
+        return registrationBean;
+    }
+
+    // ========== MQ ==========
+
+    @Bean
+    public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
+        return new TenantRedisMessageInterceptor();
+    }
+
+    // ========== Job ==========
+
+    @Bean
+    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
+    public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
+        return new BeanPostProcessor() {
+
+            @Override
+            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+                if (!(bean instanceof JobHandler)) {
+                    return bean;
+                }
+                // 有 TenantJob 注解的情况下,才会进行处理
+                if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
+                    return bean;
+                }
+
+                // 使用 TenantJobHandlerDecorator 装饰
+                return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
+            }
+
+        };
+    }
+
+}

+ 0 - 30
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantDatabaseAutoConfiguration.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.config;
-
-import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 多租户针对 DB 的自动配置
- *
- * @author 芋道源码
- */
-@Configuration
-@EnableConfigurationProperties(TenantProperties.class)
-public class YudaoTenantDatabaseAutoConfiguration {
-
-    @Bean
-    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties,
-                                                                 MybatisPlusInterceptor interceptor) {
-        TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));
-        // 添加到 interceptor 中
-        // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
-        MyBatisUtils.addInterceptor(interceptor, inner, 0);
-        return inner;
-    }
-
-}

+ 0 - 43
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantJobAutoConfiguration.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.config;
-
-import cn.hutool.core.annotation.AnnotationUtil;
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
-import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 多租户针对 Job 的自动配置
- *
- * @author 芋道源码
- */
-@Configuration
-public class YudaoTenantJobAutoConfiguration {
-
-    @Bean
-    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
-    public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
-        return new BeanPostProcessor() {
-
-            @Override
-            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
-                if (!(bean instanceof JobHandler)) {
-                    return bean;
-                }
-                // 有 TenantJob 注解的情况下,才会进行处理
-                if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
-                    return bean;
-                }
-
-                // 使用 TenantJobHandlerDecorator 装饰
-                return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
-            }
-
-        };
-    }
-
-}

+ 0 - 20
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantMQAutoConfiguration.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.config;
-
-import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 多租户针对 MQ 的自动配置
- *
- * @author 芋道源码
- */
-@Configuration
-public class YudaoTenantMQAutoConfiguration {
-
-    @Bean
-    public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
-        return new TenantRedisMessageInterceptor();
-    }
-
-}

+ 0 - 25
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantSecurityAutoConfiguration.java

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.config;
-
-import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
-import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 多租户针对 Web 的自动配置
- *
- * @author 芋道源码
- */
-@Configuration
-public class YudaoTenantSecurityAutoConfiguration {
-
-    @Bean
-    public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter() {
-        FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
-        registrationBean.setFilter(new TenantSecurityWebFilter());
-        registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
-        return registrationBean;
-    }
-
-}

+ 0 - 25
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.config;
-
-import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
-import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * 多租户针对 Web 的自动配置
- *
- * @author 芋道源码
- */
-@Configuration
-public class YudaoTenantWebAutoConfiguration {
-
-    @Bean
-    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
-        FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
-        registrationBean.setFilter(new TenantContextWebFilter());
-        registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
-        return registrationBean;
-    }
-
-}

+ 18 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnore.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.framework.tenant.core.aop;
+
+import java.lang.annotation.*;
+
+/**
+ * 忽略租户,标记指定方法不进行租户的自动过滤
+ *
+ * 注意,只有 DB 的场景会过滤,其它场景暂时不过滤:
+ * 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
+ * 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
+ *
+ * @author 芋道源码
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface TenantIgnore {
+}

+ 32 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/aop/TenantIgnoreAspect.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.framework.tenant.core.aop;
+
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+
+/**
+ * 忽略多租户的 Aspect,基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。
+ * 例如说,一个定时任务,读取所有数据,进行处理。
+ * 又例如说,读取所有数据,进行缓存。
+ *
+ * @author 芋道源码
+ */
+@Aspect
+@Slf4j
+public class TenantIgnoreAspect {
+
+    @Around("@annotation(tenantIgnore)")
+    public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {
+        Boolean oldIgnore = TenantContextHolder.isIgnore();
+        try {
+            TenantContextHolder.setIgnore(true);
+            // 执行逻辑
+            return joinPoint.proceed();
+        } finally {
+            TenantContextHolder.setIgnore(oldIgnore);
+        }
+    }
+
+}

+ 20 - 12
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java

@@ -9,12 +9,15 @@ import com.alibaba.ttl.TransmittableThreadLocal;
  */
 public class TenantContextHolder {
 
+    /**
+     * 当前租户编号
+     */
     private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
 
     /**
-     * 租户编号 - 空
+     * 是否忽略租户
      */
-    private static final Long TENANT_ID_NULL = 0L;
+    private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
 
     /**
      * 获得租户编号。
@@ -33,26 +36,31 @@ public class TenantContextHolder {
     public static Long getRequiredTenantId() {
         Long tenantId = getTenantId();
         if (tenantId == null) {
-            throw new NullPointerException("TenantContextHolder 不存在租户编号");
+            throw new NullPointerException("TenantContextHolder 不存在租户编号"); // TODO 芋艿:增加文档链接
         }
         return tenantId;
     }
 
-    /**
-     * 在一些前端场景下,可能无法请求带上租户。例如说,<img /> 方式获取图片等
-     * 此时,暂时的解决方案,是在该接口的 Controller 方法上,调用该方法
-     * TODO 芋艿:思考有没更合适的方案,目标是去掉该方法
-     */
-    public static void setNullTenantId() {
-        TENANT_ID.set(TENANT_ID_NULL);
-    }
-
     public static void setTenantId(Long tenantId) {
         TENANT_ID.set(tenantId);
     }
 
+    public static void setIgnore(Boolean ignore) {
+        IGNORE.set(ignore);
+    }
+
+    /**
+     * 当前是否忽略租户
+     *
+     * @return 是否忽略
+     */
+    public static boolean isIgnore() {
+        return Boolean.TRUE.equals(IGNORE.get());
+    }
+
     public static void clear() {
         TENANT_ID.remove();
+        IGNORE.remove();
     }
 
 }

+ 4 - 11
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java

@@ -3,12 +3,10 @@ package cn.iocoder.yudao.framework.tenant.core.db;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import com.baomidou.mybatisplus.core.metadata.TableInfo;
-import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import lombok.AllArgsConstructor;
 import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.expression.LongValue;
 
 /**
  * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
@@ -22,18 +20,13 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
 
     @Override
     public Expression getTenantId() {
-        return new StringValue(TenantContextHolder.getRequiredTenantId().toString());
+        return new LongValue( TenantContextHolder.getRequiredTenantId());
     }
 
     @Override
     public boolean ignoreTable(String tableName) {
-        // 如果实体类继承 TenantBaseDO 类,则是多租户表,不进行忽略
-        TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
-        if (tableInfo != null && TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
-            return false;
-        }
-        // 不包含,说明要过滤
-        return !CollUtil.contains(properties.getTables(), tableName);
+        return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
+            || CollUtil.contains(properties.getIgnoreTables(), tableName); // 情况二,忽略多租户的表
     }
 
 }

+ 0 - 14
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerInvoker.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.framework.tenant.core.job;
-
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
-
-/**
- * 多租户 JobHandlerInvoker 拓展实现类
- *
- * @author 芋道源码
- */
-public class TenantJobHandlerInvoker extends JobHandlerInvoker {
-
-
-
-}

+ 78 - 14
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java

@@ -1,13 +1,19 @@
 package cn.iocoder.yudao.framework.tenant.core.security;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
+import cn.iocoder.yudao.framework.web.config.WebProperties;
+import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
+import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.util.AntPathMatcher;
 
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -18,34 +24,92 @@ import java.util.Objects;
 
 /**
  * 多租户 Security Web 过滤器
- * 校验用户访问的租户,是否是其所在的租户,避免越权问题
+ * 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。
+ * 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。
+ * 3. 校验租户是合法,例如说被禁用、到期
+ *
+ * 校验用户访问的租户,是否是其所在的租户,
  *
  * @author 芋道源码
  */
 @Slf4j
-public class TenantSecurityWebFilter extends OncePerRequestFilter {
+public class TenantSecurityWebFilter extends ApiRequestFilter {
+
+    private final TenantProperties tenantProperties;
+
+    private final AntPathMatcher pathMatcher;
+
+    private final GlobalExceptionHandler globalExceptionHandler;
+    private final TenantFrameworkService tenantFrameworkService;
+
+    public TenantSecurityWebFilter(TenantProperties tenantProperties,
+                                   WebProperties webProperties,
+                                   GlobalExceptionHandler globalExceptionHandler,
+                                   TenantFrameworkService tenantFrameworkService) {
+        super(webProperties);
+        this.tenantProperties = tenantProperties;
+        this.pathMatcher = new AntPathMatcher();
+        this.globalExceptionHandler = globalExceptionHandler;
+        this.tenantFrameworkService = tenantFrameworkService;
+    }
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
+        Long tenantId = TenantContextHolder.getTenantId();
+        // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。
         LoginUser user = SecurityFrameworkUtils.getLoginUser();
-        assert user != null; // shouldNotFilter 已经校验
-        // 校验租户是否匹配。
-        if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
-            log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
-                    user.getTenantId(), user.getId(), user.getUserType(),
-                    TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
-            ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),
-                    "您无权访问该租户的数据"));
+        if (user != null) {
+            // 如果获取不到租户编号,则尝试使用登陆用户的租户编号
+            if (tenantId == null) {
+                tenantId = user.getTenantId();
+                TenantContextHolder.setTenantId(tenantId);
+            // 如果传递了租户编号,则进行比对租户编号,避免越权问题
+            } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
+                log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
+                        user.getTenantId(), user.getId(), user.getUserType(),
+                        TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
+                ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),
+                        "您无权访问该租户的数据"));
+                return;
+            }
+        }
+
+        // 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。
+        if (tenantId == null && !isIgnoreUrl(request)) {
+            log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());
+            ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
+                    "租户的请求未传递,请进行排查"));
             return;
         }
+
+        // 3. 校验租户是合法,例如说被禁用、到期
+        if (tenantId != null) {
+            try {
+                tenantFrameworkService.validTenant(tenantId);
+            } catch (Throwable ex) {
+                CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
+                ServletUtils.writeJSON(response, result);
+                return;
+            }
+        }
+
         // 继续过滤
         chain.doFilter(request, response);
     }
 
-    @Override
-    protected boolean shouldNotFilter(HttpServletRequest request) {
-        return SecurityFrameworkUtils.getLoginUser() == null;
+    private boolean isIgnoreUrl(HttpServletRequest request) {
+        // 快速匹配,保证性能
+        if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) {
+            return true;
+        }
+        // 逐个 Ant 路径匹配
+        for (String url : tenantProperties.getIgnoreUrls()) {
+            if (pathMatcher.match(url, request.getRequestURI())) {
+                return true;
+            }
+        }
+        return false;
     }
 
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/service/TenantFrameworkService.java

@@ -16,4 +16,11 @@ public interface TenantFrameworkService {
      */
     List<Long> getTenantIds();
 
+    /**
+     * 校验租户是否合法
+     *
+     * @param id 租户编号
+     */
+    void validTenant(Long id);
+
 }

+ 36 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/util/TenantUtils.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.framework.tenant.core.util;
+
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+
+/**
+ * 多租户 Util
+ *
+ * @author 芋道源码
+ */
+public class TenantUtils {
+
+    /**
+     * 使用指定租户,执行对应的逻辑
+     *
+     * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
+     * 当然,执行完成后,还是会恢复回去
+     *
+     * @param tenantId 租户编号
+     * @param runnable 逻辑
+     */
+    public static void execute(Long tenantId, Runnable runnable) {
+        Long oldTenantId = TenantContextHolder.getTenantId();
+        Boolean oldIgnore = TenantContextHolder.isIgnore();
+        try {
+            TenantContextHolder.setTenantId(tenantId);
+            TenantContextHolder.setIgnore(false);
+            // 执行逻辑
+            runnable.run();
+        } finally {
+            TenantContextHolder.setTenantId(oldTenantId);
+            TenantContextHolder.setIgnore(oldIgnore);
+        }
+    }
+
+
+}

+ 1 - 5
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories

@@ -1,6 +1,2 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
-  cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
-  cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration,\
-  cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration,\
-  cn.iocoder.yudao.framework.tenant.config.YudaoTenantSecurityAutoConfiguration
+  cn.iocoder.yudao.framework.tenant.config.YudaoTenantAutoConfiguration

+ 22 - 0
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.framework.mq.config;
 
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
@@ -10,10 +13,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisServerCommands;
 import org.springframework.data.redis.connection.stream.Consumer;
 import org.springframework.data.redis.connection.stream.ObjectRecord;
 import org.springframework.data.redis.connection.stream.ReadOffset;
 import org.springframework.data.redis.connection.stream.StreamOffset;
+import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.listener.ChannelTopic;
@@ -22,6 +27,7 @@ import org.springframework.data.redis.stream.DefaultStreamMessageListenerContain
 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
 
 import java.util.List;
+import java.util.Properties;
 
 /**
  * 消息队列配置类
@@ -73,6 +79,7 @@ public class YudaoMQAutoConfiguration {
     public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
             RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
         RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
+        checkRedisVersion(redisTemplate);
         // 第一步,创建 StreamMessageListenerContainer 容器
         // 创建 options 配置
         StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
@@ -118,4 +125,19 @@ public class YudaoMQAutoConfiguration {
         return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
     }
 
+    /**
+     * 校验 Redis 版本号,是否满足最低的版本号要求!
+     */
+    private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
+        // 获得 Redis 版本
+        Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
+        String version = MapUtil.getStr(info, "redis_version");
+        // 校验最低版本必须大于等于 5.0.0
+        int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false));
+        if (majorVersion < 5) {
+            throw new IllegalStateException(StrUtil.format("您当前的 Redis 版本为 {},小于最低要求的 5.0.0 版本!" +
+                    "请参考 {} 文档进行安装。", version, DocumentEnum.REDIS_INSTALL.getUrl()));
+        }
+    }
+
 }

+ 8 - 4
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -43,12 +43,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
     }
 
-    default Integer selectCount(String field, Object value) {
-        return selectCount(new QueryWrapper<T>().eq(field, value)).intValue();
+    default Long selectCount() {
+        return selectCount(new QueryWrapper<T>());
     }
 
-    default Integer selectCount(SFunction<T, ?> field, Object value) {
-        return selectCount(new LambdaQueryWrapper<T>().eq(field, value)).intValue();
+    default Long selectCount(String field, Object value) {
+        return selectCount(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default Long selectCount(SFunction<T, ?> field, Object value) {
+        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
     }
 
     default List<T> selectList() {

+ 7 - 2
yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/test/RedisTestConfiguration.java → yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.test;
+package cn.iocoder.yudao.framework.test.config;
 
 import com.github.fppt.jedismock.RedisServer;
 import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
@@ -9,6 +9,11 @@ import org.springframework.context.annotation.Lazy;
 
 import java.io.IOException;
 
+/**
+ * Redis 测试 Configuration,主要实现内嵌 Redis 的启动
+ *
+ * @author 芋道源码
+ */
 @Configuration(proxyBeanMethods = false)
 @Lazy(false) // 禁止延迟加载
 @EnableConfigurationProperties(RedisProperties.class)
@@ -20,7 +25,7 @@ public class RedisTestConfiguration {
     @Bean
     public RedisServer redisServer(RedisProperties properties) throws IOException {
         RedisServer redisServer = new RedisServer(properties.getPort());
-        // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
+        // 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
         try {
             redisServer.start();
         } catch (Exception ignore) {}

+ 52 - 0
yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.test.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
+import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
+import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
+import org.springframework.boot.sql.init.DatabaseInitializationSettings;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import javax.sql.DataSource;
+
+/**
+ * SQL 初始化的测试 Configuration
+ *
+ * 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢?
+ * 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true,开启延迟加载。此时,会导致 DataSourceInitializationConfiguration 初始化
+ * 不过呢,当前类的实现代码,基本是复制 DataSourceInitializationConfiguration 的哈!
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
+@ConditionalOnSingleCandidate(DataSource.class)
+@ConditionalOnClass(name = "org.springframework.jdbc.datasource.init.DatabasePopulator")
+@Lazy(value = false) // 禁止延迟加载
+@EnableConfigurationProperties(SqlInitializationProperties.class)
+public class SqlInitializationTestConfiguration {
+
+	@Bean
+	public DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
+																				   SqlInitializationProperties initializationProperties) {
+		DatabaseInitializationSettings settings = createFrom(initializationProperties);
+		return new DataSourceScriptDatabaseInitializer(dataSource, settings);
+	}
+
+	static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
+		DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
+		settings.setSchemaLocations(properties.getSchemaLocations());
+		settings.setDataLocations(properties.getDataLocations());
+		settings.setContinueOnError(properties.isContinueOnError());
+		settings.setSeparator(properties.getSeparator());
+		settings.setEncoding(properties.getEncoding());
+		settings.setMode(properties.getMode());
+		return settings;
+	}
+
+}

+ 17 - 9
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java

@@ -8,15 +8,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpHeaders;
-import springfox.documentation.RequestHandler;
 import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ExampleBuilder;
 import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.ApiKey;
-import springfox.documentation.service.AuthorizationScope;
-import springfox.documentation.service.Contact;
-import springfox.documentation.service.SecurityReference;
-import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.builders.RequestParameterBuilder;
+import springfox.documentation.service.*;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spi.service.contexts.SecurityContext;
 import springfox.documentation.spring.web.plugins.Docket;
@@ -24,7 +20,6 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.function.Predicate;
 
 import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
 
@@ -37,8 +32,8 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePacka
 @EnableSwagger2
 @EnableKnife4j
 @ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
-@ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true)
 // 允许使用 swagger.enable=false 禁用 Swagger
+@ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true)
 @EnableConfigurationProperties(SwaggerProperties.class)
 public class YudaoSwaggerAutoConfiguration {
 
@@ -62,9 +57,12 @@ public class YudaoSwaggerAutoConfiguration {
                 .paths(PathSelectors.any())
                 .build()
                 .securitySchemes(securitySchemes())
+                .globalRequestParameters(globalRequestParameters())
                 .securityContexts(securityContexts());
     }
 
+    // ========== apiInfo ==========
+
     /**
      * API 摘要信息
      */
@@ -77,6 +75,8 @@ public class YudaoSwaggerAutoConfiguration {
                 .build();
     }
 
+    // ========== securitySchemes ==========
+
     /**
      * 安全模式,这里配置通过请求头 Authorization 传递 token 参数
      */
@@ -105,4 +105,12 @@ public class YudaoSwaggerAutoConfiguration {
         return new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")};
     }
 
+    // ========== globalRequestParameters ==========
+
+    private static List<RequestParameter> globalRequestParameters() {
+        RequestParameterBuilder tenantParameter = new RequestParameterBuilder().name("tenant-id").description("租户编号")
+                .in(ParameterType.HEADER).example(new ExampleBuilder().value(1L).build());
+        return Collections.singletonList(tenantParameter.build());
+    }
+
 }

+ 27 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/ApiRequestFilter.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.web.core.filter;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.web.config.WebProperties;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 过滤 /admin-api、/app-api 等 API 请求的过滤器
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+public abstract class ApiRequestFilter extends OncePerRequestFilter {
+
+    protected final WebProperties webProperties;
+
+    @Override
+    protected boolean shouldNotFilter(HttpServletRequest request) {
+        // 只过滤 API 请求的地址
+        return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAdminApi().getPrefix(),
+                webProperties.getAppApi().getPrefix());
+    }
+
+}

+ 3 - 3
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -43,8 +43,8 @@ public interface ErrorCodeConstants {
     ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的");
 
     // ========== 流程任务 1-009-005-000 ==========
-    ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009004000, "审批任务失败,原因:该任务不处于未审批");
-    ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009004001, "审批任务失败,原因:该任务的审批人不是你");
+    ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批");
+    ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你");
 
     // ========== 流程任务分配规则 1-009-006-000 ==========
     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
@@ -55,7 +55,7 @@ public interface ErrorCodeConstants {
 
     // ========== 动态表单模块 1-009-010-000 ==========
     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");
-    ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010000, "表单项({}) 和 ({}) 使用了相同的字段名({})");
+    ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
 
     // ========== 用户组模块 1-009-011-000 ==========
     ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在");

+ 2 - 3
yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/test/BaseDbUnitTest.java

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.test;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
-import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
 import org.springframework.test.context.ActiveProfiles;
@@ -21,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbUnitTest {
 
@@ -31,7 +30,7 @@ public class BaseDbUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
-            SqlInitializationAutoConfiguration.class,
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类

+ 3 - 0
yudao-module-bpm/yudao-module-bpm-base/src/test/resources/application-unit-test.yaml

@@ -16,6 +16,9 @@ spring:
     druid:
       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
       initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
 
 mybatis:
   lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试

+ 0 - 1
yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/clean.sql

@@ -1,3 +1,2 @@
--- bpm 开头的 DB
 DELETE FROM "bpm_form";
 DELETE FROM "bpm_user_group";

+ 1 - 2
yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/create_tables.sql

@@ -1,4 +1,3 @@
--- bpm 开头的 DB
 CREATE TABLE IF NOT EXISTS "bpm_user_group" (
     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
     "name" varchar(63) NOT NULL,
@@ -11,7 +10,7 @@ CREATE TABLE IF NOT EXISTS "bpm_user_group" (
     "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
     "deleted" bit NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
-    ) COMMENT '用户组';
+) COMMENT '用户组';
 
 CREATE TABLE IF NOT EXISTS "bpm_form" (
     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,

+ 0 - 1
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/java/cn/iocoder/yudao/module/bpm/test/BaseDbUnitTest.java

@@ -20,7 +20,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbUnitTest {
 

+ 3 - 0
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/application-unit-test.yaml

@@ -16,6 +16,9 @@ spring:
     druid:
       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
       initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 0 - 2
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/sql/clean.sql

@@ -1,2 +0,0 @@
--- bpm 开头的 DB
-DELETE FROM "bpm_form";

+ 0 - 28
yudao-module-bpm/yudao-module-bpm-impl-activiti/src/test/resources/sql/create_tables.sql

@@ -1,28 +0,0 @@
-CREATE TABLE IF NOT EXISTS "bpm_form" (
-    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
-    "name" varchar(63) NOT NULL,
-    "status" tinyint NOT NULL,
-    "fields" varchar(255) NOT NULL,
-    "conf" varchar(255) NOT NULL,
-    "remark" varchar(255),
-    "creator" varchar(64) DEFAULT '',
-    "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "updater" varchar(64) DEFAULT '',
-    "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "deleted" bit NOT NULL DEFAULT FALSE,
-    PRIMARY KEY ("id")
-    ) COMMENT '动态表单';
-
-CREATE TABLE IF NOT EXISTS "bpm_user_group" (
-    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
-    "name" varchar(63) NOT NULL,
-    "description" varchar(255) NOT NULL,
-    "status" tinyint NOT NULL,
-    "member_user_ids" varchar(255) NOT NULL,
-    "creator" varchar(64) DEFAULT '',
-    "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "updater" varchar(64) DEFAULT '',
-    "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "deleted" bit NOT NULL DEFAULT FALSE,
-    PRIMARY KEY ("id")
-    ) COMMENT '用户组';

+ 1 - 0
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java

@@ -30,5 +30,6 @@ public interface ErrorCodeConstants {
     // ========= 文件相关 1001003000=================
     ErrorCode FILE_PATH_EXISTS = new ErrorCode(1001003000, "文件路径已存在");
     ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003001, "文件不存在");
+    ErrorCode FILE_IS_EMPTY = new ErrorCode(1001003002, "文件为空");
 
 }

+ 6 - 4
yudao-module-infra/yudao-module-infra-impl/pom.xml

@@ -39,10 +39,6 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
         </dependency>
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
-        </dependency>
 
         <!-- Web 相关 -->
         <dependency>
@@ -67,6 +63,12 @@
             <artifactId>yudao-spring-boot-starter-config</artifactId>
         </dependency>
 
+        <!-- Job 定时任务相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-job</artifactId>
+        </dependency>
+
         <!-- 消息队列相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 0 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -4,7 +4,6 @@ import cn.hutool.core.io.IoUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO;
 import cn.iocoder.yudao.module.infra.convert.file.FileConvert;
@@ -62,7 +61,6 @@ public class FileController {
     @ApiOperation("下载文件")
     @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class)
     public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException {
-        TenantContextHolder.setNullTenantId();
         FileDO file = fileService.getFile(path);
         if (file == null) {
             log.warn("[getFile][path({}) 文件不存在]", path);

+ 2 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.infra.dal.dataobject.file;
 
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -21,7 +21,7 @@ import java.io.InputStream;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class FileDO extends TenantBaseDO {
+public class FileDO extends BaseDO {
 
     /**
      * 文件路径

+ 2 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiAccessLogDO.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.infra.dal.dataobject.logger;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
@@ -21,7 +21,7 @@ import java.util.Date;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ApiAccessLogDO extends TenantBaseDO {
+public class ApiAccessLogDO extends BaseDO {
 
     /**
      * 编号

+ 2 - 2
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiErrorLogDO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.infra.dal.dataobject.logger;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -21,7 +21,7 @@ import java.util.Date;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ApiErrorLogDO extends TenantBaseDO {
+public class ApiErrorLogDO extends BaseDO {
 
     /**
      * 编号

+ 1 - 4
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java

@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
-import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -24,19 +23,17 @@ public interface FileMapper extends BaseMapperX<FileDO> {
                 .orderByDesc("create_time"));
     }
 
-    default Integer selectCountById(String id) {
+    default Long selectCountById(String id) {
         return selectCount(FileDO::getId, id);
     }
 
     /**
      * 基于 Path 获取文件
      * 实际上,是基于 ID 查询
-     * 由于前端使用 <img /> 的方式获取图片,所以需要忽略租户的查询
      *
      * @param path 路径
      * @return 文件
      */
-    @InterceptorIgnore(tenantLine = "true")
     default FileDO selectByPath(String path) {
         return selectById(path);
     }

+ 4 - 1
yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/BaseDbAndRedisUnitTest.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.infra.test;
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
@@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbAndRedisUnitTest {
 
@@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+
             // Redis 配置类
             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
             RedisAutoConfiguration.class, // Spring Redis 自动配置类

+ 2 - 1
yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/BaseDbUnitTest.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.infra.test;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbUnitTest {
 
@@ -30,6 +30,7 @@ public class BaseDbUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类

+ 0 - 30
yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/test/RedisTestConfiguration.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.infra.test;
-
-import com.github.fppt.jedismock.RedisServer;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-
-import java.io.IOException;
-
-@Configuration(proxyBeanMethods = false)
-@Lazy(false) // 禁止延迟加载
-@EnableConfigurationProperties(RedisProperties.class)
-public class RedisTestConfiguration {
-
-    /**
-     * 创建模拟的 Redis Server 服务器
-     */
-    @Bean
-    public RedisServer redisServer(RedisProperties properties) throws IOException {
-        RedisServer redisServer = new RedisServer(properties.getPort());
-        // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
-        try {
-            redisServer.start();
-        } catch (Exception ignore) {}
-        return redisServer;
-    }
-
-}

+ 3 - 0
yudao-module-infra/yudao-module-infra-impl/src/test/resources/application-unit-test.yaml

@@ -16,6 +16,9 @@ spring:
     druid:
       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
       initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 0 - 3
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -13,9 +13,6 @@ public interface ErrorCodeConstants {
     ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败");
 
-    // ========== 文件相关 1004002000 ===========
-    // TODO 芋艿:可以删除
-    ErrorCode FILE_IS_EMPTY = new ErrorCode(1004002000, "文件为空");
 
     // ========== AUTH 模块 1004003000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");

+ 1 - 1
yudao-module-member/yudao-module-member-impl/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppUserController.java

@@ -21,7 +21,7 @@ import java.io.IOException;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.FILE_IS_EMPTY;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
 
 @Api(tags = "用户 APP - 用户个人中心")
 @RestController

+ 4 - 1
yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/BaseDbAndRedisUnitTest.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.member.test;
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
@@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbAndRedisUnitTest {
 
@@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+
             // Redis 配置类
             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
             RedisAutoConfiguration.class, // Spring Redis 自动配置类

+ 2 - 1
yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/BaseDbUnitTest.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.member.test;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbUnitTest {
 
@@ -30,6 +30,7 @@ public class BaseDbUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类

+ 0 - 30
yudao-module-member/yudao-module-member-impl/src/test/java/cn/iocoder/yudao/module/member/test/RedisTestConfiguration.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.member.test;
-
-import com.github.fppt.jedismock.RedisServer;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-
-import java.io.IOException;
-
-@Configuration(proxyBeanMethods = false)
-@Lazy(false) // 禁止延迟加载
-@EnableConfigurationProperties(RedisProperties.class)
-public class RedisTestConfiguration {
-
-    /**
-     * 创建模拟的 Redis Server 服务器
-     */
-    @Bean
-    public RedisServer redisServer(RedisProperties properties) throws IOException {
-        RedisServer redisServer = new RedisServer(properties.getPort());
-        // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
-        try {
-            redisServer.start();
-        } catch (Exception ignore) {}
-        return redisServer;
-    }
-
-}

+ 3 - 0
yudao-module-member/yudao-module-member-impl/src/test/resources/application-unit-test.yaml

@@ -16,6 +16,9 @@ spring:
     druid:
       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
       initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 4 - 0
yudao-module-pay/yudao-module-pay-impl/pom.xml

@@ -33,6 +33,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
 
         <!-- Web 相关 -->
         <dependency>

+ 1 - 3
yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.pay.dal.mysql.merchant;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
@@ -23,7 +22,6 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
     }
 
     @Select("SELECT id FROM pay_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
-    @InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户。原因:该方法被异步 task 调用,此时获取不到租户编号
     Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
 
     default PageResult<PayChannelDO> selectPage(PayChannelPageReqVO reqVO) {

+ 15 - 9
yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java

@@ -6,19 +6,21 @@ import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -27,12 +29,12 @@ import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS;
 
 /**
  * 支付渠道 Service 实现类
@@ -64,11 +66,16 @@ public class PayChannelServiceImpl implements PayChannelService {
     @Resource
     private Validator validator;
 
+    @Resource
+    @Lazy // 注入自己,所以延迟加载
+    private PayChannelService self;
+
     @Override
     @PostConstruct
+    @TenantIgnore // 忽略自动化租户,全局初始化本地缓存
     public void initPayClients() {
         // 获取支付渠道,如果有更新
-        List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);
+        List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime);
         if (CollUtil.isEmpty(payChannels)) {
             return;
         }
@@ -78,14 +85,13 @@ public class PayChannelServiceImpl implements PayChannelService {
                 payChannel.getCode(), payChannel.getConfig()));
 
         // 写入缓存
-        assert payChannels.size() > 0; // 断言,避免告警
-        maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime);
         log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
     }
 
     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
     public void schedulePeriodicRefresh() {
-        initPayClients();
+        self.initPayClients();
     }
 
     /**

+ 4 - 1
yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseDbAndRedisUnitTest.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.test;
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
@@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbAndRedisUnitTest {
 
@@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+
             // Redis 配置类
             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
             RedisAutoConfiguration.class, // Spring Redis 自动配置类

+ 2 - 1
yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseDbUnitTest.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.test;
 
 import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
 import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql;
  */
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
 @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
-@Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表
 @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
 public class BaseDbUnitTest {
 
@@ -30,6 +30,7 @@ public class BaseDbUnitTest {
             DataSourceAutoConfiguration.class, // Spring DB 自动配置类
             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
             DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+            SqlInitializationTestConfiguration.class, // SQL 初始化
             // MyBatis 配置类
             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类

+ 1 - 0
yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/BaseRedisUnitTest.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.test;
 
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;

+ 0 - 30
yudao-module-pay/yudao-module-pay-impl/src/test/java/cn/iocoder/yudao/module/pay/test/RedisTestConfiguration.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.pay.test;
-
-import com.github.fppt.jedismock.RedisServer;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-
-import java.io.IOException;
-
-@Configuration(proxyBeanMethods = false)
-@Lazy(false) // 禁止延迟加载
-@EnableConfigurationProperties(RedisProperties.class)
-public class RedisTestConfiguration {
-
-    /**
-     * 创建模拟的 Redis Server 服务器
-     */
-    @Bean
-    public RedisServer redisServer(RedisProperties properties) throws IOException {
-        RedisServer redisServer = new RedisServer(properties.getPort());
-        // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
-        try {
-            redisServer.start();
-        } catch (Exception ignore) {}
-        return redisServer;
-    }
-
-}

+ 3 - 0
yudao-module-pay/yudao-module-pay-impl/src/test/resources/application-unit-test.yaml

@@ -16,6 +16,9 @@ spring:
     druid:
       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
       initial-size: 1 # 单元测试,配置为 1,提升启动速度
+  sql:
+    init:
+      schema-locations: classpath:/sql/create_tables.sql
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 2 - 1
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/LoginLogCreateReqDTO.java

@@ -54,8 +54,9 @@ public class LoginLogCreateReqDTO {
     private String userIp;
     /**
      * 浏览器 UserAgent
+     *
+     * 允许空,原因:Job 过期登出时,是无法传递 UserAgent 的
      */
-    @NotEmpty(message = "浏览器 UserAgent 不能为空")
     private String userAgent;
 
 }

+ 78 - 73
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -18,52 +18,54 @@ public interface ErrorCodeConstants {
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期");
 
-    // ========== 菜单模块 1002002000 ==========
-    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002002000, "已经存在该名字的菜单");
-    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002002001, "父菜单不存在");
-    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002002002, "不能设置自己为父菜单");
-    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002002003, "菜单不存在");
-    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002002004, "存在子菜单,无法删除");
-    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002002005, "父菜单的类型必须是目录或者菜单");
-
-    // ========== 角色模块 1002003000 ==========
-    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002003000, "角色不存在");
-    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002003001, "已经存在名为【{}】的角色");
-    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002003002, "已经存在编码为【{}】的角色");
-    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002003004, "不能操作类型为系统内置的角色");
-    ErrorCode ROLE_IS_DISABLE = new ErrorCode(1002003004, "名字为【{}】的角色已被禁用");
-
-    // ========== 用户模块 1002004000 ==========
-    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002004000, "用户账号已经存在");
-    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002004001, "手机号已经存在");
-    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002004002, "邮箱已经存在");
-    ErrorCode USER_NOT_EXISTS = new ErrorCode(1002004003, "用户不存在");
-    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002004004, "导入用户数据不能为空!");
-    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002004005, "用户密码校验失败");
-    ErrorCode USER_IS_DISABLE = new ErrorCode(1002003004, "名字为【{}】的用户已被禁用");
-
-    // ========== 部门模块 1002005000 ==========
-    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004001, "已经存在该名字的部门");
-    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004002,"父级部门不存在");
-    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004003, "当前部门不存在");
-    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004004, "存在子部门,无法删除");
-    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004005, "不能设置自己为父部门");
-    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004006, "部门中存在员工,无法删除");
-    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004007, "部门不处于开启状态,不允许选择");
-    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004008, "不能设置自己的子部门为父部门");
+    // ========== 菜单模块 1002001000 ==========
+    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002001000, "已经存在该名字的菜单");
+    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002001001, "父菜单不存在");
+    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002001002, "不能设置自己为父菜单");
+    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002001003, "菜单不存在");
+    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002001004, "存在子菜单,无法删除");
+    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002001005, "父菜单的类型必须是目录或者菜单");
+
+    // ========== 角色模块 1002002000 ==========
+    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002002000, "角色不存在");
+    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002002001, "已经存在名为【{}】的角色");
+    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002002002, "已经存在编码为【{}】的角色");
+    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002002003, "不能操作类型为系统内置的角色");
+    ErrorCode ROLE_IS_DISABLE = new ErrorCode(1002002004, "名字为【{}】的角色已被禁用");
+    ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1002002005, "编码【{}】不能使用");
+
+    // ========== 用户模块 1002003000 ==========
+    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002003000, "用户账号已经存在");
+    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002003001, "手机号已经存在");
+    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002003002, "邮箱已经存在");
+    ErrorCode USER_NOT_EXISTS = new ErrorCode(1002003003, "用户不存在");
+    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002003004, "导入用户数据不能为空!");
+    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002003005, "用户密码校验失败");
+    ErrorCode USER_IS_DISABLE = new ErrorCode(1002003006, "名字为【{}】的用户已被禁用");
+    ErrorCode USER_COUNT_MAX = new ErrorCode(1002003008, "创建用户失败,原因:超过租户最大租户配额({})!");
+
+    // ========== 部门模块 1002004000 ==========
+    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004000, "已经存在该名字的部门");
+    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004001,"父级部门不存在");
+    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004002, "当前部门不存在");
+    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004003, "存在子部门,无法删除");
+    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004004, "不能设置自己为父部门");
+    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004005, "部门中存在员工,无法删除");
+    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004006, "部门不处于开启状态,不允许选择");
+    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004007, "不能设置自己的子部门为父部门");
 
     // ========== 岗位模块 1002005000 ==========
-    ErrorCode POST_NOT_FOUND = new ErrorCode(1002005001, "当前岗位不存在");
-    ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005002, "岗位({}) 不处于开启状态,不允许选择");
-    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005001, "已经存在该名字的岗位");
-    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005001, "已经存在该标识的岗位");
+    ErrorCode POST_NOT_FOUND = new ErrorCode(1002005000, "当前岗位不存在");
+    ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005001, "岗位({}) 不处于开启状态,不允许选择");
+    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005002, "已经存在该名字的岗位");
+    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005003, "已经存在该标识的岗位");
 
     // ========== 字典类型 1002006000 ==========
     ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1002006001, "当前字典类型不存在");
     ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1002006002, "字典类型不处于开启状态,不允许选择");
     ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1002006003, "已经存在该名字的字典类型");
     ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1002006004, "已经存在该类型的字典类型");
-    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006004, "无法删除,该字典类型还有字典数据");
+    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006005, "无法删除,该字典类型还有字典数据");
 
     // ========== 字典数据 1002007000 ==========
     ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1002007001, "当前字典数据不存在");
@@ -73,45 +75,48 @@ public interface ErrorCodeConstants {
     // ========== 通知公告 1002008000 ==========
     ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1002008001, "当前通知公告不存在");
 
-    // ========== 文件 1002009000 ==========
-    ErrorCode FILE_PATH_EXISTS = new ErrorCode(1002009001, "文件路径已经存在");
-    ErrorCode FILE_UPLOAD_FAILED = new ErrorCode(1002009002, "文件上传失败");
-    ErrorCode FILE_IS_EMPTY= new ErrorCode(1002009003, "文件为空");
-
     // ========== 短信渠道 1002011000 ==========
     ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1002011000, "短信渠道不存在");
     ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1002011001, "短信渠道不处于开启状态,不允许选择");
     ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1002011002, "无法删除,该短信渠道还有短信模板");
 
-    // ========== 短信模板 1002011000 ==========
-    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002011000, "短信模板不存在");
-    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002011001, "已经存在编码为【{}】的短信模板");
-
-    // ========== 短信发送 1002012000 ==========
-    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002012000, "手机号不存在");
-    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002012001, "模板参数({})缺失");
-    ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002012002, "短信模板不存在");
-
-    // ========== 短信验证码 1002013000 ==========
-    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002013000, "验证码不存在");
-    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002013001, "验证码已过期");
-    ErrorCode SMS_CODE_USED = new ErrorCode(1002013002, "验证码已使用");
-    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1002013004, "验证码不正确");
-    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1002013005, "超过每日短信发送数量");
-    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1002013006, "短信发送过于频率");
-    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1002013007, "手机号已被使用");
-    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1002013008, "验证码未被使用");
-
-    // ========== 租户模块 1002014000 ==========
-    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1002014000, "租户不存在");
-
-    // ========== 错误码模块 1002015000 ==========
-    ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002015000, "错误码不存在");
-    ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002015001, "已经存在编码为【{}】的错误码");
-
-    // ========== 社交用户 1002015000 ==========
-    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1002015000, "社交授权失败,原因是:{}");
-    ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002015001, "社交解绑失败,非当前用户绑定");
-    ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002015001, "社交授权失败,找不到对应的用户");
+    // ========== 短信模板 1002012000 ==========
+    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002012000, "短信模板不存在");
+    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002012001, "已经存在编码为【{}】的短信模板");
+
+    // ========== 短信发送 1002013000 ==========
+    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002013000, "手机号不存在");
+    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002013001, "模板参数({})缺失");
+    ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002013002, "短信模板不存在");
+
+    // ========== 短信验证码 1002014000 ==========
+    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002014000, "验证码不存在");
+    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002014001, "验证码已过期");
+    ErrorCode SMS_CODE_USED = new ErrorCode(1002014002, "验证码已使用");
+    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1002014003, "验证码不正确");
+    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1002014004, "超过每日短信发送数量");
+    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1002014005, "短信发送过于频率");
+    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1002014006, "手机号已被使用");
+    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1002014007, "验证码未被使用");
+
+    // ========== 租户信息 1002015000 ==========
+    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1002015000, "租户不存在");
+    ErrorCode TENANT_DISABLE = new ErrorCode(1002015001, "名字为【{}】的租户已被禁用");
+    ErrorCode TENANT_EXPIRE = new ErrorCode(1002015002, "名字为【{}】的租户已过期");
+    ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1002015003, "系统租户不能进行修改、删除等操作!");
+
+    // ========== 租户套餐 1002016000 ==========
+    ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1002016000, "租户套餐不存在");
+    ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1002016001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
+    ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1002016002, "名字为【{}】的租户套餐已被禁用");
+
+    // ========== 错误码模块 1002017000 ==========
+    ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002017000, "错误码不存在");
+    ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002017001, "已经存在编码为【{}】的错误码");
+
+    // ========== 社交用户 1002018000 ==========
+    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1002018000, "社交授权失败,原因是:{}");
+    ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定");
+    ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户");
 
 }

+ 8 - 2
yudao-module-system/yudao-module-system-impl/pom.xml

@@ -53,11 +53,11 @@
         </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+            <artifactId>yudao-spring-boot-starter-biz-social</artifactId>
         </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-biz-social</artifactId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
         </dependency>
 
         <!-- Web 相关 -->
@@ -77,6 +77,12 @@
             <artifactId>yudao-spring-boot-starter-redis</artifactId>
         </dependency>
 
+        <!-- Job 定时任务相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-job</artifactId>
+        </dependency>
+
         <!-- 消息队列相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -72,7 +72,7 @@ public class AuthController {
         // 获得角色列表
         List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds());
         // 获得菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenusFromCache(
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
                 getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
                 SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
@@ -84,7 +84,7 @@ public class AuthController {
     @ApiOperation("获得登录用户的菜单列表")
     public CommonResult<List<AuthMenuRespVO>> getMenus() {
         // 获得用户拥有的菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenusFromCache(
+        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(
                 getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
                 SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的

+ 1 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptBaseVO.java

@@ -20,8 +20,7 @@ public class DeptBaseVO {
     @Size(max = 30, message = "部门名称长度不能超过30个字符")
     private String name;
 
-    @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
-    @NotNull(message = "父菜单 ID 不能为空")
+    @ApiModelProperty(value = "父菜单 ID", example = "1024")
     private Long parentId;
 
     @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")

+ 8 - 4
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*;
 import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.service.permission.MenuService;
+import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
@@ -28,6 +29,8 @@ public class MenuController {
 
     @Resource
     private MenuService menuService;
+    @Resource
+    private TenantService tenantService;
 
     @PostMapping("/create")
     @ApiOperation("创建菜单")
@@ -55,7 +58,7 @@ public class MenuController {
     }
 
     @GetMapping("/list")
-    @ApiOperation("获取菜单列表")
+    @ApiOperation(value = "获取菜单列表", notes = "用于【菜单管理】界面")
     @PreAuthorize("@ss.hasPermission('system:menu:query')")
     public CommonResult<List<MenuRespVO>> getMenus(MenuListReqVO reqVO) {
         List<MenuDO> list = menuService.getMenus(reqVO);
@@ -64,13 +67,14 @@ public class MenuController {
     }
 
     @GetMapping("/list-all-simple")
-    @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,主要用于前端的下拉选项")
+    @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" +
+            "在多租户的场景下,会只返回租户所在套餐有的菜单")
     public CommonResult<List<MenuSimpleRespVO>> getSimpleMenus() {
         // 获得菜单列表,只要开启状态的
         MenuListReqVO reqVO = new MenuListReqVO();
         reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        List<MenuDO> list = menuService.getMenus(reqVO);
-        // 排序后,返回个诶前端
+        List<MenuDO> list = menuService.getTenantMenus(reqVO);
+        // 排序后,返回前端
         list.sort(Comparator.comparing(MenuDO::getSort));
         return success(MenuConvert.INSTANCE.convertList02(list));
     }

+ 15 - 7
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java

@@ -1,13 +1,16 @@
 package cn.iocoder.yudao.module.system.controller.admin.permission;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
+import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -29,28 +32,33 @@ public class PermissionController {
 
     @Resource
     private PermissionService permissionService;
+    @Resource
+    private TenantService tenantService;
 
-    // TODO @芋艿:处理下全新啊标识
 
     @ApiOperation("获得角色拥有的菜单编号")
     @ApiImplicitParam(name = "roleId", value = "角色编号", required = true, dataTypeClass = Long.class)
     @GetMapping("/list-role-resources")
-//    @RequiresPermissions("system:permission:assign-role-menu")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
     public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
-        return success(permissionService.listRoleMenuIds(roleId));
+        return success(permissionService.getRoleMenuIds(roleId));
     }
 
     @PostMapping("/assign-role-menu")
     @ApiOperation("赋予角色菜单")
-//    @RequiresPermissions("system:permission:assign-role-resource")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
     public CommonResult<Boolean> assignRoleMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) {
+        // 开启多租户的情况下,需要过滤掉未开通的菜单
+        tenantService.handleTenantMenu(menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId)));
+
+        // 执行菜单的分配
         permissionService.assignRoleMenu(reqVO.getRoleId(), reqVO.getMenuIds());
         return success(true);
     }
 
     @PostMapping("/assign-role-data-scope")
     @ApiOperation("赋予角色数据权限")
-//    @RequiresPermissions("system:permission:assign-role-data-scope")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')")
     public CommonResult<Boolean> assignRoleDataScope(@Valid @RequestBody PermissionAssignRoleDataScopeReqVO reqVO) {
         permissionService.assignRoleDataScope(reqVO.getRoleId(), reqVO.getDataScope(), reqVO.getDataScopeDeptIds());
         return success(true);
@@ -59,14 +67,14 @@ public class PermissionController {
     @ApiOperation("获得管理员拥有的角色编号列表")
     @ApiImplicitParam(name = "userId", value = "用户编号", required = true, dataTypeClass = Long.class)
     @GetMapping("/list-user-roles")
-//    @RequiresPermissions("system:permission:assign-user-role")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')")
     public CommonResult<Set<Long>> listAdminRoles(@RequestParam("userId") Long userId) {
         return success(permissionService.getUserRoleIdListByUserId(userId));
     }
 
     @ApiOperation("赋予用户角色")
     @PostMapping("/assign-user-role")
-//    @RequiresPermissions("system:permission:assign-user-role")
+    @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')")
     public CommonResult<Boolean> assignUserRole(@Validated @RequestBody PermissionAssignUserRoleReqVO reqVO) {
         permissionService.assignUserRole(reqVO.getUserId(), reqVO.getRoleIds());
         return success(true);

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/RoleController.java

@@ -40,7 +40,7 @@ public class RoleController {
     @ApiOperation("创建角色")
     @PreAuthorize("@ss.hasPermission('system:role:create')")
     public CommonResult<Long> createRole(@Valid @RequestBody RoleCreateReqVO reqVO) {
-        return success(roleService.createRole(reqVO));
+        return success(roleService.createRole(reqVO, null));
     }
 
     @PutMapping("/update")
@@ -88,7 +88,7 @@ public class RoleController {
     public CommonResult<List<RoleSimpleRespVO>> getSimpleRoles() {
         // 获得角色列表,只要开启状态的
         List<RoleDO> list = roleService.getRoles(Collections.singleton(CommonStatusEnum.ENABLE.getStatus()));
-        // 排序后,返回个诶前端
+        // 排序后,返回前端
         list.sort(Comparator.comparing(RoleDO::getSort));
         return success(RoleConvert.INSTANCE.convertList02(list));
     }

+ 6 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java

@@ -6,6 +6,8 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.validation.constraints.NotNull;
+
 @ApiModel("管理后台 - 菜单精简信息 Response VO")
 @Data
 @NoArgsConstructor
@@ -21,4 +23,8 @@ public class MenuSimpleRespVO {
     @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
     private Long parentId;
 
+    @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 MenuTypeEnum 枚举类")
+    @NotNull(message = "菜单类型不能为空")
+    private Integer type;
+
 }

+ 0 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleBaseVO.java

@@ -28,9 +28,6 @@ public class RoleBaseVO {
     @NotNull(message = "显示顺序不能为空")
     private Integer sort;
 
-    @ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 RoleTypeEnum 枚举")
-    private Integer type;
-
     @ApiModelProperty(value = "备注", example = "我是一个角色")
     private String remark;
 

+ 2 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java

@@ -19,7 +19,6 @@ import javax.servlet.http.HttpServletRequest;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO 芋艿:这块的接口命名,在纠结下
 @Api(tags = "管理后台 - 短信回调")
 @RestController
 @RequestMapping("/system/sms/callback")
@@ -28,7 +27,7 @@ public class SmsCallbackController {
     @Resource
     private SmsSendService smsSendService;
 
-    @PostMapping("/sms/yunpian")
+    @PostMapping("/yunpian")
     @ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档")
     @ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = String.class)
     @OperateLog(enable = false)
@@ -38,7 +37,7 @@ public class SmsCallbackController {
         return "SUCCESS"; // 约定返回 SUCCESS 为成功
     }
 
-    @PostMapping("/sms/aliyun")
+    @PostMapping("/aliyun")
     @ApiOperation(value = "阿里云短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
     @OperateLog(enable = false)
     public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {

+ 18 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.http

@@ -0,0 +1,18 @@
+### 创建租户 /admin-api/system/tenant/create
+POST {{baseUrl}}/system/tenant/create
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+{
+  "name": "芋道",
+  "contactName": "芋艿",
+  "contactMobile": "15601691300",
+  "status": 0,
+  "domain": "https://www.iocoder.cn",
+  "packageId": 110,
+  "expireTime": 1699545600000,
+  "accountCount": 20,
+  "username": "admin",
+  "password": "123321"
+}

+ 4 - 14
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java

@@ -1,13 +1,13 @@
 package cn.iocoder.yudao.module.system.controller.admin.tenant;
 
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.*;
-import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert;
-import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
-import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.*;
+import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
@@ -18,7 +18,6 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
-import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -73,15 +72,6 @@ public class TenantController {
         return success(TenantConvert.INSTANCE.convert(tenant));
     }
 
-    @GetMapping("/list")
-    @ApiOperation("获得租户列表")
-    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
-    @PreAuthorize("@ss.hasPermission('system:tenant:query')")
-    public CommonResult<List<TenantRespVO>> getTenantList(@RequestParam("ids") Collection<Long> ids) {
-        List<TenantDO> list = tenantService.getTenantList(ids);
-        return success(TenantConvert.INSTANCE.convertList(list));
-    }
-
     @GetMapping("/page")
     @ApiOperation("获得租户分页")
     @PreAuthorize("@ss.hasPermission('system:tenant:query')")

+ 81 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantPackageController.java

@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.*;
+import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
+import cn.iocoder.yudao.module.system.service.tenant.TenantPackageService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 租户套餐")
+@RestController
+@RequestMapping("/system/tenant-package")
+@Validated
+public class TenantPackageController {
+
+    @Resource
+    private TenantPackageService tenantPackageService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建租户套餐")
+    @PreAuthorize("@ss.hasPermission('system:tenant-package:create')")
+    public CommonResult<Long> createTenantPackage(@Valid @RequestBody TenantPackageCreateReqVO createReqVO) {
+        return success(tenantPackageService.createTenantPackage(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新租户套餐")
+    @PreAuthorize("@ss.hasPermission('system:tenant-package:update')")
+    public CommonResult<Boolean> updateTenantPackage(@Valid @RequestBody TenantPackageUpdateReqVO updateReqVO) {
+        tenantPackageService.updateTenantPackage(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除租户套餐")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:tenant-package:delete')")
+    public CommonResult<Boolean> deleteTenantPackage(@RequestParam("id") Long id) {
+        tenantPackageService.deleteTenantPackage(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得租户套餐")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('system:tenant-package:query')")
+    public CommonResult<TenantPackageRespVO> getTenantPackage(@RequestParam("id") Long id) {
+        TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(id);
+        return success(TenantPackageConvert.INSTANCE.convert(tenantPackage));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得租户套餐分页")
+    @PreAuthorize("@ss.hasPermission('system:tenant-package:query')")
+    public CommonResult<PageResult<TenantPackageRespVO>> getTenantPackagePage(@Valid TenantPackagePageReqVO pageVO) {
+        PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(pageVO);
+        return success(TenantPackageConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/get-simple-list")
+    @ApiOperation(value = "获取租户套餐精简信息列表", notes = "只包含被开启的租户套餐,主要用于前端的下拉选项")
+    public CommonResult<List<TenantPackageSimpleRespVO>> getTenantPackageList() {
+        // 获得角色列表,只要开启状态的
+        List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(TenantPackageConvert.INSTANCE.convertList02(list));
+    }
+
+}

+ 0 - 29
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantBaseVO.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
-
-import lombok.*;
-import io.swagger.annotations.*;
-import javax.validation.constraints.*;
-
-/**
-* 租户 Base VO,提供给添加、修改、详细的子 VO 使用
-* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
-*/
-@Data
-public class TenantBaseVO {
-
-    @ApiModelProperty(value = "租户名", required = true, example = "芋道")
-    @NotNull(message = "租户名不能为空")
-    private String name;
-
-    @ApiModelProperty(value = "联系人", required = true, example = "芋艿")
-    @NotNull(message = "联系人不能为空")
-    private String contactName;
-
-    @ApiModelProperty(value = "联系手机", example = "15601691300")
-    private String contactMobile;
-
-    @ApiModelProperty(value = "租户状态(0正常 1停用)", required = true, example = "1")
-    @NotNull(message = "租户状态(0正常 1停用)不能为空")
-    private Integer status;
-
-}

+ 0 - 12
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantCreateReqVO.java

@@ -1,12 +0,0 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
-
-import lombok.*;
-import io.swagger.annotations.*;
-
-@ApiModel("管理后台 - 租户创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class TenantCreateReqVO extends TenantBaseVO {
-
-}

+ 31 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageBaseVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+/**
+* 租户套餐 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class TenantPackageBaseVO {
+
+    @ApiModelProperty(value = "套餐名", required = true, example = "VIP")
+    @NotNull(message = "套餐名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注", example = "好")
+    private String remark;
+
+    @ApiModelProperty(value = "关联的菜单编号", required = true)
+    @NotNull(message = "关联的菜单编号不能为空")
+    private Set<Long> menuIds;
+
+}

+ 14 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 租户套餐创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TenantPackageCreateReqVO extends TenantPackageBaseVO {
+
+}

+ 38 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 租户套餐分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TenantPackagePageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "套餐名", example = "VIP")
+    private String name;
+
+    @ApiModelProperty(value = "状态", example = "1")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注", example = "好")
+    private String remark;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 23 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 租户套餐 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TenantPackageRespVO extends TenantPackageBaseVO {
+
+    @ApiModelProperty(value = "套餐编号", required = true, example = "1024")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 21 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 租户套餐精简 Response VO")
+@Data
+public class TenantPackageSimpleRespVO {
+
+    @ApiModelProperty(value = "套餐编号", required = true, example = "1024")
+    @NotNull(message = "套餐编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "套餐名", required = true, example = "VIP")
+    @NotNull(message = "套餐名不能为空")
+    private String name;
+
+}

+ 18 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 租户套餐更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TenantPackageUpdateReqVO extends TenantPackageBaseVO {
+
+    @ApiModelProperty(value = "套餐编号", required = true, example = "1024")
+    @NotNull(message = "套餐编号不能为空")
+    private Long id;
+
+}

+ 48 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantBaseVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.*;
+import java.util.Date;
+
+/**
+* 租户 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class TenantBaseVO {
+
+    @ApiModelProperty(value = "租户名", required = true, example = "芋道")
+    @NotNull(message = "租户名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "联系人", required = true, example = "芋艿")
+    @NotNull(message = "联系人不能为空")
+    private String contactName;
+
+    @ApiModelProperty(value = "联系手机", example = "15601691300")
+    private String contactMobile;
+
+    @ApiModelProperty(value = "租户状态", required = true, example = "1")
+    @NotNull(message = "租户状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "绑定域名", example = "https://www.iocoder.cn")
+    @URL(message = "绑定域名的地址非 URL 格式")
+    private String domain;
+
+    @ApiModelProperty(value = "租户套餐编号", required = true, example = "1024")
+    @NotNull(message = "租户套餐编号不能为空")
+    private Long packageId;
+
+    @ApiModelProperty(value = "过期时间", required = true)
+    @NotNull(message = "过期时间不能为空")
+    private Date expireTime;
+
+    @ApiModelProperty(value = "账号数量", required = true, example = "1024")
+    @NotNull(message = "账号数量不能为空")
+    private Integer accountCount;
+
+}

+ 29 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantCreateReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+@ApiModel("管理后台 - 租户创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TenantCreateReqVO extends TenantBaseVO {
+
+    @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
+    @NotBlank(message = "用户账号不能为空")
+    @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成")
+    @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符")
+    private String username;
+
+    @ApiModelProperty(value = "密码", required = true, example = "123456")
+    @NotEmpty(message = "密码不能为空")
+    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+    private String password;
+
+}

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantExcelVO.java → yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantExcelVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantExportReqVO.java → yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantExportReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantPageReqVO.java → yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.annotations.ApiModel;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantRespVO.java → yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
 
 import lombok.*;
 import java.util.*;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/TenantUpdateReqVO.java → yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantUpdateReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.controller.admin.tenant.vo;
+package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant;
 
 import lombok.*;
 import io.swagger.annotations.*;

+ 7 - 7
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java

@@ -1,24 +1,24 @@
 package cn.iocoder.yudao.module.system.controller.admin.user;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.user.UserConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.dept.PostService;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
-import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -31,9 +31,9 @@ import javax.validation.Valid;
 import java.io.IOException;
 import java.util.List;
 
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.FILE_IS_EMPTY;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
 
 @Api(tags = "管理后台 - 用户个人中心")
 @RestController

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserBaseVO.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.system.controller.admin.user.vo.user;
 
+import cn.iocoder.yudao.framework.common.validation.Mobile;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.Email;
 import javax.validation.constraints.NotBlank;
@@ -42,7 +42,7 @@ public class UserBaseVO {
     private String email;
 
     @ApiModelProperty(value = "手机号码", example = "15601691300")
-    @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
+    @Mobile
     private String mobile;
 
     @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类")

+ 3 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/permission/RoleConvert.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.convert.permission;
 
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.*;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
+import cn.iocoder.yudao.module.system.service.permission.bo.RoleCreateReqBO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -22,4 +23,6 @@ public interface RoleConvert {
 
     List<RoleExcelVO> convertList03(List<RoleDO> list);
 
+    RoleDO convert(RoleCreateReqBO bean);
+
 }

+ 14 - 5
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/tenant/TenantConvert.java

@@ -1,11 +1,12 @@
 package cn.iocoder.yudao.module.system.convert.tenant;
 
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantCreateReqVO;
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantExcelVO;
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantRespVO;
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantUpdateReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExcelVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserCreateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -33,4 +34,12 @@ public interface TenantConvert {
 
     List<TenantExcelVO> convertList02(List<TenantDO> list);
 
+    default UserCreateReqVO convert02(TenantCreateReqVO bean) {
+        UserCreateReqVO reqVO = new UserCreateReqVO();
+        reqVO.setUsername(bean.getUsername());
+        reqVO.setPassword(bean.getPassword());
+        reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile());
+        return reqVO;
+    }
+
 }

+ 37 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/tenant/TenantPackageConvert.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.system.convert.tenant;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSimpleRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageSimpleRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 租户套餐 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface TenantPackageConvert {
+
+    TenantPackageConvert INSTANCE = Mappers.getMapper(TenantPackageConvert.class);
+
+    TenantPackageDO convert(TenantPackageCreateReqVO bean);
+
+    TenantPackageDO convert(TenantPackageUpdateReqVO bean);
+
+    TenantPackageRespVO convert(TenantPackageDO bean);
+
+    List<TenantPackageRespVO> convertList(List<TenantPackageDO> list);
+
+    PageResult<TenantPackageRespVO> convertPage(PageResult<TenantPackageDO> page);
+
+    List<TenantPackageSimpleRespVO> convertList02(List<TenantPackageDO> list);
+
+}

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.auth;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -25,7 +25,7 @@ import java.util.Date;
 @Data
 @Builder
 @EqualsAndHashCode(callSuper = true)
-public class UserSessionDO extends TenantBaseDO {
+public class UserSessionDO extends BaseDO {
 
     /**
      * 会话编号, 即 sessionId

+ 3 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/DeptDO.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.dept;
 
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
@@ -17,7 +17,7 @@ import lombok.EqualsAndHashCode;
 @TableName("system_dept")
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class DeptDO extends TenantBaseDO {
+public class DeptDO extends BaseDO {
 
     /**
      * 部门ID

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/PostDO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.dept;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
 @TableName("system_post")
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class PostDO extends TenantBaseDO {
+public class PostDO extends BaseDO {
 
     /**
      * 岗位序号

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogDO.java

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.system.dal.dataobject.logger;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
-import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +22,7 @@ import java.util.Map;
 @TableName(value = "system_operate_log", autoResultMap = true)
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class OperateLogDO extends TenantBaseDO {
+public class OperateLogDO extends BaseDO {
 
     /**
      * {@link #javaMethodArgs} 的最大长度

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels