Преглед изворни кода

!286 合并 多租户功能
* add 新增 ruoyi-common-tenant 多租户模块 全框架适配多租户改动
* update 优化 隐藏页面主键
* remove 移除 缓存列表功能(多租户缓存功能繁杂多样 没有办法在页面管理)
* update 重构 全局缓存KEY 与 常用缓存KEY做区分
* update 重构 OssFactory 加载方式 改为每次比对配置做实例更新
* update 优化 SaTokenDao 改为 Bean 注入 便于扩展
* update 重构 项目初始化数据改为懒加载 不提供热加载
* update 重构 验证码开关使用配置文件(经调查少有动态开启需求)
* update 优化 启用 sqlserver 高版本语法 简化sql脚本语法
* update 优化 DataPermissionHelper 增加 开启/关闭 忽略数据权限功能
* update 优化 连接池增加 keepaliveTime 探活参数
* update 优化 调整连接池最长生命周期 防止出现警告
* update 优化 代码生成页面模板 校验不必要的表单数据
* add 新增 StringUtils splitTo 与 splitList 方法 优化业务代码

疯狂的狮子Li пре 2 година
родитељ
комит
45ac0f23e1
100 измењених фајлова са 1659 додато и 570 уклоњено
  1. 3 3
      pom.xml
  2. 5 7
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
  3. 37 8
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java
  4. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysRegisterController.java
  5. 19 0
      ruoyi-admin/src/main/java/com/ruoyi/web/domain/vo/TenantListVo.java
  6. 78 37
      ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java
  7. 17 12
      ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java
  8. 3 1
      ruoyi-admin/src/main/resources/application-dev.yml
  9. 3 1
      ruoyi-admin/src/main/resources/application-prod.yml
  10. 15 2
      ruoyi-admin/src/main/resources/application.yml
  11. 5 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  12. 5 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  13. 5 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  14. 1 0
      ruoyi-common/pom.xml
  15. 7 0
      ruoyi-common/ruoyi-common-bom/pom.xml
  16. 0 5
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/config/RuoYiConfig.java
  17. 1 25
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
  18. 5 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java
  19. 5 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java
  20. 39 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java
  21. 51 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java
  22. 6 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
  23. 5 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
  24. 6 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java
  25. 30 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java
  26. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java
  27. 1 1
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StreamUtils.java
  28. 60 7
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java
  29. 3 1
      ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/annotation/ExcelDictFormat.java
  30. 2 2
      ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java
  31. 2 2
      ruoyi-common/ruoyi-common-idempotent/src/main/java/com/ruoyi/common/idempotent/aspectj/RepeatSubmitAspect.java
  32. 1 0
      ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java
  33. 5 0
      ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java
  34. 5 0
      ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java
  35. 0 41
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TreeEntity.java
  36. 8 1
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java
  37. 2 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/page/PageQuery.java
  38. 1 1
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java
  39. 1 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java
  40. 17 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/helper/DataPermissionHelper.java
  41. 13 0
      ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/core/OssClient.java
  42. 13 29
      ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
  43. 2 2
      ruoyi-common/ruoyi-common-ratelimiter/src/main/java/com/ruoyi/common/ratelimiter/annotation/RateLimiter.java
  44. 10 0
      ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/config/SaTokenConfig.java
  45. 0 2
      ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/core/dao/PlusSaTokenDao.java
  46. 41 16
      ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java
  47. 0 1
      ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  48. 0 6
      ruoyi-common/ruoyi-common-security/pom.xml
  49. 1 1
      ruoyi-common/ruoyi-common-sms/src/main/java/com/ruoyi/common/sms/core/TencentSmsTemplate.java
  50. 37 0
      ruoyi-common/ruoyi-common-tenant/pom.xml
  51. 100 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java
  52. 21 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java
  53. 114 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java
  54. 20 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java
  55. 57 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java
  56. 58 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java
  57. 112 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java
  58. 32 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java
  59. 27 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java
  60. 1 0
      ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  61. 1 1
      ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/config/FilterConfig.java
  62. 2 0
      ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/config/properties/CaptchaProperties.java
  63. 1 1
      ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/filter/XssFilter.java
  64. 9 9
      ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java
  65. 7 7
      ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestTreeController.java
  66. 7 2
      ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestTree.java
  67. 9 5
      ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestTreeBo.java
  68. 5 10
      ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/constant/GenConstants.java
  69. 5 7
      ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
  70. 1 1
      ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
  71. 2 2
      ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
  72. 1 1
      ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
  73. 4 15
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm
  74. 7 7
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
  75. 19 14
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
  76. 4 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  77. 4 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  78. 4 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
  79. 4 0
      ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
  80. 1 1
      ruoyi-modules/ruoyi-system/pom.xml
  81. 0 168
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/CacheController.java
  82. 12 11
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysLogininforController.java
  83. 6 5
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysOperlogController.java
  84. 6 5
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysUserOnlineController.java
  85. 5 5
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysDeptController.java
  86. 41 0
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysMenuController.java
  87. 7 7
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
  88. 6 6
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssController.java
  89. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
  90. 10 12
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysRoleController.java
  91. 168 0
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysTenantController.java
  92. 124 0
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysTenantPackageController.java
  93. 12 6
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysUserController.java
  94. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java
  95. 7 6
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDept.java
  96. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDictData.java
  97. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDictType.java
  98. 5 24
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java
  99. 23 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysMenu.java
  100. 2 2
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java

+ 3 - 3
pom.xml

@@ -30,7 +30,7 @@
         <hutool.version>5.8.11</hutool.version>
         <okhttp.version>4.10.0</okhttp.version>
         <spring-boot-admin.version>3.0.0</spring-boot-admin.version>
-        <redisson.version>3.19.1</redisson.version>
+        <redisson.version>3.19.2</redisson.version>
         <lock4j.version>2.2.4</lock4j.version>
         <dynamic-ds.version>3.6.1</dynamic-ds.version>
         <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
@@ -42,10 +42,10 @@
         <snakeyaml.version>1.33</snakeyaml.version>
 
         <!-- OSS 配置 -->
-        <aws-java-sdk-s3.version>1.12.373</aws-java-sdk-s3.version>
+        <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
         <!-- SMS 配置 -->
         <aliyun.sms.version>2.0.23</aliyun.sms.version>
-        <tencent.sms.version>3.1.660</tencent.sms.version>
+        <tencent.sms.version>3.1.687</tencent.sms.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>

+ 5 - 7
ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java

@@ -5,8 +5,8 @@ import cn.hutool.captcha.AbstractCaptcha;
 import cn.hutool.captcha.generator.CodeGenerator;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.RandomUtil;
-import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.utils.SpringUtils;
 import com.ruoyi.common.core.utils.StringUtils;
@@ -17,8 +17,8 @@ import com.ruoyi.common.sms.core.SmsTemplate;
 import com.ruoyi.common.sms.entity.SmsResult;
 import com.ruoyi.common.web.config.properties.CaptchaProperties;
 import com.ruoyi.common.web.enums.CaptchaType;
-import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.web.domain.vo.CaptchaVo;
+import jakarta.validation.constraints.NotBlank;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.expression.Expression;
@@ -28,7 +28,6 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import jakarta.validation.constraints.NotBlank;
 import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
@@ -47,7 +46,6 @@ public class CaptchaController {
 
     private final CaptchaProperties captchaProperties;
     private final SmsProperties smsProperties;
-    private final ISysConfigService configService;
 
     /**
      * 短信验证码
@@ -60,7 +58,7 @@ public class CaptchaController {
         if (!smsProperties.getEnabled()) {
             return R.fail("当前系统没有开启短信功能!");
         }
-        String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
+        String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
         String code = RandomUtil.randomNumbers(4);
         RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
         // 验证码模板id 自行处理 (查数据库或写死均可)
@@ -82,14 +80,14 @@ public class CaptchaController {
     @GetMapping("/captchaImage")
     public R<CaptchaVo> getCode() {
         CaptchaVo captchaVo = new CaptchaVo();
-        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        boolean captchaEnabled = captchaProperties.getEnable();
         if (!captchaEnabled) {
             captchaVo.setCaptchaEnabled(false);
             return R.ok(captchaVo);
         }
         // 保存验证码信息
         String uuid = IdUtil.simpleUUID();
-        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
         // 生成验证码
         CaptchaType captchaType = captchaProperties.getType();
         boolean isMath = CaptchaType.MATH == captchaType;

+ 37 - 8
ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java

@@ -1,19 +1,27 @@
 package com.ruoyi.web.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.model.LoginBody;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.core.domain.model.SmsLoginBody;
+import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.satoken.utils.LoginHelper;
 import com.ruoyi.system.domain.SysMenu;
+import com.ruoyi.system.domain.bo.SysTenantBo;
 import com.ruoyi.system.domain.vo.RouterVo;
+import com.ruoyi.system.domain.vo.SysTenantVo;
 import com.ruoyi.system.domain.vo.SysUserVo;
 import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysTenantService;
 import com.ruoyi.system.service.ISysUserService;
-import com.ruoyi.system.service.SysLoginService;
 import com.ruoyi.web.domain.vo.LoginVo;
+import com.ruoyi.web.domain.vo.TenantListVo;
 import com.ruoyi.web.domain.vo.UserInfoVo;
+import com.ruoyi.web.service.SysLoginService;
+import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.constraints.NotBlank;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
@@ -22,6 +30,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.net.URL;
 import java.util.List;
 
 /**
@@ -37,20 +46,23 @@ public class SysLoginController {
     private final SysLoginService loginService;
     private final ISysMenuService menuService;
     private final ISysUserService userService;
+    private final ISysTenantService tenantService;
 
     /**
      * 登录方法
      *
-     * @param loginBody 登录信息
+     * @param body 登录信息
      * @return 结果
      */
     @SaIgnore
     @PostMapping("/login")
-    public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) {
+    public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
         LoginVo loginVo = new LoginVo();
         // 生成令牌
-        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
-            loginBody.getUuid());
+        String token = loginService.login(
+                body.getTenantId(),
+                body.getUsername(), body.getPassword(),
+                body.getCode(), body.getUuid());
         loginVo.setToken(token);
         return R.ok(loginVo);
     }
@@ -58,15 +70,15 @@ public class SysLoginController {
     /**
      * 短信登录(示例)
      *
-     * @param smsLoginBody 登录信息
+     * @param body 登录信息
      * @return 结果
      */
     @SaIgnore
     @PostMapping("/smsLogin")
-    public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
+    public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
         LoginVo loginVo = new LoginVo();
         // 生成令牌
-        String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
+        String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode());
         loginVo.setToken(token);
         return R.ok(loginVo);
     }
@@ -97,6 +109,23 @@ public class SysLoginController {
         return R.ok("退出成功");
     }
 
+    /**
+     * 登录页面租户下拉框
+     *
+     * @return 租户列表
+     */
+    @SaIgnore
+    @GetMapping("/tenant/list")
+    public R<List<TenantListVo>> tenantList(HttpServletRequest request) throws Exception {
+        List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
+        List<TenantListVo> voList = BeanUtil.copyToList(tenantList, TenantListVo.class);
+        // 获取域名
+        String host = new URL(request.getRequestURL().toString()).getHost();
+        // 根据域名进行筛选
+        List<TenantListVo> list = voList.stream().filter(vo -> StringUtils.equals(vo.getDomain(), host)).toList();
+        return R.ok(CollUtil.isNotEmpty(list) ? list : voList);
+    }
+
     /**
      * 获取用户信息
      *

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysRegisterController.java

@@ -5,7 +5,7 @@ import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.model.RegisterBody;
 import com.ruoyi.system.service.ISysConfigService;
-import com.ruoyi.system.service.SysRegisterService;
+import com.ruoyi.web.service.SysRegisterService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;

+ 19 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/domain/vo/TenantListVo.java

@@ -0,0 +1,19 @@
+package com.ruoyi.web.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 租户列表
+ *
+ * @author Lion Li
+ */
+@Data
+public class TenantListVo {
+
+    private String tenantId;
+
+    private String companyName;
+
+    private String domain;
+
+}

+ 78 - 37
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java → ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java

@@ -1,4 +1,4 @@
-package com.ruoyi.system.service;
+package com.ruoyi.web.service;
 
 import cn.dev33.satoken.exception.NotLoginException;
 import cn.dev33.satoken.secure.BCrypt;
@@ -6,13 +6,14 @@ import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.domain.dto.RoleDTO;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.core.domain.model.XcxLoginUser;
 import com.ruoyi.common.core.enums.DeviceType;
 import com.ruoyi.common.core.enums.LoginType;
+import com.ruoyi.common.core.enums.TenantStatus;
 import com.ruoyi.common.core.enums.UserStatus;
 import com.ruoyi.common.core.exception.user.CaptchaException;
 import com.ruoyi.common.core.exception.user.CaptchaExpireException;
@@ -21,9 +22,15 @@ import com.ruoyi.common.core.utils.*;
 import com.ruoyi.common.log.event.LogininforEvent;
 import com.ruoyi.common.redis.utils.RedisUtils;
 import com.ruoyi.common.satoken.utils.LoginHelper;
+import com.ruoyi.common.tenant.exception.TenantException;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+import com.ruoyi.common.web.config.properties.CaptchaProperties;
 import com.ruoyi.system.domain.SysUser;
+import com.ruoyi.system.domain.vo.SysTenantVo;
 import com.ruoyi.system.domain.vo.SysUserVo;
 import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.service.ISysPermissionService;
+import com.ruoyi.system.service.ISysTenantService;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -31,6 +38,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import java.time.Duration;
+import java.util.Date;
 import java.util.List;
 import java.util.function.Supplier;
 
@@ -45,8 +53,9 @@ import java.util.function.Supplier;
 public class SysLoginService {
 
     private final SysUserMapper userMapper;
-    private final ISysConfigService configService;
-    private final SysPermissionService permissionService;
+    private final CaptchaProperties captchaProperties;
+    private final ISysPermissionService permissionService;
+    private final ISysTenantService tenantService;
 
     @Value("${user.password.maxRetryCount}")
     private Integer maxRetryCount;
@@ -63,36 +72,41 @@ public class SysLoginService {
      * @param uuid     唯一标识
      * @return 结果
      */
-    public String login(String username, String password, String code, String uuid) {
+    public String login(String tenantId, String username, String password, String code, String uuid) {
         HttpServletRequest request = ServletUtils.getRequest();
-        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        boolean captchaEnabled = captchaProperties.getEnable();
         // 验证码开关
         if (captchaEnabled) {
-            validateCaptcha(username, code, uuid, request);
+            validateCaptcha(tenantId, username, code, uuid, request);
         }
-        SysUserVo user = loadUserByUsername(username);
-        checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+        // 校验租户
+        checkTenant(tenantId);
+
+        SysUserVo user = loadUserByUsername(tenantId, username);
+        checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
         // 此处可根据登录用户的数据不同 自行创建 loginUser
         LoginUser loginUser = buildLoginUser(user);
         // 生成token
         LoginHelper.loginByDevice(loginUser, DeviceType.PC);
 
-        recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
         recordLoginInfo(user.getUserId());
         return StpUtil.getTokenValue();
     }
 
-    public String smsLogin(String phonenumber, String smsCode) {
+    public String smsLogin(String tenantId, String phonenumber, String smsCode) {
+        // 校验租户
+        checkTenant(tenantId);
         // 通过手机号查找用户
-        SysUserVo user = loadUserByPhonenumber(phonenumber);
+        SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
 
-        checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
+        checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
         // 此处可根据登录用户的数据不同 自行创建 loginUser
         LoginUser loginUser = buildLoginUser(user);
         // 生成token
         LoginHelper.loginByDevice(loginUser, DeviceType.APP);
 
-        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
         recordLoginInfo(user.getUserId());
         return StpUtil.getTokenValue();
     }
@@ -104,9 +118,12 @@ public class SysLoginService {
         // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
         String openid = "";
         SysUserVo user = loadUserByOpenid(openid);
+        // 校验租户
+        checkTenant(user.getTenantId());
 
         // 此处可根据登录用户的数据不同 自行创建 loginUser
         XcxLoginUser loginUser = new XcxLoginUser();
+        loginUser.setTenantId(user.getTenantId());
         loginUser.setUserId(user.getUserId());
         loginUser.setUsername(user.getUserName());
         loginUser.setUserType(user.getUserType());
@@ -114,7 +131,7 @@ public class SysLoginService {
         // 生成token
         LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
 
-        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
         recordLoginInfo(user.getUserId());
         return StpUtil.getTokenValue();
     }
@@ -126,7 +143,7 @@ public class SysLoginService {
         try {
             LoginUser loginUser = LoginHelper.getLoginUser();
             StpUtil.logout();
-            recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
+            recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
         } catch (NotLoginException ignored) {
         }
     }
@@ -134,13 +151,15 @@ public class SysLoginService {
     /**
      * 记录登录信息
      *
+     * @param tenantId 租户ID
      * @param username 用户名
      * @param status   状态
      * @param message  消息内容
      * @return
      */
-    private void recordLogininfor(String username, String status, String message) {
+    private void recordLogininfor(String tenantId, String username, String status, String message) {
         LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
         logininforEvent.setUsername(username);
         logininforEvent.setStatus(status);
         logininforEvent.setMessage(message);
@@ -151,10 +170,10 @@ public class SysLoginService {
     /**
      * 校验短信验证码
      */
-    private boolean validateSmsCode(String phonenumber, String smsCode) {
-        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
+    private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
         if (StringUtils.isBlank(code)) {
-            recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
             throw new CaptchaExpireException();
         }
         return code.equals(smsCode);
@@ -167,24 +186,25 @@ public class SysLoginService {
      * @param code     验证码
      * @param uuid     唯一标识
      */
-    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
-        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
+    public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
         String captcha = RedisUtils.getCacheObject(verifyKey);
         RedisUtils.deleteObject(verifyKey);
         if (captcha == null) {
-            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
             throw new CaptchaExpireException();
         }
         if (!code.equalsIgnoreCase(captcha)) {
-            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
             throw new CaptchaException();
         }
     }
 
-    private SysUserVo loadUserByUsername(String username) {
+    private SysUserVo loadUserByUsername(String tenantId, String username) {
         SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
-            .select(SysUser::getUserName, SysUser::getStatus)
-            .eq(SysUser::getUserName, username));
+                .select(SysUser::getUserName, SysUser::getStatus)
+                .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+                .eq(SysUser::getUserName, username));
         if (ObjectUtil.isNull(user)) {
             log.info("登录用户:{} 不存在.", username);
             throw new UserException("user.not.exists", username);
@@ -195,10 +215,11 @@ public class SysLoginService {
         return userMapper.selectUserByUserName(username);
     }
 
-    private SysUserVo loadUserByPhonenumber(String phonenumber) {
+    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
         SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
-            .select(SysUser::getPhonenumber, SysUser::getStatus)
-            .eq(SysUser::getPhonenumber, phonenumber));
+                .select(SysUser::getPhonenumber, SysUser::getStatus)
+                .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+                .eq(SysUser::getPhonenumber, phonenumber));
         if (ObjectUtil.isNull(user)) {
             log.info("登录用户:{} 不存在.", phonenumber);
             throw new UserException("user.not.exists", phonenumber);
@@ -228,12 +249,13 @@ public class SysLoginService {
      */
     private LoginUser buildLoginUser(SysUserVo user) {
         LoginUser loginUser = new LoginUser();
+        loginUser.setTenantId(user.getTenantId());
         loginUser.setUserId(user.getUserId());
         loginUser.setDeptId(user.getDeptId());
         loginUser.setUsername(user.getUserName());
         loginUser.setUserType(user.getUserType());
-        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId(), user.isAdmin()));
-        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId(), user.isAdmin()));
+        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
+        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
         loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
         List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
         loginUser.setRoles(roles);
@@ -257,15 +279,15 @@ public class SysLoginService {
     /**
      * 登录校验
      */
-    private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
-        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
+    private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
+        String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
         String loginFail = Constants.LOGIN_FAIL;
 
         // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
         Integer errorNumber = RedisUtils.getCacheObject(errorKey);
         // 锁定时间内登录 则踢出
         if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
-            recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+            recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
             throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
         }
 
@@ -275,12 +297,12 @@ public class SysLoginService {
             // 达到规定错误次数 则锁定登录
             if (errorNumber.equals(maxRetryCount)) {
                 RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
-                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
                 throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
             } else {
                 // 未达到规定错误次数 则递增
                 RedisUtils.setCacheObject(errorKey, errorNumber);
-                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
+                recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
                 throw new UserException(loginType.getRetryLimitCount(), errorNumber);
             }
         }
@@ -288,4 +310,23 @@ public class SysLoginService {
         // 登录成功 清空错误次数
         RedisUtils.deleteObject(errorKey);
     }
+
+    private void checkTenant(String tenantId) {
+        if (!TenantHelper.isEnable()) {
+            return;
+        }
+        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
+        if (ObjectUtil.isNull(tenant)) {
+            log.info("登录租户:{} 不存在.", tenantId);
+            throw new TenantException("tenant.not.exists");
+        } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
+            log.info("登录租户:{} 已被停用.", tenantId);
+            throw new TenantException("tenant.blocked");
+        } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
+                && new Date().after(tenant.getExpireTime())) {
+            log.info("登录租户:{} 已超过有效期.", tenantId);
+            throw new TenantException("tenant.expired");
+        }
+    }
+
 }

+ 17 - 12
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/SysRegisterService.java → ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java

@@ -1,8 +1,8 @@
-package com.ruoyi.system.service;
+package com.ruoyi.web.service;
 
 import cn.dev33.satoken.secure.BCrypt;
-import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.model.RegisterBody;
 import com.ruoyi.common.core.enums.UserType;
@@ -15,7 +15,9 @@ import com.ruoyi.common.core.utils.SpringUtils;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.log.event.LogininforEvent;
 import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.common.web.config.properties.CaptchaProperties;
 import com.ruoyi.system.domain.bo.SysUserBo;
+import com.ruoyi.system.service.ISysUserService;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
@@ -30,22 +32,23 @@ import org.springframework.stereotype.Service;
 public class SysRegisterService {
 
     private final ISysUserService userService;
-    private final ISysConfigService configService;
+    private final CaptchaProperties captchaProperties;
 
     /**
      * 注册
      */
     public void register(RegisterBody registerBody) {
         HttpServletRequest request = ServletUtils.getRequest();
+        String tenantId = registerBody.getTenantId();
         String username = registerBody.getUsername();
         String password = registerBody.getPassword();
         // 校验用户类型是否存在
         String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
 
-        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        boolean captchaEnabled = captchaProperties.getEnable();
         // 验证码开关
         if (captchaEnabled) {
-            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request);
+            validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid(), request);
         }
         SysUserBo sysUser = new SysUserBo();
         sysUser.setUserName(username);
@@ -56,11 +59,11 @@ public class SysRegisterService {
         if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) {
             throw new UserException("user.register.save.error", username);
         }
-        boolean regFlag = userService.registerUser(sysUser);
+        boolean regFlag = userService.registerUser(sysUser, tenantId);
         if (!regFlag) {
             throw new UserException("user.register.error");
         }
-        recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"));
+        recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
     }
 
     /**
@@ -71,16 +74,16 @@ public class SysRegisterService {
      * @param uuid     唯一标识
      * @return 结果
      */
-    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
-        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
+    public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
         String captcha = RedisUtils.getCacheObject(verifyKey);
         RedisUtils.deleteObject(verifyKey);
         if (captcha == null) {
-            recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
+            recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
             throw new CaptchaExpireException();
         }
         if (!code.equalsIgnoreCase(captcha)) {
-            recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
+            recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
             throw new CaptchaException();
         }
     }
@@ -88,13 +91,15 @@ public class SysRegisterService {
     /**
      * 记录登录信息
      *
+     * @param tenantId 租户ID
      * @param username 用户名
      * @param status   状态
      * @param message  消息内容
      * @return
      */
-    private void recordLogininfor(String username, String status, String message) {
+    private void recordLogininfor(String tenantId, String username, String status, String message) {
         LogininforEvent logininforEvent = new LogininforEvent();
+        logininforEvent.setTenantId(tenantId);
         logininforEvent.setUsername(username);
         logininforEvent.setStatus(status);
         logininforEvent.setMessage(message);

+ 3 - 1
ruoyi-admin/src/main/resources/application-dev.yml

@@ -92,9 +92,11 @@ spring:
         # 空闲连接存活最大时间,默认10分钟
         idleTimeout: 600000
         # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
-        maxLifetime: 900000
+        maxLifetime: 1800000
         # 连接测试query(配置检测连接是否有效)
         connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring.data:

+ 3 - 1
ruoyi-admin/src/main/resources/application-prod.yml

@@ -95,9 +95,11 @@ spring:
         # 空闲连接存活最大时间,默认10分钟
         idleTimeout: 600000
         # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
-        maxLifetime: 900000
+        maxLifetime: 1800000
         # 连接测试query(配置检测连接是否有效)
         connectionTestQuery: SELECT 1
+        # 多久检查一次连接的活性
+        keepaliveTime: 30000
 
 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
 spring.data:

+ 15 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -10,10 +10,9 @@ ruoyi:
   demoEnabled: true
   # 获取ip地址开关
   addressEnabled: true
-  # 缓存懒加载
-  cacheLazy: false
 
 captcha:
+  enable: true
   # 页面 <参数设置> 可开启关闭 验证码校验
   # 验证码类型 math 数组计算 char 字符验证
   type: MATH
@@ -136,6 +135,20 @@ security:
     - /actuator
     - /actuator/**
 
+# 多租户配置
+tenant:
+  # 是否开启
+  enable: true
+  # 排除表
+  excludes:
+    - sys_menu
+    - sys_tenant
+    - sys_tenant_package
+    - sys_role_dept
+    - sys_role_menu
+    - sys_user_post
+    - sys_user_role
+
 # MyBatisPlus配置
 # https://baomidou.com/config/
 mybatis-plus:

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空
 sms.code.retry.limit.count=短信验证码输入错误{0}次
 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
 xcx.code.not.blank=小程序code不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -43,3 +43,8 @@ sms.code.not.blank=Sms code cannot be blank
 sms.code.retry.limit.count=Sms code input error {0} times
 sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
 xcx.code.not.blank=Mini program code cannot be blank
+##租户
+tenant.number.not.blank=Tenant number cannot be blank
+tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
+tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
+tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空
 sms.code.retry.limit.count=短信验证码输入错误{0}次
 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
 xcx.code.not.blank=小程序code不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员

+ 1 - 0
ruoyi-common/pom.xml

@@ -31,6 +31,7 @@
         <module>ruoyi-common-sensitive</module>
         <module>ruoyi-common-json</module>
         <module>ruoyi-common-encrypt</module>
+        <module>ruoyi-common-tenant</module>
     </modules>
 
     <artifactId>ruoyi-common</artifactId>

+ 7 - 0
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -152,6 +152,13 @@
                 <version>${revision}</version>
             </dependency>
 
+            <!-- 租户模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common-tenant</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 

+ 0 - 5
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/config/RuoYiConfig.java

@@ -36,11 +36,6 @@ public class RuoYiConfig {
      */
     private boolean demoEnabled;
 
-    /**
-     * 缓存懒加载
-     */
-    private boolean cacheLazy;
-
     /**
      * 获取地址开关
      */

+ 1 - 25
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java

@@ -3,25 +3,15 @@ package com.ruoyi.common.core.constant;
 /**
  * 缓存的key 常量
  *
- * @author ruoyi
+ * @author Lion Li
  */
 public interface CacheConstants {
 
-    /**
-     * 登录用户 redis key
-     */
-    String LOGIN_TOKEN_KEY = "Authorization:login:token:";
-
     /**
      * 在线用户 redis key
      */
     String ONLINE_TOKEN_KEY = "online_tokens:";
 
-    /**
-     * 验证码 redis key
-     */
-    String CAPTCHA_CODE_KEY = "captcha_codes:";
-
     /**
      * 参数管理 cache key
      */
@@ -32,18 +22,4 @@ public interface CacheConstants {
      */
     String SYS_DICT_KEY = "sys_dict:";
 
-    /**
-     * 防重提交 redis key
-     */
-    String REPEAT_SUBMIT_KEY = "repeat_submit:";
-
-    /**
-     * 限流 redis key
-     */
-    String RATE_LIMIT_KEY = "rate_limit:";
-
-    /**
-     * 登录账户密码错误次数 redis key
-     */
-    String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
 }

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheNames.java

@@ -30,6 +30,11 @@ public interface CacheNames {
      */
     String SYS_DICT = "sys_dict";
 
+    /**
+     * 租户
+     */
+    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
     /**
      * 用户账户
      */

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java

@@ -72,5 +72,10 @@ public interface Constants {
      */
     String TOKEN = "token";
 
+    /**
+     * 顶级部门id
+     */
+    Long TOP_PARENT_ID = 0L;
+
 }
 

+ 39 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/GlobalConstants.java

@@ -0,0 +1,39 @@
+package com.ruoyi.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+    /**
+     * 全局 redis key (业务无关的key)
+     */
+    String GLOBAL_REDIS_KEY = "global:";
+
+    /**
+     * 登录用户 redis key
+     */
+    String LOGIN_TOKEN_KEY = GLOBAL_REDIS_KEY + "Authorization:login:token:";
+
+    /**
+     * 验证码 redis key
+     */
+    String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+    /**
+     * 防重提交 redis key
+     */
+    String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+}

+ 51 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/TenantConstants.java

@@ -0,0 +1,51 @@
+package com.ruoyi.common.core.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+    /**
+     * 租户正常状态
+     */
+    String NORMAL = "0";
+
+    /**
+     * 租户封禁状态
+     */
+    String DISABLE = "1";
+
+    /**
+     * 校验返回结果码
+     */
+    String PASS = "0";
+    String NOT_PASS = "1";
+
+    /**
+     * 超级管理员ID
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+    /**
+     * 超级管理员角色 roleKey
+     */
+    String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+    /**
+     * 租户管理员角色 roleKey
+     */
+    String TENANT_ADMIN_ROLE_KEY = "admin";
+
+    /**
+     * 租户管理员角色名称
+     */
+    String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+    /**
+     * 默认租户ID
+     */
+    String DEFAULT_TENANT_ID = "000000";
+
+}

+ 6 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java

@@ -15,6 +15,12 @@ import jakarta.validation.constraints.NotBlank;
 @Data
 public class LoginBody {
 
+    /**
+     * 租户ID
+     */
+    @NotBlank(message = "{tenant.number.not.blank}")
+    private String tenantId;
+
     /**
      * 用户名
      */

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java

@@ -22,6 +22,11 @@ public class LoginUser implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
     /**
      * 用户ID
      */

+ 6 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java

@@ -13,6 +13,12 @@ import jakarta.validation.constraints.NotBlank;
 @Data
 public class SmsLoginBody {
 
+    /**
+     * 租户ID
+     */
+    @NotBlank(message = "{tenant.number.not.blank}")
+    private String tenantId;
+
     /**
      * 用户名
      */

+ 30 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author LionLi
+ */
+@Getter
+@AllArgsConstructor
+public enum TenantStatus {
+    /**
+     * 正常
+     */
+    OK("0", "正常"),
+    /**
+     * 停用
+     */
+    DISABLE("1", "停用"),
+    /**
+     * 删除
+     */
+    DELETED("2", "删除");
+
+    private final String code;
+    private final String info;
+
+}

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java

@@ -92,7 +92,7 @@ public class ServletUtils extends JakartaServletUtil {
     public static Map<String, String> getParamMap(ServletRequest request) {
         Map<String, String> params = new HashMap<>();
         for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
-            params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
+            params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
         }
         return params;
     }

+ 1 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StreamUtils.java

@@ -42,7 +42,7 @@ public class StreamUtils {
      * @return 拼接后的list
      */
     public static <E> String join(Collection<E> collection, Function<E, String> function) {
-        return join(collection, function, ",");
+        return join(collection, function, StringUtils.SEPARATOR);
     }
 
     /**

+ 60 - 7
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java

@@ -1,16 +1,16 @@
 package com.ruoyi.common.core.utils;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.StrUtil;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.springframework.util.AntPathMatcher;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 字符串工具类
@@ -20,6 +20,8 @@ import java.util.Set;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class StringUtils extends org.apache.commons.lang3.StringUtils {
 
+    public static final String SEPARATOR = ",";
+
     /**
      * 获取参数不为空值
      *
@@ -233,7 +235,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
     /**
      * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
      *
-     * @param num 数字对象
+     * @param num  数字对象
      * @param size 字符串指定长度
      * @return 返回数字的字符串格式,该字符串为指定长度。
      */
@@ -244,9 +246,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
     /**
      * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
      *
-     * @param s 原始字符串
+     * @param s    原始字符串
      * @param size 字符串指定长度
-     * @param c 用于补齐的字符
+     * @param c    用于补齐的字符
      * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
      */
     public static String padl(final String s, final int size, final char c) {
@@ -265,4 +267,55 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
         return sb.toString();
     }
 
+    /**
+     * 切分字符串(分隔符默认逗号)
+     *
+     * @param str 被切分的字符串
+     * @return 分割后的数据列表
+     */
+    public static List<String> splitList(String str) {
+        return splitTo(str, Convert::toStr);
+    }
+
+    /**
+     * 切分字符串
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @return 分割后的数据列表
+     */
+    public static List<String> splitList(String str, String separator) {
+        return splitTo(str, separator, Convert::toStr);
+    }
+
+    /**
+     * 切分字符串自定义转换(分隔符默认逗号)
+     *
+     * @param str    被切分的字符串
+     * @param mapper 自定义转换
+     * @return 分割后的数据列表
+     */
+    public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
+        return splitTo(str, SEPARATOR, mapper);
+    }
+
+    /**
+     * 切分字符串自定义转换
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @param mapper    自定义转换
+     * @return 分割后的数据列表
+     */
+    public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
+        if (isBlank(str)) {
+            return new ArrayList<>(0);
+        }
+        return StrUtil.split(str, separator)
+            .stream()
+            .filter(Objects::nonNull)
+            .map(mapper)
+            .collect(Collectors.toList());
+    }
+
 }

+ 3 - 1
ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/annotation/ExcelDictFormat.java

@@ -1,5 +1,7 @@
 package com.ruoyi.common.excel.annotation;
 
+import com.ruoyi.common.core.utils.StringUtils;
+
 import java.lang.annotation.*;
 
 /**
@@ -25,6 +27,6 @@ public @interface ExcelDictFormat {
     /**
      * 分隔符,读取字符串组内容
      */
-    String separator() default ",";
+    String separator() default StringUtils.SEPARATOR;
 
 }

+ 2 - 2
ruoyi-common/ruoyi-common-excel/src/main/java/com/ruoyi/common/excel/utils/ExcelUtil.java

@@ -269,7 +269,7 @@ public class ExcelUtil {
      */
     public static String convertByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
         for (String item : convertSource) {
             String[] itemArray = item.split("=");
             if (StringUtils.containsAny(propertyValue, separator)) {
@@ -298,7 +298,7 @@ public class ExcelUtil {
      */
     public static String reverseByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
         for (String item : convertSource) {
             String[] itemArray = item.split("=");
             if (StringUtils.containsAny(propertyValue, separator)) {

+ 2 - 2
ruoyi-common/ruoyi-common-idempotent/src/main/java/com/ruoyi/common/idempotent/aspectj/RepeatSubmitAspect.java

@@ -3,7 +3,7 @@ package com.ruoyi.common.idempotent.aspectj;
 import cn.dev33.satoken.SaManager;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.crypto.SecureUtil;
-import com.ruoyi.common.core.constant.CacheConstants;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.MessageUtils;
@@ -57,7 +57,7 @@ public class RepeatSubmitAspect {
 
         submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
         // 唯一标识(指定key + url + 消息头)
-        String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
+        String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
         String key = RedisUtils.getCacheObject(cacheRepeatKey);
         if (key == null) {
             RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval));

+ 1 - 0
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java

@@ -67,6 +67,7 @@ public class LogAspect {
 
             // *========数据库日志=========*//
             OperLogEvent operLog = new OperLogEvent();
+            operLog.setTenantId(LoginHelper.getTenantId());
             operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
             // 请求的地址
             String ip = ServletUtils.getClientIP();

+ 5 - 0
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java

@@ -19,6 +19,11 @@ public class LogininforEvent implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
     /**
      * 用户账号
      */

+ 5 - 0
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java

@@ -23,6 +23,11 @@ public class OperLogEvent implements Serializable {
      */
     private Long operId;
 
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
     /**
      * 操作模块
      */

+ 0 - 41
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TreeEntity.java

@@ -1,41 +0,0 @@
-package com.ruoyi.common.mybatis.core.domain;
-
-import com.baomidou.mybatisplus.annotation.TableField;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tree基类
- *
- * @author Lion Li
- */
-
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class TreeEntity<T> extends BaseEntity {
-
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 父菜单名称
-     */
-    @TableField(exist = false)
-    private String parentName;
-
-    /**
-     * 父菜单ID
-     */
-    private Long parentId;
-
-    /**
-     * 子部门
-     */
-    @TableField(exist = false)
-    private List<T> children = new ArrayList<>();
-
-}

+ 8 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java

@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.core.toolkit.*;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.toolkit.Db;
 import com.ruoyi.common.core.utils.BeanCopyUtils;
@@ -17,6 +17,9 @@ import java.io.Serializable;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 自定义 Mapper 接口, 实现 自定义扩展
@@ -193,4 +196,8 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
         return (P) voPage;
     }
 
+    default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
+        return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
+    }
+
 }

+ 2 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/page/PageQuery.java

@@ -89,8 +89,8 @@ public class PageQuery implements Serializable {
         // 兼容前端排序类型
         isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
 
-        String[] orderByArr = orderBy.split(",");
-        String[] isAscArr = isAsc.split(",");
+        String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+        String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
         if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
             throw new ServiceException("排序参数有误");
         }

+ 1 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/helper/MybatisExceptionHandler.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java

@@ -1,4 +1,4 @@
-package com.ruoyi.common.mybatis.helper;
+package com.ruoyi.common.mybatis.handler;
 
 import com.ruoyi.common.core.domain.R;
 import lombok.extern.slf4j.Slf4j;

+ 1 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java

@@ -35,7 +35,6 @@ import java.lang.reflect.Method;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 /**
  * 数据权限过滤
@@ -79,7 +78,7 @@ public class PlusDataPermissionHandler {
             DataPermissionHelper.setVariable("user", currentUser);
         }
         // 如果是超级管理员,则不过滤数据
-        if (LoginHelper.isAdmin()) {
+        if (LoginHelper.isSuperAdmin()) {
             return where;
         }
         String dataFilterSql = buildDataFilter(dataColumns, isSelect);

+ 17 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/helper/DataPermissionHelper.java

@@ -3,6 +3,8 @@ package com.ruoyi.common.mybatis.helper;
 import cn.dev33.satoken.context.SaHolder;
 import cn.dev33.satoken.context.model.SaStorage;
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
@@ -44,4 +46,19 @@ public class DataPermissionHelper {
         }
         throw new NullPointerException("data permission context type exception");
     }
+
+    /**
+     * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
+    }
+
+    /**
+     * 关闭忽略数据权限
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
 }

+ 13 - 0
ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/core/OssClient.java

@@ -183,6 +183,12 @@ public class OssClient {
         return configKey;
     }
 
+    /**
+     * 获取私有URL链接
+     *
+     * @param objectKey 对象KEY
+     * @param second    授权时间
+     */
     public String getPrivateUrl(String objectKey, Integer second) {
         GeneratePresignedUrlRequest generatePresignedUrlRequest =
             new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
@@ -192,6 +198,13 @@ public class OssClient {
         return url.toString();
     }
 
+    /**
+     * 检查配置是否相同
+     */
+    public boolean checkPropertiesSame(OssProperties properties) {
+        return this.properties.equals(properties);
+    }
+
     /**
      * 获取当前桶权限类型
      *

+ 13 - 29
ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java

@@ -24,21 +24,6 @@ public class OssFactory {
 
     private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
 
-    /**
-     * 初始化工厂
-     */
-    public static void init() {
-        log.info("初始化OSS工厂");
-        RedisUtils.subscribe(OssConstant.DEFAULT_CONFIG_KEY, String.class, configKey -> {
-            OssClient client = getClient(configKey);
-            // 未初始化不处理
-            if (client != null) {
-                refresh(configKey);
-                log.info("订阅刷新OSS配置 => " + configKey);
-            }
-        });
-    }
-
     /**
      * 获取默认实例
      */
@@ -55,25 +40,24 @@ public class OssFactory {
      * 根据类型获取实例
      */
     public static OssClient instance(String configKey) {
-        OssClient client = getClient(configKey);
-        if (client == null) {
-            refresh(configKey);
-            return getClient(configKey);
-        }
-        return client;
-    }
-
-    private static void refresh(String configKey) {
         String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
         if (json == null) {
             throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
         }
         OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
-        CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
-    }
-
-    private static OssClient getClient(String configKey) {
-        return CLIENT_CACHE.get(configKey);
+        OssClient client = CLIENT_CACHE.get(configKey);
+        if (client == null) {
+            CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
+            log.info("创建OSS实例 key => {}", configKey);
+            return CLIENT_CACHE.get(configKey);
+        }
+        // 配置不相同则重新构建
+        if (!client.checkPropertiesSame(properties)) {
+            CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
+            log.info("重载OSS实例 key => {}", configKey);
+            return CLIENT_CACHE.get(configKey);
+        }
+        return client;
     }
 
 }

+ 2 - 2
ruoyi-common/ruoyi-common-ratelimiter/src/main/java/com/ruoyi/common/ratelimiter/annotation/RateLimiter.java

@@ -1,6 +1,6 @@
 package com.ruoyi.common.ratelimiter.annotation;
 
-import com.ruoyi.common.core.constant.CacheConstants;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.ratelimiter.enums.LimitType;
 
 import java.lang.annotation.*;
@@ -17,7 +17,7 @@ public @interface RateLimiter {
     /**
      * 限流key
      */
-    String key() default CacheConstants.RATE_LIMIT_KEY;
+    String key() default GlobalConstants.RATE_LIMIT_KEY;
 
     /**
      * 限流时间,单位秒

+ 10 - 0
ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/config/SaTokenConfig.java

@@ -1,8 +1,10 @@
 package com.ruoyi.common.satoken.config;
 
+import cn.dev33.satoken.dao.SaTokenDao;
 import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
 import cn.dev33.satoken.stp.StpInterface;
 import cn.dev33.satoken.stp.StpLogic;
+import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
 import com.ruoyi.common.satoken.core.service.SaPermissionImpl;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
@@ -30,4 +32,12 @@ public class SaTokenConfig implements WebMvcConfigurer {
         return new SaPermissionImpl();
     }
 
+    /**
+     * 自定义dao层存储
+     */
+    @Bean
+    public SaTokenDao saTokenDao() {
+        return new PlusSaTokenDao();
+    }
+
 }

+ 0 - 2
ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/core/dao/PlusSaTokenDao.java

@@ -3,7 +3,6 @@ package com.ruoyi.common.satoken.core.dao;
 import cn.dev33.satoken.dao.SaTokenDao;
 import cn.dev33.satoken.util.SaFoxUtil;
 import com.ruoyi.common.redis.utils.RedisUtils;
-import org.springframework.stereotype.Component;
 
 import java.time.Duration;
 import java.util.ArrayList;
@@ -15,7 +14,6 @@ import java.util.List;
  *
  * @author Lion Li
  */
-@Component
 public class PlusSaTokenDao implements SaTokenDao {
 
     /**

+ 41 - 16
ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java

@@ -1,8 +1,11 @@
 package com.ruoyi.common.satoken.utils;
 
 import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONObject;
+import com.ruoyi.common.core.constant.TenantConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.core.enums.DeviceType;
@@ -12,6 +15,8 @@ import com.ruoyi.common.core.utils.StringUtils;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
+import java.util.Set;
+
 /**
  * 登录鉴权助手
  * <p>
@@ -37,8 +42,7 @@ public class LoginHelper {
      */
     public static void login(LoginUser loginUser) {
         SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
-        StpUtil.login(loginUser.getLoginId());
-        setLoginUser(loginUser);
+        StpUtil.login(loginUser.getLoginId(), new SaLoginModel().setExtra(LOGIN_USER_KEY, loginUser));
     }
 
     /**
@@ -49,15 +53,9 @@ public class LoginHelper {
      */
     public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
         SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
-        StpUtil.login(loginUser.getLoginId(), deviceType.getDevice());
-        setLoginUser(loginUser);
-    }
-
-    /**
-     * 设置用户数据(多级缓存)
-     */
-    public static void setLoginUser(LoginUser loginUser) {
-        StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
+        StpUtil.login(loginUser.getLoginId(), new SaLoginModel()
+            .setDevice(deviceType.getDevice())
+            .setExtra(LOGIN_USER_KEY, loginUser));
     }
 
     /**
@@ -68,7 +66,7 @@ public class LoginHelper {
         if (loginUser != null) {
             return loginUser;
         }
-        loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
+        loginUser = ((JSONObject) StpUtil.getExtra(LOGIN_USER_KEY)).toBean(LoginUser.class);
         SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
         return loginUser;
     }
@@ -96,6 +94,19 @@ public class LoginHelper {
         return loginUser.getUserId();
     }
 
+    /**
+     * 获取租户ID
+     */
+    public static String getTenantId() {
+        LoginUser loginUser;
+        try {
+            loginUser = getLoginUser();
+        } catch (Exception e) {
+            return null;
+        }
+        return loginUser.getTenantId();
+    }
+
     /**
      * 获取部门ID
      */
@@ -119,17 +130,31 @@ public class LoginHelper {
     }
 
     /**
-     * 是否为管理员
+     * 是否为超级管理员
      *
      * @param userId 用户ID
      * @return 结果
      */
-    public static boolean isAdmin(Long userId) {
+    public static boolean isSuperAdmin(Long userId) {
         return UserConstants.SUPER_ADMIN_ID.equals(userId);
     }
 
-    public static boolean isAdmin() {
-        return isAdmin(getUserId());
+    public static boolean isSuperAdmin() {
+        return isSuperAdmin(getUserId());
+    }
+
+    /**
+     * 是否为超级管理员
+     *
+     * @param rolePermission 角色权限标识组
+     * @return 结果
+     */
+    public static boolean isTenantAdmin(Set<String> rolePermission) {
+        return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
+    }
+
+    public static boolean isTenantAdmin() {
+        return isTenantAdmin(getLoginUser().getRolePermission());
     }
 
 }

+ 0 - 1
ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1,2 +1 @@
-com.ruoyi.common.satoken.core.dao.PlusSaTokenDao
 com.ruoyi.common.satoken.config.SaTokenConfig

+ 0 - 6
ruoyi-common/ruoyi-common-security/pom.xml

@@ -22,12 +22,6 @@
             <artifactId>ruoyi-common-satoken</artifactId>
         </dependency>
 
-        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
-        <dependency>
-            <groupId>cn.dev33</groupId>
-            <artifactId>sa-token-spring-boot3-starter</artifactId>
-            <version>${satoken.version}</version>
-        </dependency>
     </dependencies>
 
 </project>

+ 1 - 1
ruoyi-common/ruoyi-common-sms/src/main/java/com/ruoyi/common/sms/core/TencentSmsTemplate.java

@@ -53,7 +53,7 @@ public class TencentSmsTemplate implements SmsTemplate {
             throw new SmsException("模板ID不能为空");
         }
         SendSmsRequest req = new SendSmsRequest();
-        Set<String> set = Arrays.stream(phones.split(",")).map(p -> "+86" + p).collect(Collectors.toSet());
+        Set<String> set = Arrays.stream(phones.split(StringUtils.SEPARATOR)).map(p -> "+86" + p).collect(Collectors.toSet());
         req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class));
         if (CollUtil.isNotEmpty(param)) {
             req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class));

+ 37 - 0
ruoyi-common/ruoyi-common-tenant/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.ruoyi</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-tenant</artifactId>
+
+    <description>
+        ruoyi-common-tenant 租户模块
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 100 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/config/TenantConfig.java

@@ -0,0 +1,100 @@
+package com.ruoyi.common.tenant.config;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import com.ruoyi.common.core.utils.reflect.ReflectUtils;
+import com.ruoyi.common.mybatis.config.MybatisPlusConfig;
+import com.ruoyi.common.redis.config.RedisConfig;
+import com.ruoyi.common.redis.config.properties.RedissonProperties;
+import com.ruoyi.common.tenant.core.TenantSaTokenDao;
+import com.ruoyi.common.tenant.handle.PlusTenantLineHandler;
+import com.ruoyi.common.tenant.handle.TenantKeyPrefixHandler;
+import com.ruoyi.common.tenant.manager.TenantSpringCacheManager;
+import com.ruoyi.common.tenant.properties.TenantProperties;
+import org.redisson.config.ClusterServersConfig;
+import org.redisson.config.SingleServerConfig;
+import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 租户配置类
+ *
+ * @author Lion Li
+ */
+@EnableConfigurationProperties(TenantProperties.class)
+@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class})
+@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
+public class TenantConfig {
+
+    /**
+     * 初始化租户配置
+     */
+    @Bean
+    public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor,
+                              TenantProperties tenantProperties) {
+        List<InnerInterceptor> interceptors = new ArrayList<>();
+        // 多租户插件 必须放到第一位
+        interceptors.add(tenantLineInnerInterceptor(tenantProperties));
+        interceptors.addAll(mybatisPlusInterceptor.getInterceptors());
+        mybatisPlusInterceptor.setInterceptors(interceptors);
+        return true;
+    }
+
+    /**
+     * 多租户插件
+     */
+    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
+        return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
+    }
+
+    @Bean
+    public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
+        return config -> {
+            TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
+            SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
+            if (ObjectUtil.isNotNull(singleServerConfig)) {
+                // 使用单机模式
+                // 设置多租户 redis key前缀
+                singleServerConfig.setNameMapper(nameMapper);
+                ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
+            }
+            ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
+            // 集群配置方式 参考下方注释
+            if (ObjectUtil.isNotNull(clusterServersConfig)) {
+                // 设置多租户 redis key前缀
+                clusterServersConfig.setNameMapper(nameMapper);
+                ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
+            }
+        };
+    }
+
+    /**
+     * 多租户缓存管理器
+     */
+    @Primary
+    @Bean
+    public CacheManager tenantCacheManager() {
+        return new TenantSpringCacheManager();
+    }
+
+    /**
+     * 多租户鉴权dao实现
+     */
+    @Primary
+    @Bean
+    public SaTokenDao tenantSaTokenDao() {
+        return new TenantSaTokenDao();
+    }
+
+}

+ 21 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantEntity.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.tenant.core;
+
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 租户基类
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantEntity extends BaseEntity {
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+}

+ 114 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/core/TenantSaTokenDao.java

@@ -0,0 +1,114 @@
+package com.ruoyi.common.tenant.core;
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
+
+import java.util.List;
+
+/**
+ * SaToken 认证数据持久层 适配多租户
+ *
+ * @author Lion Li
+ */
+public class TenantSaTokenDao extends PlusSaTokenDao {
+
+    @Override
+    public String get(String key) {
+        return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    @Override
+    public void set(String key, String value, long timeout) {
+        super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
+    }
+
+    /**
+     * 修修改指定key-value键值对 (过期时间不变)
+     */
+    @Override
+    public void update(String key, String value) {
+        super.update(GlobalConstants.GLOBAL_REDIS_KEY + key, value);
+    }
+
+    /**
+     * 删除Value
+     */
+    @Override
+    public void delete(String key) {
+        super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 获取Value的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public long getTimeout(String key) {
+        return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 修改Value的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public void updateTimeout(String key, long timeout) {
+        super.updateTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key, timeout);
+    }
+
+
+    /**
+     * 获取Object,如无返空
+     */
+    @Override
+    public Object getObject(String key) {
+        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 写入Object,并设定存活时间 (单位: 秒)
+     */
+    @Override
+    public void setObject(String key, Object object, long timeout) {
+        super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
+    }
+
+    /**
+     * 更新Object (过期时间不变)
+     */
+    @Override
+    public void updateObject(String key, Object object) {
+        super.updateObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object);
+    }
+
+    /**
+     * 删除Object
+     */
+    @Override
+    public void deleteObject(String key) {
+        super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 获取Object的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public long getObjectTimeout(String key) {
+        return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 修改Object的剩余存活时间 (单位: 秒)
+     */
+    @Override
+    public void updateObjectTimeout(String key, long timeout) {
+        super.updateObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key, timeout);
+    }
+
+
+    /**
+     * 搜索数据
+     */
+    @Override
+    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+        return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
+    }
+}

+ 20 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/exception/TenantException.java

@@ -0,0 +1,20 @@
+package com.ruoyi.common.tenant.exception;
+
+import com.ruoyi.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 租户异常类
+ *
+ * @author Lion Li
+ */
+public class TenantException extends BaseException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public TenantException(String code, Object... args) {
+        super("tenant", code, args, null);
+    }
+}

+ 57 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/PlusTenantLineHandler.java

@@ -0,0 +1,57 @@
+package com.ruoyi.common.tenant.handle;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.satoken.utils.LoginHelper;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+import com.ruoyi.common.tenant.properties.TenantProperties;
+import lombok.AllArgsConstructor;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.NullValue;
+
+import java.util.List;
+
+/**
+ * 自定义租户处理器
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+public class PlusTenantLineHandler implements TenantLineHandler {
+
+    private final TenantProperties tenantProperties;
+
+    @Override
+    public Expression getTenantId() {
+        String tenantId = LoginHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            return new NullValue();
+        }
+        String dynamicTenantId = TenantHelper.getDynamic();
+        if (StringUtils.isNotBlank(dynamicTenantId)) {
+            // 返回动态租户
+            return new LongValue(dynamicTenantId);
+        }
+        // 返回固定租户
+        return new LongValue(tenantId);
+    }
+
+    @Override
+    public boolean ignoreTable(String tableName) {
+        String tenantId = LoginHelper.getTenantId();
+        // 判断是否有租户
+        if (StringUtils.isNotBlank(tenantId)) {
+            // 不需要过滤租户的表
+            List<String> excludes = tenantProperties.getExcludes();
+            // 非业务表
+            excludes.addAll(List.of(
+                    "gen_table",
+                    "gen_table_column"
+            ));
+            return excludes.contains(tableName);
+        }
+        return true;
+    }
+
+}

+ 58 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java

@@ -0,0 +1,58 @@
+package com.ruoyi.common.tenant.handle;
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.handler.KeyPrefixHandler;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+
+/**
+ * 多租户redis缓存key前缀处理
+ *
+ * @author Lion Li
+ */
+public class TenantKeyPrefixHandler extends KeyPrefixHandler {
+
+    public TenantKeyPrefixHandler(String keyPrefix) {
+        super(keyPrefix);
+    }
+
+    /**
+     * 增加前缀
+     */
+    @Override
+    public String map(String name) {
+        if (StringUtils.isBlank(name)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.map(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(name, tenantId)) {
+            // 如果存在则直接返回
+            return super.map(name);
+        }
+        return super.map(tenantId + ":" + name);
+    }
+
+    /**
+     * 去除前缀
+     */
+    @Override
+    public String unmap(String name) {
+        String unmap = super.unmap(name);
+        if (StringUtils.isBlank(unmap)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.unmap(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(unmap, tenantId)) {
+            // 如果存在则删除
+            return unmap.substring((tenantId + ":").length());
+        }
+        return unmap;
+    }
+
+}

+ 112 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/helper/TenantHelper.java

@@ -0,0 +1,112 @@
+package com.ruoyi.common.tenant.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.spring.SpringMVCUtil;
+import com.alibaba.ttl.TransmittableThreadLocal;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.SpringUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.common.satoken.utils.LoginHelper;
+import com.ruoyi.common.tenant.properties.TenantProperties;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 租户助手
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TenantHelper {
+
+    private static final TenantProperties PROPERTIES = SpringUtils.getBean(TenantProperties.class);
+
+    private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
+
+    private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
+
+    /**
+     * 租户功能是否启用
+     */
+    public static boolean isEnable() {
+        return PROPERTIES.getEnable();
+    }
+
+    /**
+     * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+    }
+
+    /**
+     * 关闭忽略租户
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
+    /**
+     * 设置动态租户(一直有效 需要手动清理)
+     * <p>
+     * 如果为非web环境 那么只在当前线程内生效
+     */
+    public static void setDynamic(String tenantId) {
+        if (!SpringMVCUtil.isWeb()) {
+            TEMP_DYNAMIC_TENANT.set(tenantId);
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
+        RedisUtils.setCacheObject(cacheKey, tenantId);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+    }
+
+    /**
+     * 获取动态租户(一直有效 需要手动清理)
+     * <p>
+     * 如果为非web环境 那么只在当前线程内生效
+     */
+    public static String getDynamic() {
+        if (!SpringMVCUtil.isWeb()) {
+            return TEMP_DYNAMIC_TENANT.get();
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
+        String tenantId = (String) SaHolder.getStorage().get(cacheKey);
+        if (StringUtils.isNotBlank(tenantId)) {
+            return tenantId;
+        }
+        tenantId = RedisUtils.getCacheObject(cacheKey);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+        return tenantId;
+    }
+
+    /**
+     * 清除动态租户
+     */
+    public static void clearDynamic() {
+        if (!SpringMVCUtil.isWeb()) {
+            TEMP_DYNAMIC_TENANT.remove();
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
+        RedisUtils.deleteObject(cacheKey);
+        SaHolder.getStorage().delete(cacheKey);
+    }
+
+    /**
+     * 获取当前租户id(动态租户优先)
+     */
+    public static String getTenantId() {
+        String tenantId = TenantHelper.getDynamic();
+        if (StringUtils.isBlank(tenantId)) {
+            tenantId = LoginHelper.getTenantId();
+        }
+        return tenantId;
+    }
+
+}

+ 32 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/manager/TenantSpringCacheManager.java

@@ -0,0 +1,32 @@
+package com.ruoyi.common.tenant.manager;
+
+import com.ruoyi.common.core.constant.GlobalConstants;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.redis.manager.PlusSpringCacheManager;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+import org.springframework.cache.Cache;
+
+/**
+ * 重写 cacheName 处理方法 支持多租户
+ *
+ * @author Lion Li
+ */
+public class TenantSpringCacheManager extends PlusSpringCacheManager {
+
+    public TenantSpringCacheManager() {
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.getCache(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(name, tenantId)) {
+            // 如果存在则直接返回
+            return super.getCache(name);
+        }
+        return super.getCache(tenantId + ":" + name);
+    }
+
+}

+ 27 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/com/ruoyi/common/tenant/properties/TenantProperties.java

@@ -0,0 +1,27 @@
+package com.ruoyi.common.tenant.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * 租户 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "tenant")
+public class TenantProperties {
+
+    /**
+     * 是否启用
+     */
+    private Boolean enable;
+
+    /**
+     * 排除表
+     */
+    private List<String> excludes;
+
+}

+ 1 - 0
ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.ruoyi.common.tenant.config.TenantConfig

+ 1 - 1
ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/config/FilterConfig.java

@@ -30,7 +30,7 @@ public class FilterConfig {
         FilterRegistrationBean registration = new FilterRegistrationBean();
         registration.setDispatcherTypes(DispatcherType.REQUEST);
         registration.setFilter(new XssFilter());
-        registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), ","));
+        registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
         registration.setName("xssFilter");
         registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
         Map<String, String> initParameters = new HashMap<>();

+ 2 - 0
ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/config/properties/CaptchaProperties.java

@@ -14,6 +14,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 @ConfigurationProperties(prefix = "captcha")
 public class CaptchaProperties {
 
+    private Boolean enable;
+
     /**
      * 验证码类型
      */

+ 1 - 1
ruoyi-common/ruoyi-common-web/src/main/java/com/ruoyi/common/web/filter/XssFilter.java

@@ -25,7 +25,7 @@ public class XssFilter implements Filter {
     public void init(FilterConfig filterConfig) throws ServletException {
         String tempExcludes = filterConfig.getInitParameter("excludes");
         if (StringUtils.isNotEmpty(tempExcludes)) {
-            String[] url = tempExcludes.split(",");
+            String[] url = tempExcludes.split(StringUtils.SEPARATOR);
             for (int i = 0; url != null && i < url.length; i++) {
                 excludes.add(url[i]);
             }

+ 9 - 9
ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java

@@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit;
 @RequestMapping("/demo/demo")
 public class TestDemoController extends BaseController {
 
-    private final ITestDemoService iTestDemoService;
+    private final ITestDemoService testDemoService;
 
     /**
      * 查询测试单表列表
@@ -53,7 +53,7 @@ public class TestDemoController extends BaseController {
     @SaCheckPermission("demo:demo:list")
     @GetMapping("/list")
     public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
-        return iTestDemoService.queryPageList(bo, pageQuery);
+        return testDemoService.queryPageList(bo, pageQuery);
     }
 
     /**
@@ -62,7 +62,7 @@ public class TestDemoController extends BaseController {
     @SaCheckPermission("demo:demo:list")
     @GetMapping("/page")
     public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
-        return iTestDemoService.customPageList(bo, pageQuery);
+        return testDemoService.customPageList(bo, pageQuery);
     }
 
     /**
@@ -77,7 +77,7 @@ public class TestDemoController extends BaseController {
         ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
         List<TestDemoImportVo> volist = excelResult.getList();
         List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
-        iTestDemoService.saveBatch(list);
+        testDemoService.saveBatch(list);
         return R.ok(excelResult.getAnalysis());
     }
 
@@ -88,7 +88,7 @@ public class TestDemoController extends BaseController {
     @Log(title = "测试单表", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(@Validated TestDemoBo bo, HttpServletResponse response) {
-        List<TestDemoVo> list = iTestDemoService.queryList(bo);
+        List<TestDemoVo> list = testDemoService.queryList(bo);
         // 测试雪花id导出
 //        for (TestDemoVo vo : list) {
 //            vo.setId(1234567891234567893L);
@@ -105,7 +105,7 @@ public class TestDemoController extends BaseController {
     @GetMapping("/{id}")
     public R<TestDemoVo> getInfo(@NotNull(message = "主键不能为空")
                                  @PathVariable("id") Long id) {
-        return R.ok(iTestDemoService.queryById(id));
+        return R.ok(testDemoService.queryById(id));
     }
 
     /**
@@ -119,7 +119,7 @@ public class TestDemoController extends BaseController {
         // 使用校验工具对标 @Validated(AddGroup.class) 注解
         // 用于在非 Controller 的地方校验对象
         ValidatorUtils.validate(bo, AddGroup.class);
-        return toAjax(iTestDemoService.insertByBo(bo));
+        return toAjax(testDemoService.insertByBo(bo));
     }
 
     /**
@@ -130,7 +130,7 @@ public class TestDemoController extends BaseController {
     @RepeatSubmit
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) {
-        return toAjax(iTestDemoService.updateByBo(bo));
+        return toAjax(testDemoService.updateByBo(bo));
     }
 
     /**
@@ -143,6 +143,6 @@ public class TestDemoController extends BaseController {
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] ids) {
-        return toAjax(iTestDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
+        return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
     }
 }

+ 7 - 7
ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestTreeController.java

@@ -35,7 +35,7 @@ import java.util.List;
 @RequestMapping("/demo/tree")
 public class TestTreeController extends BaseController {
 
-    private final ITestTreeService iTestTreeService;
+    private final ITestTreeService testTreeService;
 
     /**
      * 查询测试树表列表
@@ -43,7 +43,7 @@ public class TestTreeController extends BaseController {
     @SaCheckPermission("demo:tree:list")
     @GetMapping("/list")
     public R<List<TestTreeVo>> list(@Validated(QueryGroup.class) TestTreeBo bo) {
-        List<TestTreeVo> list = iTestTreeService.queryList(bo);
+        List<TestTreeVo> list = testTreeService.queryList(bo);
         return R.ok(list);
     }
 
@@ -54,7 +54,7 @@ public class TestTreeController extends BaseController {
     @Log(title = "测试树表", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
-        List<TestTreeVo> list = iTestTreeService.queryList(bo);
+        List<TestTreeVo> list = testTreeService.queryList(bo);
         ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response);
     }
 
@@ -67,7 +67,7 @@ public class TestTreeController extends BaseController {
     @GetMapping("/{id}")
     public R<TestTreeVo> getInfo(@NotNull(message = "主键不能为空")
                                  @PathVariable("id") Long id) {
-        return R.ok(iTestTreeService.queryById(id));
+        return R.ok(testTreeService.queryById(id));
     }
 
     /**
@@ -78,7 +78,7 @@ public class TestTreeController extends BaseController {
     @RepeatSubmit
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) {
-        return toAjax(iTestTreeService.insertByBo(bo));
+        return toAjax(testTreeService.insertByBo(bo));
     }
 
     /**
@@ -89,7 +89,7 @@ public class TestTreeController extends BaseController {
     @RepeatSubmit
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) {
-        return toAjax(iTestTreeService.updateByBo(bo));
+        return toAjax(testTreeService.updateByBo(bo));
     }
 
     /**
@@ -102,6 +102,6 @@ public class TestTreeController extends BaseController {
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] ids) {
-        return toAjax(iTestTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
+        return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
     }
 }

+ 7 - 2
ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/TestTree.java

@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.Version;
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -19,7 +19,7 @@ import java.io.Serial;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("test_tree")
-public class TestTree extends TreeEntity<TestTree> {
+public class TestTree extends BaseEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;
@@ -30,6 +30,11 @@ public class TestTree extends TreeEntity<TestTree> {
     @TableId(value = "id")
     private Long id;
 
+    /**
+     * 父ID
+     */
+    private Long parentId;
+
     /**
      * 部门id
      */

+ 9 - 5
ruoyi-modules/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestTreeBo.java

@@ -2,12 +2,11 @@ package com.ruoyi.demo.domain.bo;
 
 import com.ruoyi.common.core.validate.AddGroup;
 import com.ruoyi.common.core.validate.EditGroup;
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 
 /**
  * 测试树表业务对象 test_tree
@@ -18,7 +17,7 @@ import jakarta.validation.constraints.NotNull;
 
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class TestTreeBo extends TreeEntity<TestTreeBo> {
+public class TestTreeBo extends BaseEntity {
 
     /**
      * 主键
@@ -26,6 +25,11 @@ public class TestTreeBo extends TreeEntity<TestTreeBo> {
     @NotNull(message = "主键不能为空", groups = {EditGroup.class})
     private Long id;
 
+    /**
+     * 父ID
+     */
+    private Long parentId;
+
     /**
      * 部门id
      */

+ 5 - 10
ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/constant/GenConstants.java

@@ -69,35 +69,30 @@ public interface GenConstants {
      * BO对象 不需要添加字段
      */
     String[] COLUMNNAME_NOT_ADD = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
-        "update_time", "version"};
+        "update_time", "version", "tenant_id"};
 
     /**
      * BO对象 不需要编辑字段
      */
     String[] COLUMNNAME_NOT_EDIT = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
-        "update_time", "version"};
+        "update_time", "version", "tenant_id"};
 
     /**
      * VO对象 不需要返回字段
      */
     String[] COLUMNNAME_NOT_LIST = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
-        "update_time", "version"};
+        "update_time", "version", "tenant_id"};
 
     /**
      * BO对象 不需要查询字段
      */
     String[] COLUMNNAME_NOT_QUERY = {"id", "create_dept", "create_by", "create_time", "del_flag", "update_by",
-        "update_time", "remark", "version"};
+        "update_time", "remark", "version", "tenant_id"};
 
     /**
      * Entity基类字段
      */
-    String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime"};
-
-    /**
-     * Tree基类字段
-     */
-    String[] TREE_ENTITY = {"parentName", "parentId", "children"};
+    String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime", "tenantId"};
 
     /**
      * 文本框

+ 5 - 7
ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java

@@ -1,14 +1,16 @@
 package com.ruoyi.generator.domain;
 
-import com.baomidou.mybatisplus.annotation.*;
-import com.ruoyi.generator.constant.GenConstants;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.ruoyi.generator.constant.GenConstants;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.apache.commons.lang3.ArrayUtils;
 
 import java.util.List;
 
@@ -183,10 +185,6 @@ public class GenTable extends BaseEntity {
     }
 
     public static boolean isSuperColumn(String tplCategory, String javaField) {
-        if (isTree(tplCategory)) {
-            return StringUtils.equalsAnyIgnoreCase(javaField,
-                ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
-        }
         return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
     }
 }

+ 1 - 1
ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java

@@ -212,7 +212,7 @@ public class GenTableColumn extends BaseEntity {
                 if (StringUtils.isNotEmpty(value)) {
                     Object startStr = value.subSequence(0, 1);
                     String endStr = value.substring(1);
-                    sb.append("").append(startStr).append("=").append(endStr).append(",");
+                    sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
                 }
             }
             return sb.deleteCharAt(sb.length() - 1).toString();

+ 2 - 2
ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java

@@ -59,7 +59,7 @@ public class GenUtils {
             column.setHtmlType(GenConstants.HTML_INPUT);
 
             // 如果是浮点型 统一用BigDecimal
-            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
+            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), StringUtils.SEPARATOR);
             if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
                 column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
             }
@@ -168,7 +168,7 @@ public class GenUtils {
         boolean autoRemovePre = GenConfig.getAutoRemovePre();
         String tablePrefix = GenConfig.getTablePrefix();
         if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
-            String[] searchList = StringUtils.split(tablePrefix, ",");
+            String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
             tableName = replaceFirst(tableName, searchList);
         }
         return StringUtils.convertToCamelCase(tableName);

+ 1 - 1
ruoyi-modules/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java

@@ -225,7 +225,7 @@ public class VelocityUtils {
      */
     public static String getDicts(GenTable genTable) {
         List<GenTableColumn> columns = genTable.getColumns();
-        Set<String> dicts = new HashSet<String>();
+        Set<String> dicts = new HashSet<>();
         addDicts(dicts, columns);
         return StringUtils.join(dicts, ", ");
     }

+ 4 - 15
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/bo.java.vm

@@ -1,21 +1,16 @@
 package ${packageName}.domain.bo;
 
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
 import com.ruoyi.common.core.validate.AddGroup;
 import com.ruoyi.common.core.validate.EditGroup;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import jakarta.validation.constraints.*;
-
-import java.util.Date;
-
 #foreach ($import in $importList)
 import ${import};
 #end
-#if($table.crud || $table.sub)
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
-#elseif($table.tree)
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
-#end
+
+import java.util.Date;
 
 /**
  * ${functionName}业务对象 ${tableName}
@@ -23,15 +18,9 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
  * @author ${author}
  * @date ${datetime}
  */
-#if($table.crud || $table.sub)
-#set($Entity="BaseEntity")
-#elseif($table.tree)
-#set($Entity="TreeEntity<${ClassName}Bo>")
-#end
-
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class ${ClassName}Bo extends ${Entity} {
+public class ${ClassName}Bo extends BaseEntity {
 
 #foreach ($column in $columns)
 #if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.isInsert || $column.isEdit))

+ 7 - 7
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/controller.java.vm

@@ -39,7 +39,7 @@ import com.ruoyi.common.mybatis.core.page.TableDataInfo;
 @RequestMapping("/${moduleName}/${businessName}")
 public class ${ClassName}Controller extends BaseController {
 
-    private final I${ClassName}Service i${ClassName}Service;
+    private final I${ClassName}Service ${className}Service;
 
     /**
      * 查询${functionName}列表
@@ -52,7 +52,7 @@ public class ${ClassName}Controller extends BaseController {
     }
 #elseif($table.tree)
     public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) {
-        List<${ClassName}Vo> list = i${ClassName}Service.queryList(bo);
+        List<${ClassName}Vo> list = ${className}Service.queryList(bo);
         return R.ok(list);
     }
 #end
@@ -64,7 +64,7 @@ public class ${ClassName}Controller extends BaseController {
     @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(${ClassName}Bo bo, HttpServletResponse response) {
-        List<${ClassName}Vo> list = i${ClassName}Service.queryList(bo);
+        List<${ClassName}Vo> list = ${className}Service.queryList(bo);
         ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
     }
 
@@ -77,7 +77,7 @@ public class ${ClassName}Controller extends BaseController {
     @GetMapping("/{${pkColumn.javaField}}")
     public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
                                      @PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) {
-        return R.ok(i${ClassName}Service.queryById(${pkColumn.javaField}));
+        return R.ok(${className}Service.queryById(${pkColumn.javaField}));
     }
 
     /**
@@ -88,7 +88,7 @@ public class ${ClassName}Controller extends BaseController {
     @RepeatSubmit()
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) {
-        return toAjax(i${ClassName}Service.insertByBo(bo));
+        return toAjax(${className}Service.insertByBo(bo));
     }
 
     /**
@@ -99,7 +99,7 @@ public class ${ClassName}Controller extends BaseController {
     @RepeatSubmit()
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) {
-        return toAjax(i${ClassName}Service.updateByBo(bo));
+        return toAjax(${className}Service.updateByBo(bo));
     }
 
     /**
@@ -112,6 +112,6 @@ public class ${ClassName}Controller extends BaseController {
     @DeleteMapping("/{${pkColumn.javaField}s}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
-        return toAjax(i${ClassName}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
+        return toAjax(${className}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
     }
 }

+ 19 - 14
ruoyi-modules/ruoyi-generator/src/main/resources/vm/java/domain.java.vm

@@ -1,21 +1,25 @@
 package ${packageName}.domain;
 
+#foreach ($column in $columns)
+#if($column.javaField=='tenantId')
+#set($IsTenant=1)
+#end
+#end
+#if($IsTenant==1)
+import com.ruoyi.common.tenant.core.TenantEntity;
+#else
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+#end
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import java.io.Serial;
-import java.io.Serializable;
-import java.util.Date;
-import java.math.BigDecimal;
-
 #foreach ($import in $importList)
 import ${import};
 #end
-#if($table.crud || $table.sub)
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
-#elseif($table.tree)
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
-#end
+
+import java.io.Serial;
+import java.util.Date;
+import java.math.BigDecimal;
 
 /**
  * ${functionName}对象 ${tableName}
@@ -23,10 +27,10 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
  * @author ${author}
  * @date ${datetime}
  */
-#if($table.crud || $table.sub)
-    #set($Entity="BaseEntity")
-#elseif($table.tree)
-    #set($Entity="TreeEntity<${ClassName}>")
+#if($IsTenant==1)
+#set($Entity="TenantEntity")
+#else
+#set($Entity="BaseEntity")
 #end
 @Data
 @EqualsAndHashCode(callSuper = true)
@@ -51,6 +55,7 @@ public class ${ClassName} extends ${Entity} {
     @TableId(value = "$column.columnName")
 #end
     private $column.javaType $column.javaField;
+
 #end
 #end
 

+ 4 - 0
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -333,6 +333,7 @@ export default {
       // 表单校验
       rules: {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.required)
 #set($parentheseIndex=$column.columnComment.indexOf("("))
 #if($parentheseIndex != -1)
@@ -344,6 +345,7 @@ export default {
           { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
         ]#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
       }
     };
@@ -404,11 +406,13 @@ export default {
     reset() {
       this.form = {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.htmlType == "checkbox")
         $column.javaField: []#if($foreach.count != $columns.size()),#end
 #else
         $column.javaField: null#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
       };
       this.resetForm("form");

+ 4 - 0
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -343,6 +343,7 @@ export default {
       // 表单校验
       rules: {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.required)
 #set($parentheseIndex=$column.columnComment.indexOf("("))
 #if($parentheseIndex != -1)
@@ -354,6 +355,7 @@ export default {
           { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
         ]#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
       }
     };
@@ -395,11 +397,13 @@ export default {
     reset() {
       this.form = {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.htmlType == "checkbox")
         $column.javaField: []#if($foreach.count != $columns.size()),#end
 #else
         $column.javaField: undefined#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
       };
       this.resetForm("form");

+ 4 - 0
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm

@@ -304,6 +304,7 @@ const data = reactive({
   },
   rules: {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.required)
 #set($parentheseIndex=$column.columnComment.indexOf("("))
 #if($parentheseIndex != -1)
@@ -315,6 +316,7 @@ const data = reactive({
       { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
     ]#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
   }
 });
@@ -365,11 +367,13 @@ function cancel() {
 function reset() {
   form.value = {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.htmlType == "checkbox")
     $column.javaField: []#if($foreach.count != $columns.size()),#end
 #else
     $column.javaField: null#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
   };
   proxy.resetForm("${businessName}Ref");

+ 4 - 0
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm

@@ -315,6 +315,7 @@ const data = reactive({
   },
   rules: {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.required)
 #set($parentheseIndex=$column.columnComment.indexOf("("))
 #if($parentheseIndex != -1)
@@ -326,6 +327,7 @@ const data = reactive({
       { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
     ]#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
   }
 });
@@ -367,11 +369,13 @@ function cancel() {
 function reset() {
   form.value = {
 #foreach ($column in $columns)
+#if($column.isInsert || $column.isEdit)
 #if($column.htmlType == "checkbox")
     $column.javaField: []#if($foreach.count != $columns.size()),#end
 #else
     $column.javaField: null#if($foreach.count != $columns.size()),#end
 #end
+#end
 #end
   };
   proxy.resetForm("${businessName}Ref");

+ 1 - 1
ruoyi-modules/ruoyi-system/pom.xml

@@ -63,7 +63,7 @@
 
         <dependency>
             <groupId>com.ruoyi</groupId>
-            <artifactId>ruoyi-common-satoken</artifactId>
+            <artifactId>ruoyi-common-tenant</artifactId>
         </dependency>
 
         <dependency>

+ 0 - 168
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/CacheController.java

@@ -1,168 +0,0 @@
-package com.ruoyi.system.controller.monitor;
-
-import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.collection.CollUtil;
-import com.ruoyi.common.core.constant.CacheConstants;
-import com.ruoyi.common.core.constant.CacheNames;
-import com.ruoyi.common.core.domain.R;
-import com.ruoyi.common.core.utils.StreamUtils;
-import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.json.utils.JsonUtils;
-import com.ruoyi.common.redis.utils.CacheUtils;
-import com.ruoyi.common.redis.utils.RedisUtils;
-import com.ruoyi.system.domain.SysCache;
-import com.ruoyi.system.domain.vo.CacheListInfoVo;
-import lombok.RequiredArgsConstructor;
-import org.redisson.spring.data.connection.RedissonConnectionFactory;
-import org.springframework.data.redis.connection.RedisConnection;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.*;
-
-/**
- * 缓存监控
- *
- * @author Lion Li
- */
-@RequiredArgsConstructor
-@RestController
-@RequestMapping("/monitor/cache")
-public class CacheController {
-
-    private final RedissonConnectionFactory connectionFactory;
-
-    private final static List<SysCache> CACHES = new ArrayList<>();
-
-    static {
-        CACHES.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
-        CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
-        CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
-        CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
-        CACHES.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
-        CACHES.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
-        CACHES.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
-        CACHES.add(new SysCache(CacheNames.SYS_OSS_CONFIG, "OSS配置"));
-        CACHES.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
-    }
-
-    /**
-     * 获取缓存监控列表
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @GetMapping()
-    public R<CacheListInfoVo> getInfo() throws Exception {
-        RedisConnection connection = connectionFactory.getConnection();
-        Properties commandStats = connection.commands().info("commandstats");
-
-        List<Map<String, String>> pieList = new ArrayList<>();
-        if (commandStats != null) {
-            commandStats.stringPropertyNames().forEach(key -> {
-                Map<String, String> data = new HashMap<>(2);
-                String property = commandStats.getProperty(key);
-                data.put("name", StringUtils.removeStart(key, "cmdstat_"));
-                data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
-                pieList.add(data);
-            });
-        }
-
-        CacheListInfoVo infoVo = new CacheListInfoVo();
-        infoVo.setInfo(connection.commands().info());
-        infoVo.setDbSize(connection.commands().dbSize());
-        infoVo.setCommandStats(pieList);
-        return R.ok(infoVo);
-    }
-
-    /**
-     * 获取缓存监控缓存名列表
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @GetMapping("/getNames")
-    public R<List<SysCache>> cache() {
-        return R.ok(CACHES);
-    }
-
-    /**
-     * 获取缓存监控Key列表
-     *
-     * @param cacheName 缓存名
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @GetMapping("/getKeys/{cacheName}")
-    public R<Collection<String>> getCacheKeys(@PathVariable String cacheName) {
-        Collection<String> cacheKeys = new HashSet<>(0);
-        if (isCacheNames(cacheName)) {
-            Set<Object> keys = CacheUtils.keys(cacheName);
-            if (CollUtil.isNotEmpty(keys)) {
-                cacheKeys = StreamUtils.toList(keys, Object::toString);
-            }
-        } else {
-            cacheKeys = RedisUtils.keys(cacheName + "*");
-        }
-        return R.ok(cacheKeys);
-    }
-
-    /**
-     * 获取缓存监控缓存值详情
-     *
-     * @param cacheName 缓存名
-     * @param cacheKey  缓存key
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @GetMapping("/getValue/{cacheName}/{cacheKey}")
-    public R<SysCache> getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
-        Object cacheValue;
-        if (isCacheNames(cacheName)) {
-            cacheValue = CacheUtils.get(cacheName, cacheKey);
-        } else {
-            cacheValue = RedisUtils.getCacheObject(cacheKey);
-        }
-        SysCache sysCache = new SysCache(cacheName, cacheKey, JsonUtils.toJsonString(cacheValue));
-        return R.ok(sysCache);
-    }
-
-    /**
-     * 清理缓存监控缓存名
-     *
-     * @param cacheName 缓存名
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @DeleteMapping("/clearCacheName/{cacheName}")
-    public R<Void> clearCacheName(@PathVariable String cacheName) {
-        if (isCacheNames(cacheName)) {
-            CacheUtils.clear(cacheName);
-        } else {
-            RedisUtils.deleteKeys(cacheName + "*");
-        }
-        return R.ok();
-    }
-
-    /**
-     * 清理缓存监控Key
-     *
-     * @param cacheKey key名
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @DeleteMapping("/clearCacheKey/{cacheName}/{cacheKey}")
-    public R<Void> clearCacheKey(@PathVariable String cacheName, @PathVariable String cacheKey) {
-        if (isCacheNames(cacheName)) {
-            CacheUtils.evict(cacheName, cacheKey);
-        } else {
-            RedisUtils.deleteObject(cacheKey);
-        }
-        return R.ok();
-    }
-
-    /**
-     * 清理全部缓存监控
-     */
-    @SaCheckPermission("monitor:cache:list")
-    @DeleteMapping("/clearCacheAll")
-    public R<Void> clearCacheAll() {
-        RedisUtils.deleteKeys("*");
-        return R.ok();
-    }
-
-    private boolean isCacheNames(String cacheName) {
-        return !StringUtils.contains(cacheName, ":");
-    }
-}

+ 12 - 11
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysLogininforController.java

@@ -1,22 +1,23 @@
 package com.ruoyi.system.controller.monitor;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import com.ruoyi.common.core.constant.CacheConstants;
-import com.ruoyi.common.mybatis.core.page.PageQuery;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.domain.R;
-import com.ruoyi.common.mybatis.core.page.TableDataInfo;
-import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.common.excel.utils.ExcelUtil;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.mybatis.core.page.PageQuery;
+import com.ruoyi.common.mybatis.core.page.TableDataInfo;
 import com.ruoyi.common.redis.utils.RedisUtils;
-import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.system.domain.bo.SysLogininforBo;
+import com.ruoyi.system.domain.vo.SysLogininforVo;
 import com.ruoyi.system.service.ISysLogininforService;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import jakarta.servlet.http.HttpServletResponse;
 import java.util.List;
 
 /**
@@ -37,7 +38,7 @@ public class SysLogininforController extends BaseController {
      */
     @SaCheckPermission("monitor:logininfor:list")
     @GetMapping("/list")
-    public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) {
+    public TableDataInfo<SysLogininforVo> list(SysLogininforBo logininfor, PageQuery pageQuery) {
         return logininforService.selectPageLogininforList(logininfor, pageQuery);
     }
 
@@ -47,9 +48,9 @@ public class SysLogininforController extends BaseController {
     @Log(title = "登录日志", businessType = BusinessType.EXPORT)
     @SaCheckPermission("monitor:logininfor:export")
     @PostMapping("/export")
-    public void export(SysLogininfor logininfor, HttpServletResponse response) {
-        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
-        ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response);
+    public void export(SysLogininforBo logininfor, HttpServletResponse response) {
+        List<SysLogininforVo> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil.exportExcel(list, "登录日志", SysLogininforVo.class, response);
     }
 
     /**
@@ -78,7 +79,7 @@ public class SysLogininforController extends BaseController {
     @Log(title = "账户解锁", businessType = BusinessType.OTHER)
     @GetMapping("/unlock/{userName}")
     public R<Void> unlock(@PathVariable("userName") String userName) {
-        String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
+        String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName;
         if (RedisUtils.hasKey(loginName)) {
             RedisUtils.deleteObject(loginName);
         }

+ 6 - 5
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysOperlogController.java

@@ -8,7 +8,8 @@ import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.mybatis.core.page.TableDataInfo;
 import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.excel.utils.ExcelUtil;
-import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.domain.bo.SysOperLogBo;
+import com.ruoyi.system.domain.vo.SysOperLogVo;
 import com.ruoyi.system.service.ISysOperLogService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
@@ -35,7 +36,7 @@ public class SysOperlogController extends BaseController {
      */
     @SaCheckPermission("monitor:operlog:list")
     @GetMapping("/list")
-    public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) {
+    public TableDataInfo<SysOperLogVo> list(SysOperLogBo operLog, PageQuery pageQuery) {
         return operLogService.selectPageOperLogList(operLog, pageQuery);
     }
 
@@ -45,9 +46,9 @@ public class SysOperlogController extends BaseController {
     @Log(title = "操作日志", businessType = BusinessType.EXPORT)
     @SaCheckPermission("monitor:operlog:export")
     @PostMapping("/export")
-    public void export(SysOperLog operLog, HttpServletResponse response) {
-        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
-        ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response);
+    public void export(SysOperLogBo operLog, HttpServletResponse response) {
+        List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil.exportExcel(list, "操作日志", SysOperLogVo.class, response);
     }
 
     /**

+ 6 - 5
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/monitor/SysUserOnlineController.java

@@ -4,16 +4,17 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.exception.NotLoginException;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.bean.BeanUtil;
-import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.core.constant.CacheConstants;
-import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.common.core.constant.GlobalConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.dto.UserOnlineDTO;
-import com.ruoyi.common.mybatis.core.page.TableDataInfo;
-import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.core.utils.StreamUtils;
 import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.mybatis.core.page.TableDataInfo;
 import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.system.domain.SysUserOnline;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
@@ -45,7 +46,7 @@ public class SysUserOnlineController extends BaseController {
         List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
         List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
         for (String key : keys) {
-            String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
+            String token = key.replace(GlobalConstants.LOGIN_TOKEN_KEY, "");
             // 如果已经过期则跳过
             if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
                 continue;

+ 5 - 5
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysDeptController.java

@@ -1,13 +1,13 @@
 package com.ruoyi.system.controller.system;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.util.ArrayUtil;
-import com.ruoyi.common.log.annotation.Log;
+import cn.hutool.core.convert.Convert;
 import com.ruoyi.common.core.constant.UserConstants;
-import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.common.core.domain.R;
-import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.system.domain.bo.SysDeptBo;
 import com.ruoyi.system.domain.vo.SysDeptVo;
 import com.ruoyi.system.service.ISysDeptService;
@@ -50,7 +50,7 @@ public class SysDeptController extends BaseController {
     public R<List<SysDeptVo>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
         List<SysDeptVo> depts = deptService.selectDeptList(new SysDeptBo());
         depts.removeIf(d -> d.getDeptId().equals(deptId)
-            || ArrayUtil.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
+            || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
         return R.ok(depts);
     }
 

+ 41 - 0
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysMenuController.java

@@ -1,7 +1,10 @@
 package com.ruoyi.system.controller.system;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
 import cn.hutool.core.lang.tree.Tree;
+import com.ruoyi.common.core.constant.TenantConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.utils.StringUtils;
@@ -35,6 +38,10 @@ public class SysMenuController extends BaseController {
     /**
      * 获取菜单列表
      */
+    @SaCheckRole(value = {
+            TenantConstants.SUPER_ADMIN_ROLE_KEY,
+            TenantConstants.TENANT_ADMIN_ROLE_KEY
+    }, mode = SaMode.OR)
     @SaCheckPermission("system:menu:list")
     @GetMapping("/list")
     public R<List<SysMenuVo>> list(SysMenuBo menu) {
@@ -47,6 +54,10 @@ public class SysMenuController extends BaseController {
      *
      * @param menuId 菜单ID
      */
+    @SaCheckRole(value = {
+            TenantConstants.SUPER_ADMIN_ROLE_KEY,
+            TenantConstants.TENANT_ADMIN_ROLE_KEY
+    }, mode = SaMode.OR)
     @SaCheckPermission("system:menu:query")
     @GetMapping(value = "/{menuId}")
     public R<SysMenuVo> getInfo(@PathVariable Long menuId) {
@@ -56,6 +67,11 @@ public class SysMenuController extends BaseController {
     /**
      * 获取菜单下拉树列表
      */
+    @SaCheckRole(value = {
+            TenantConstants.SUPER_ADMIN_ROLE_KEY,
+            TenantConstants.TENANT_ADMIN_ROLE_KEY
+    }, mode = SaMode.OR)
+    @SaCheckPermission("system:menu:query")
     @GetMapping("/treeselect")
     public R<List<Tree<Long>>> treeselect(SysMenuBo menu) {
         List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
@@ -67,6 +83,11 @@ public class SysMenuController extends BaseController {
      *
      * @param roleId 角色ID
      */
+    @SaCheckRole(value = {
+            TenantConstants.SUPER_ADMIN_ROLE_KEY,
+            TenantConstants.TENANT_ADMIN_ROLE_KEY
+    }, mode = SaMode.OR)
+    @SaCheckPermission("system:menu:query")
     @GetMapping(value = "/roleMenuTreeselect/{roleId}")
     public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
         List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
@@ -76,9 +97,26 @@ public class SysMenuController extends BaseController {
         return R.ok(selectVo);
     }
 
+    /**
+     * 加载对应租户套餐菜单列表树
+     *
+     * @param packageId 租户套餐ID
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:menu:query")
+    @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
+    public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
+        List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
+        MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
+        selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId));
+        selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
+        return R.ok(selectVo);
+    }
+
     /**
      * 新增菜单
      */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
     @SaCheckPermission("system:menu:add")
     @Log(title = "菜单管理", businessType = BusinessType.INSERT)
     @PostMapping
@@ -94,6 +132,7 @@ public class SysMenuController extends BaseController {
     /**
      * 修改菜单
      */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
     @SaCheckPermission("system:menu:edit")
     @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
     @PutMapping
@@ -113,6 +152,7 @@ public class SysMenuController extends BaseController {
      *
      * @param menuId 菜单ID
      */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
     @SaCheckPermission("system:menu:remove")
     @Log(title = "菜单管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{menuId}")
@@ -125,4 +165,5 @@ public class SysMenuController extends BaseController {
         }
         return toAjax(menuService.deleteMenuById(menuId));
     }
+
 }

+ 7 - 7
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java

@@ -35,7 +35,7 @@ import java.util.List;
 @RequestMapping("/system/oss/config")
 public class SysOssConfigController extends BaseController {
 
-    private final ISysOssConfigService iSysOssConfigService;
+    private final ISysOssConfigService sysOssConfigService;
 
     /**
      * 查询对象存储配置列表
@@ -43,7 +43,7 @@ public class SysOssConfigController extends BaseController {
     @SaCheckPermission("system:oss:list")
     @GetMapping("/list")
     public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
-        return iSysOssConfigService.queryPageList(bo, pageQuery);
+        return sysOssConfigService.queryPageList(bo, pageQuery);
     }
 
     /**
@@ -55,7 +55,7 @@ public class SysOssConfigController extends BaseController {
     @GetMapping("/{ossConfigId}")
     public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
                                      @PathVariable Long ossConfigId) {
-        return R.ok(iSysOssConfigService.queryById(ossConfigId));
+        return R.ok(sysOssConfigService.queryById(ossConfigId));
     }
 
     /**
@@ -66,7 +66,7 @@ public class SysOssConfigController extends BaseController {
     @RepeatSubmit()
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
-        return toAjax(iSysOssConfigService.insertByBo(bo));
+        return toAjax(sysOssConfigService.insertByBo(bo));
     }
 
     /**
@@ -77,7 +77,7 @@ public class SysOssConfigController extends BaseController {
     @RepeatSubmit()
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
-        return toAjax(iSysOssConfigService.updateByBo(bo));
+        return toAjax(sysOssConfigService.updateByBo(bo));
     }
 
     /**
@@ -90,7 +90,7 @@ public class SysOssConfigController extends BaseController {
     @DeleteMapping("/{ossConfigIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] ossConfigIds) {
-        return toAjax(iSysOssConfigService.deleteWithValidByIds(List.of(ossConfigIds), true));
+        return toAjax(sysOssConfigService.deleteWithValidByIds(List.of(ossConfigIds), true));
     }
 
     /**
@@ -100,6 +100,6 @@ public class SysOssConfigController extends BaseController {
     @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
     @PutMapping("/changeStatus")
     public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
-        return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
+        return toAjax(sysOssConfigService.updateOssConfigStatus(bo));
     }
 }

+ 6 - 6
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssController.java

@@ -38,7 +38,7 @@ import java.util.List;
 @RequestMapping("/system/oss")
 public class SysOssController extends BaseController {
 
-    private final ISysOssService iSysOssService;
+    private final ISysOssService sysOssService;
 
     /**
      * 查询OSS对象存储列表
@@ -46,7 +46,7 @@ public class SysOssController extends BaseController {
     @SaCheckPermission("system:oss:list")
     @GetMapping("/list")
     public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
-        return iSysOssService.queryPageList(bo, pageQuery);
+        return sysOssService.queryPageList(bo, pageQuery);
     }
 
     /**
@@ -58,7 +58,7 @@ public class SysOssController extends BaseController {
     @GetMapping("/listByIds/{ossIds}")
     public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
                                        @PathVariable Long[] ossIds) {
-        List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
+        List<SysOssVo> list = sysOssService.listByIds(Arrays.asList(ossIds));
         return R.ok(list);
     }
 
@@ -74,7 +74,7 @@ public class SysOssController extends BaseController {
         if (ObjectUtil.isNull(file)) {
             throw new ServiceException("上传文件不能为空");
         }
-        SysOssVo oss = iSysOssService.upload(file);
+        SysOssVo oss = sysOssService.upload(file);
         SysOssUploadVo uploadVo = new SysOssUploadVo();
         uploadVo.setUrl(oss.getUrl());
         uploadVo.setFileName(oss.getOriginalName());
@@ -90,7 +90,7 @@ public class SysOssController extends BaseController {
     @SaCheckPermission("system:oss:download")
     @GetMapping("/download/{ossId}")
     public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
-        iSysOssService.download(ossId, response);
+        sysOssService.download(ossId, response);
     }
 
     /**
@@ -103,7 +103,7 @@ public class SysOssController extends BaseController {
     @DeleteMapping("/{ossIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] ossIds) {
-        return toAjax(iSysOssService.deleteWithValidByIds(List.of(ossIds), true));
+        return toAjax(sysOssService.deleteWithValidByIds(List.of(ossIds), true));
     }
 
 }

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java

@@ -39,7 +39,7 @@ import java.util.Arrays;
 public class SysProfileController extends BaseController {
 
     private final ISysUserService userService;
-    private final ISysOssService iSysOssService;
+    private final ISysOssService sysOssService;
 
     /**
      * 个人信息
@@ -114,7 +114,7 @@ public class SysProfileController extends BaseController {
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
                 return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
             }
-            SysOssVo oss = iSysOssService.upload(avatarfile);
+            SysOssVo oss = sysOssService.upload(avatarfile);
             String avatar = oss.getUrl();
             if (userService.updateUserAvatar(LoginHelper.getUsername(), oss.getOssId())) {
                 AvatarVo avatarVo = new AvatarVo();

+ 10 - 12
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysRoleController.java

@@ -1,16 +1,13 @@
 package com.ruoyi.system.controller.system;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.R;
-import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.excel.utils.ExcelUtil;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.mybatis.core.page.PageQuery;
 import com.ruoyi.common.mybatis.core.page.TableDataInfo;
-import com.ruoyi.common.satoken.utils.LoginHelper;
 import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.system.domain.SysDept;
 import com.ruoyi.system.domain.SysUserRole;
@@ -20,9 +17,9 @@ import com.ruoyi.system.domain.vo.DeptTreeSelectVo;
 import com.ruoyi.system.domain.vo.SysRoleVo;
 import com.ruoyi.system.domain.vo.SysUserVo;
 import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysPermissionService;
 import com.ruoyi.system.service.ISysRoleService;
 import com.ruoyi.system.service.ISysUserService;
-import com.ruoyi.system.service.SysPermissionService;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
@@ -44,7 +41,7 @@ public class SysRoleController extends BaseController {
     private final ISysRoleService roleService;
     private final ISysUserService userService;
     private final ISysDeptService deptService;
-    private final SysPermissionService permissionService;
+    private final ISysPermissionService permissionService;
 
     /**
      * 获取角色信息列表
@@ -110,13 +107,14 @@ public class SysRoleController extends BaseController {
         }
 
         if (roleService.updateRole(role) > 0) {
-            // 更新缓存用户权限
-            LoginUser loginUser = LoginHelper.getLoginUser();
-            SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
-            if (ObjectUtil.isNotNull(sysUser) && !LoginHelper.isAdmin()) {
-                loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId(), sysUser.isAdmin()));
-                LoginHelper.setLoginUser(loginUser);
-            }
+//            // 更新缓存用户权限
+//            LoginUser loginUser = LoginHelper.getLoginUser();
+//            SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
+//            if (ObjectUtil.isNotNull(sysUser)) {
+//                loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId()));
+//                LoginHelper.setLoginUser(loginUser);
+//            }
+            // todo LoginUser 改为存储到token内部 无法热更新 等待后续想办法
             return R.ok();
         }
         return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");

+ 168 - 0
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysTenantController.java

@@ -0,0 +1,168 @@
+package com.ruoyi.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import com.baomidou.lock.annotation.Lock4j;
+import com.ruoyi.common.core.constant.TenantConstants;
+import com.ruoyi.common.core.constant.UserConstants;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.excel.utils.ExcelUtil;
+import com.ruoyi.common.idempotent.annotation.RepeatSubmit;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.mybatis.core.page.PageQuery;
+import com.ruoyi.common.mybatis.core.page.TableDataInfo;
+import com.ruoyi.common.tenant.helper.TenantHelper;
+import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.system.domain.bo.SysTenantBo;
+import com.ruoyi.system.domain.bo.SysUserBo;
+import com.ruoyi.system.domain.vo.SysTenantVo;
+import com.ruoyi.system.service.ISysTenantService;
+import com.ruoyi.system.service.ISysUserService;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 租户管理
+ *
+ * @author Michelle.Chung
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/tenant")
+public class SysTenantController extends BaseController {
+
+    private final ISysTenantService sysTenantService;
+    private final ISysUserService sysUserService;
+
+    /**
+     * 查询租户列表
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysTenantVo> list(SysTenantBo bo, PageQuery pageQuery) {
+        return sysTenantService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出租户列表
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:export")
+    @Log(title = "租户", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysTenantBo bo, HttpServletResponse response) {
+        List<SysTenantVo> list = sysTenantService.queryList(bo);
+        ExcelUtil.exportExcel(list, "租户", SysTenantVo.class, response);
+    }
+
+    /**
+     * 获取租户详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:query")
+    @GetMapping("/{id}")
+    public R<SysTenantVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long id) {
+        return R.ok(sysTenantService.queryById(id));
+    }
+
+    /**
+     * 新增租户
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:add")
+    @Log(title = "租户", businessType = BusinessType.INSERT)
+    @Lock4j
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) {
+        if (TenantConstants.NOT_PASS.equals(sysTenantService.checkCompanyNameUnique(bo))) {
+            throw new ServiceException("新增租户'" + bo.getCompanyName() + "'失败,企业名称已存在");
+        }
+        SysUserBo userBo = new SysUserBo();
+        userBo.setUserName(bo.getUsername());
+        // 判断用户名是否重复
+        if (UserConstants.NOT_UNIQUE.equals(sysUserService.checkUserNameUnique(userBo))) {
+            throw new ServiceException("新增用户'" + bo.getUsername() + "'失败,登录账号已存在");
+        }
+        return toAjax(sysTenantService.insertByBo(bo));
+    }
+
+    /**
+     * 修改租户
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:edit")
+    @Log(title = "租户", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
+        if (UserConstants.NOT_UNIQUE.equals(sysTenantService.checkCompanyNameUnique(bo))) {
+            throw new ServiceException("修改租户'" + bo.getCompanyName() + "'失败,公司名称已存在");
+        }
+        return toAjax(sysTenantService.updateByBo(bo));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:edit")
+    @Log(title = "租户套餐", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
+        return toAjax(sysTenantService.updateTenantStatus(bo));
+    }
+
+    /**
+     * 删除租户
+     *
+     * @param ids 主键串
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenant:remove")
+    @Log(title = "租户", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(sysTenantService.deleteWithValidByIds(List.of(ids), true));
+    }
+
+    /**
+     * 动态切换租户
+     *
+     * @param tenantId 租户ID
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @GetMapping("/dynamic/{tenantId}")
+    public R<Void> dynamicTenant(@NotBlank(message = "租户ID不能为空") @PathVariable String tenantId) {
+        TenantHelper.setDynamic(tenantId);
+        return R.ok();
+    }
+
+    /**
+     * 清除动态租户
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @GetMapping("/dynamic/clear")
+    public R<Void> dynamicClear() {
+        TenantHelper.clearDynamic();
+        return R.ok();
+    }
+
+}

+ 124 - 0
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysTenantPackageController.java

@@ -0,0 +1,124 @@
+package com.ruoyi.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import com.ruoyi.common.core.constant.TenantConstants;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.excel.utils.ExcelUtil;
+import com.ruoyi.common.idempotent.annotation.RepeatSubmit;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.mybatis.core.page.PageQuery;
+import com.ruoyi.common.mybatis.core.page.TableDataInfo;
+import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.system.domain.bo.SysTenantPackageBo;
+import com.ruoyi.system.domain.vo.SysTenantPackageVo;
+import com.ruoyi.system.service.ISysTenantPackageService;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 租户套餐管理
+ *
+ * @author Michelle.Chung
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/tenant/package")
+public class SysTenantPackageController extends BaseController {
+
+    private final ISysTenantPackageService sysTenantPackageService;
+
+    /**
+     * 查询租户套餐列表
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysTenantPackageVo> list(SysTenantPackageBo bo, PageQuery pageQuery) {
+        return sysTenantPackageService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出租户套餐列表
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:export")
+    @Log(title = "租户套餐", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysTenantPackageBo bo, HttpServletResponse response) {
+        List<SysTenantPackageVo> list = sysTenantPackageService.queryList(bo);
+        ExcelUtil.exportExcel(list, "租户套餐", SysTenantPackageVo.class, response);
+    }
+
+    /**
+     * 获取租户套餐详细信息
+     *
+     * @param packageId 主键
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:query")
+    @GetMapping("/{packageId}")
+    public R<SysTenantPackageVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long packageId) {
+        return R.ok(sysTenantPackageService.queryById(packageId));
+    }
+
+    /**
+     * 新增租户套餐
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:add")
+    @Log(title = "租户套餐", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
+        return toAjax(sysTenantPackageService.insertByBo(bo));
+    }
+
+    /**
+     * 修改租户套餐
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:edit")
+    @Log(title = "租户套餐", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
+        return toAjax(sysTenantPackageService.updateByBo(bo));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:edit")
+    @Log(title = "租户套餐", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysTenantPackageBo bo) {
+        return toAjax(sysTenantPackageService.updatePackageStatus(bo));
+    }
+
+    /**
+     * 删除租户套餐
+     *
+     * @param packageIds 主键串
+     */
+    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
+    @SaCheckPermission("system:tenantPackage:remove")
+    @Log(title = "租户套餐", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{packageIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] packageIds) {
+        return toAjax(sysTenantPackageService.deleteWithValidByIds(List.of(packageIds), true));
+    }
+}

+ 12 - 6
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysUserController.java

@@ -6,6 +6,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.core.constant.TenantConstants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.utils.StreamUtils;
@@ -16,16 +17,14 @@ import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.mybatis.core.page.PageQuery;
 import com.ruoyi.common.mybatis.core.page.TableDataInfo;
+import com.ruoyi.common.tenant.helper.TenantHelper;
 import com.ruoyi.common.satoken.utils.LoginHelper;
 import com.ruoyi.common.web.core.BaseController;
 import com.ruoyi.system.domain.SysDept;
 import com.ruoyi.system.domain.bo.SysUserBo;
 import com.ruoyi.system.domain.vo.*;
 import com.ruoyi.system.listener.SysUserImportListener;
-import com.ruoyi.system.service.ISysDeptService;
-import com.ruoyi.system.service.ISysPostService;
-import com.ruoyi.system.service.ISysRoleService;
-import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.*;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.http.MediaType;
@@ -51,6 +50,7 @@ public class SysUserController extends BaseController {
     private final ISysRoleService roleService;
     private final ISysPostService postService;
     private final ISysDeptService deptService;
+    private final ISysTenantService tenantService;
 
     /**
      * 获取用户列表
@@ -114,7 +114,7 @@ public class SysUserController extends BaseController {
         userService.checkUserDataScope(userId);
         SysUserInfoVo userInfoVo = new SysUserInfoVo();
         List<SysRoleVo> roles = roleService.selectRoleAll();
-        userInfoVo.setRoles(LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
         userInfoVo.setPosts(postService.selectPostAll());
         if (ObjectUtil.isNotNull(userId)) {
             SysUserVo sysUser = userService.selectUserById(userId);
@@ -141,6 +141,12 @@ public class SysUserController extends BaseController {
                    && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
             return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
         }
+        if (TenantHelper.isEnable()) {
+            String status = tenantService.checkAccountBalance(LoginHelper.getTenantId());
+            if (TenantConstants.NOT_PASS.equals(status)) {
+                return R.fail("当前租户下用户名额不足,请联系管理员");
+            }
+        }
         user.setPassword(BCrypt.hashpw(user.getPassword()));
         return toAjax(userService.insertUser(user));
     }
@@ -218,7 +224,7 @@ public class SysUserController extends BaseController {
         List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
         SysUserInfoVo userInfoVo = new SysUserInfoVo();
         userInfoVo.setUser(user);
-        userInfoVo.setRoles(LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
+        userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
         return R.ok(userInfoVo);
     }
 

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java

@@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
 
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.ruoyi.common.tenant.core.TenantEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_config")
-public class SysConfig extends BaseEntity {
+public class SysConfig extends TenantEntity {
 
     /**
      * 参数主键

+ 7 - 6
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDept.java

@@ -3,11 +3,7 @@ package com.ruoyi.system.domain;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
-import jakarta.validation.constraints.Email;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
+import com.ruoyi.common.tenant.core.TenantEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -22,7 +18,7 @@ import java.io.Serial;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_dept")
-public class SysDept extends TreeEntity<SysDept> {
+public class SysDept extends TenantEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;
@@ -33,6 +29,11 @@ public class SysDept extends TreeEntity<SysDept> {
     @TableId(value = "dept_id")
     private Long deptId;
 
+    /**
+     * 父部门ID
+     */
+    private Long parentId;
+
     /**
      * 部门名称
      */

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDictData.java

@@ -3,7 +3,7 @@ package com.ruoyi.system.domain;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.ruoyi.common.core.constant.UserConstants;
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.ruoyi.common.tenant.core.TenantEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_dict_data")
-public class SysDictData extends BaseEntity {
+public class SysDictData extends TenantEntity {
 
     /**
      * 字典编码

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDictType.java

@@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
 
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.ruoyi.common.tenant.core.TenantEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_dict_type")
-public class SysDictType extends BaseEntity {
+public class SysDictType extends TenantEntity {
 
     /**
      * 字典主键

+ 5 - 24
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java

@@ -1,19 +1,12 @@
 package com.ruoyi.system.domain;
 
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.ruoyi.common.excel.annotation.ExcelDictFormat;
-import com.ruoyi.common.excel.convert.ExcelDictConvert;
 import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * 系统访问记录表 sys_logininfor
@@ -23,7 +16,6 @@ import java.util.Map;
 
 @Data
 @TableName("sys_logininfor")
-@ExcelIgnoreUnannotated
 public class SysLogininfor implements Serializable {
 
     @Serial
@@ -32,63 +24,52 @@ public class SysLogininfor implements Serializable {
     /**
      * ID
      */
-    @ExcelProperty(value = "序号")
     @TableId(value = "info_id")
     private Long infoId;
 
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
     /**
      * 用户账号
      */
-    @ExcelProperty(value = "用户账号")
     private String userName;
 
     /**
      * 登录状态 0成功 1失败
      */
-    @ExcelProperty(value = "登录状态", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(dictType = "sys_common_status")
     private String status;
 
     /**
      * 登录IP地址
      */
-    @ExcelProperty(value = "登录地址")
     private String ipaddr;
 
     /**
      * 登录地点
      */
-    @ExcelProperty(value = "登录地点")
     private String loginLocation;
 
     /**
      * 浏览器类型
      */
-    @ExcelProperty(value = "浏览器")
     private String browser;
 
     /**
      * 操作系统
      */
-    @ExcelProperty(value = "操作系统")
     private String os;
 
     /**
      * 提示消息
      */
-    @ExcelProperty(value = "提示消息")
     private String msg;
 
     /**
      * 访问时间
      */
-    @ExcelProperty(value = "访问时间")
     private Date loginTime;
 
-    /**
-     * 请求参数
-     */
-    @TableField(exist = false)
-    private Map<String, Object> params = new HashMap<>();
-
 }

+ 23 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysMenu.java

@@ -1,14 +1,18 @@
 package com.ruoyi.system.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.ruoyi.common.core.constant.Constants;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.mybatis.core.domain.TreeEntity;
+import com.ruoyi.common.mybatis.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * 菜单权限表 sys_menu
  *
@@ -18,7 +22,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_menu")
-public class SysMenu extends TreeEntity<SysMenu> {
+public class SysMenu extends BaseEntity {
 
     /**
      * 菜单ID
@@ -26,6 +30,11 @@ public class SysMenu extends TreeEntity<SysMenu> {
     @TableId(value = "menu_id")
     private Long menuId;
 
+    /**
+     * 父菜单ID
+     */
+    private Long parentId;
+
     /**
      * 菜单名称
      */
@@ -91,6 +100,18 @@ public class SysMenu extends TreeEntity<SysMenu> {
      */
     private String remark;
 
+    /**
+     * 父菜单名称
+     */
+    @TableField(exist = false)
+    private String parentName;
+
+    /**
+     * 子菜单
+     */
+    @TableField(exist = false)
+    private List<SysMenu> children = new ArrayList<>();
+
     /**
      * 获取路由名称
      */

+ 2 - 2
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java

@@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
 
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.ruoyi.common.tenant.core.TenantEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("sys_notice")
-public class SysNotice extends BaseEntity {
+public class SysNotice extends TenantEntity {
 
     /**
      * 公告ID

Неке датотеке нису приказане због велике количине промена