Browse Source

1. 新建租户、修改租户、修改租户套餐时,自动修改角色的权限
2. 租户的本地缓存,提升访问性能
3. 精简本地缓存的实现逻辑

YunaiV 3 years ago
parent
commit
e4be51b14a
33 changed files with 405 additions and 139 deletions
  1. 19 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  2. 4 0
      yudao-module-pay/yudao-module-pay-impl/pom.xml
  3. 9 8
      yudao-module-pay/yudao-module-pay-impl/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
  4. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  5. 3 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java
  6. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java
  7. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/packages/TenantPackageBaseVO.java
  8. 2 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java
  9. 2 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleMenuDO.java
  10. 2 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
  11. 10 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java
  12. 7 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java
  13. 0 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsSendConsumer.java
  14. 29 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java
  15. 21 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java
  16. 29 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java
  17. 6 6
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
  18. 2 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java
  19. 3 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java
  20. 5 7
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
  21. 3 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java
  22. 25 24
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
  23. 4 4
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
  24. 7 10
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
  25. 6 8
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
  26. 11 15
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
  27. 14 3
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java
  28. 22 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java
  29. 147 18
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  30. 2 2
      yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java
  31. 2 2
      yudao-server/src/main/resources/application.yaml
  32. 2 6
      yudao-ui-admin/src/views/system/tenantPackage/index.vue
  33. 2 1
      更新日志.md

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

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

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

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

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

@@ -6,15 +6,16 @@ import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
@@ -27,12 +28,12 @@ import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS;
 
 /**
  * 支付渠道 Service 实现类
@@ -66,9 +67,10 @@ public class PayChannelServiceImpl implements PayChannelService {
 
     @Override
     @PostConstruct
+    @TenantIgnore // 忽略自动化租户,全局初始化本地缓存
     public void initPayClients() {
         // 获取支付渠道,如果有更新
-        List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);
+        List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime);
         if (CollUtil.isEmpty(payChannels)) {
             return;
         }
@@ -78,8 +80,7 @@ public class PayChannelServiceImpl implements PayChannelService {
                 payChannel.getCode(), payChannel.getConfig()));
 
         // 写入缓存
-        assert payChannels.size() > 0; // 断言,避免告警
-        maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime);
         log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
     }
 

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

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

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

@@ -55,7 +55,7 @@ public class MenuController {
     }
 
     @GetMapping("/list")
-    @ApiOperation("获取菜单列表")
+    @ApiOperation(value = "获取菜单列表", notes = "用于【菜单管理】界面")
     @PreAuthorize("@ss.hasPermission('system:menu:query')")
     public CommonResult<List<MenuRespVO>> getMenus(MenuListReqVO reqVO) {
         List<MenuDO> list = menuService.getMenus(reqVO);
@@ -64,13 +64,13 @@ public class MenuController {
     }
 
     @GetMapping("/list-all-simple")
-    @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,主要用于前端的下拉选项")
+    @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,用于【角色分配菜单】功能的选项")
     public CommonResult<List<MenuSimpleRespVO>> getSimpleMenus() {
         // 获得菜单列表,只要开启状态的
         MenuListReqVO reqVO = new MenuListReqVO();
         reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
         List<MenuDO> list = menuService.getMenus(reqVO);
-        // 排序后,返回个诶前端
+        // 排序后,返回前端
         list.sort(Comparator.comparing(MenuDO::getSort));
         return success(MenuConvert.INSTANCE.convertList02(list));
     }

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

@@ -37,7 +37,7 @@ public class PermissionController {
     @GetMapping("/list-role-resources")
 //    @RequiresPermissions("system:permission:assign-role-menu")
     public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
-        return success(permissionService.listRoleMenuIds(roleId));
+        return success(permissionService.getRoleMenuIds(roleId));
     }
 
     @PostMapping("/assign-role-menu")

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

@@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotNull;
-import java.util.List;
+import java.util.Set;
 
 /**
 * 租户套餐 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -26,6 +26,6 @@ public class TenantPackageBaseVO {
 
     @ApiModelProperty(value = "关联的菜单编号", required = true)
     @NotNull(message = "关联的菜单编号不能为空")
-    private List<Long> menuIds;
+    private Set<Long> menuIds;
 
 }

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

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.permission;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
 import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -21,7 +21,7 @@ import java.util.Set;
 @TableName(value = "system_role", autoResultMap = true)
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class RoleDO extends BaseDO {
+public class RoleDO extends TenantBaseDO {
 
     /**
      * 角色ID

+ 2 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleMenuDO.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.permission;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
@@ -14,7 +15,7 @@ import lombok.EqualsAndHashCode;
 @TableName("system_role_menu")
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class RoleMenuDO extends BaseDO {
+public class RoleMenuDO extends TenantBaseDO {
 
     /**
      * 自增主键

+ 2 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
@@ -41,7 +42,7 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
     }
 
     default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {
-        return selectList(new QueryWrapperX<RoleDO>().in("status", statuses));
+        return selectList(new LambdaQueryWrapperX<RoleDO>().inIfPresent(RoleDO::getStatus, statuses));
     }
 
     @InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户。原因:该方法被异步 task 调用,此时获取不到租户编号

+ 10 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java

@@ -7,7 +7,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -43,7 +45,14 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
     }
 
     default Integer selectCountByPackageId(Long packageId) {
-        return selectCount("package_id", packageId);
+        return selectCount(TenantDO::getPackageId, packageId);
     }
 
+    default List<TenantDO> selectListByPackageId(Long packageId) {
+        return selectList(TenantDO::getPackageId, packageId);
+    }
+
+    @Select("SELECT id FROM system_tenant WHERE update_time > #{maxUpdateTime} LIMIT 1")
+    Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
+
 }

+ 7 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java

@@ -10,12 +10,17 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum RoleCodeEnum {
 
-    ADMIN("admin"), // 超级管理员
+    SUPER_ADMIN("super_admin", "超级管理员"),
+    TENANT_ADMIN("tenant_admin", "租户管理员"),
     ;
 
     /**
      * 角色编码
      */
-    private final String key;
+    private final String code;
+    /**
+     * 名字
+     */
+    private final String name;
 
 }

+ 0 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsSendConsumer.java

@@ -12,7 +12,6 @@ import javax.annotation.Resource;
  * 针对 {@link SmsSendMessage} 的消费者
  *
  * @author zzf
- * @date 2021/3/9 16:35
  */
 @Component
 @Slf4j

+ 29 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.mq.consumer.tenant;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
+import cn.iocoder.yudao.module.system.service.tenant.TenantService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage} 的消费者
+ *
+ * @author 芋道源码
+ */
+@Component
+@Slf4j
+public class TenantRefreshConsumer extends AbstractChannelMessageListener<TenantRefreshMessage> {
+
+    @Resource
+    private TenantService tenantService;
+
+    @Override
+    public void onMessage(TenantRefreshMessage message) {
+        log.info("[onMessage][收到 Tenant 刷新消息]");
+        tenantService.initLocalCache();
+    }
+
+}

+ 21 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.mq.message.tenant;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 租户数据刷新 Message
+ *
+ * @author 芋道源码
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantRefreshMessage extends AbstractChannelMessage {
+
+    @Override
+    public String getChannel() {
+        return "system.tenant.refresh";
+    }
+
+}

+ 29 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.mq.producer.tenant;
+
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
+import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
+import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * Tenant 租户相关消息的 Producer
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TenantProducer {
+
+    @Resource
+    private RedisMQTemplate redisMQTemplate;
+
+    /**
+     * 发送 {@link RoleRefreshMessage} 消息
+     */
+    public void sendTenantRefreshMessage() {
+        TenantRefreshMessage message = new TenantRefreshMessage();
+        redisMQTemplate.send(message);
+    }
+
+}

+ 6 - 6
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java

@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
@@ -73,9 +73,10 @@ public class DeptServiceImpl implements DeptService {
 
     @Override
     @PostConstruct
+    @TenantIgnore // 初始化缓存,无需租户过滤
     public synchronized void initLocalCache() {
         // 获取部门列表,如果有更新
-        List<DeptDO> deptList = this.loadDeptIfUpdate(maxUpdateTime);
+        List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime);
         if (CollUtil.isEmpty(deptList)) {
             return;
         }
@@ -90,8 +91,7 @@ public class DeptServiceImpl implements DeptService {
         // 设置缓存
         deptCache = builder.build();
         parentDeptCache = parentBuilder.build();
-        assert deptList.size() > 0; // 断言,避免告警
-        maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime);
         log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
     }
 
@@ -107,7 +107,7 @@ public class DeptServiceImpl implements DeptService {
      * @param maxUpdateTime 当前部门的最大更新时间
      * @return 部门列表
      */
-    private List<DeptDO> loadDeptIfUpdate(Date maxUpdateTime) {
+    protected List<DeptDO> loadDeptIfUpdate(Date maxUpdateTime) {
         // 第一步,判断是否要更新。
         if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
             log.info("[loadMenuIfUpdate][首次加载全量部门]");
@@ -118,7 +118,7 @@ public class DeptServiceImpl implements DeptService {
             log.info("[loadMenuIfUpdate][增量加载全量部门]");
         }
         // 第二步,如果有更新,则从数据库加载所有部门
-        return deptMapper.selectListIgnoreTenant();
+        return deptMapper.selectList();
     }
 
     @Override

+ 2 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java

@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.system.service.dict;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
@@ -99,8 +99,7 @@ public class DictDataServiceImpl implements DictDataService {
         });
         labelDictDataCache = labelDictDataBuilder.build();
         valueDictDataCache = valueDictDataBuilder.build();
-        assert dataList.size() > 0; // 断言,避免告警
-        maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(dataList, DictDataDO::getUpdateTime);
         log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size());
     }
 

+ 3 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java

@@ -66,7 +66,7 @@ public interface MenuService {
      * @param menusStatuses 菜单状态数组
      * @return 菜单列表
      */
-    List<MenuDO> listMenusFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
+    List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
 
     /**
      * 获得指定编号的菜单数组,从缓存中
@@ -78,8 +78,8 @@ public interface MenuService {
      * @param menusStatuses 菜单状态数组
      * @return 菜单数组
      */
-    List<MenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
-                                    Collection<Integer> menusStatuses);
+    List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
+                                      Collection<Integer> menusStatuses);
 
     /**
      * 获得权限对应的菜单数组

+ 5 - 7
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.service.permission;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO;
@@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
 import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
@@ -95,8 +94,7 @@ public class MenuServiceImpl implements MenuService {
         });
         menuCache = menuCacheBuilder.build();
         permissionMenuCache = permMenuCacheBuilder.build();
-        assert menuList.size() > 0; // 断言,避免告警
-        maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
         log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
     }
 
@@ -201,7 +199,7 @@ public class MenuServiceImpl implements MenuService {
     }
 
     @Override
-    public List<MenuDO> listMenusFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
+    public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
         // 任一一个参数为空,则返回空
         if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) {
             return Collections.emptyList();
@@ -213,8 +211,8 @@ public class MenuServiceImpl implements MenuService {
     }
 
     @Override
-    public List<MenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
-                                           Collection<Integer> menusStatuses) {
+    public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
+                                             Collection<Integer> menusStatuses) {
         // 任一一个参数为空,则返回空
         if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
             return Collections.emptyList();

+ 3 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java

@@ -33,8 +33,8 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
      * @param menusStatuses 菜单状态数组
      * @return 菜单列表
      */
-    List<MenuDO> getRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
-                                       Collection<Integer> menusStatuses);
+    List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
+                                          Collection<Integer> menusStatuses);
 
     /**
      * 获得用户拥有的角色编号集合
@@ -51,7 +51,7 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
      * @param roleId 角色编号
      * @return 菜单编号集合
      */
-    Set<Long> listRoleMenuIds(Long roleId);
+    Set<Long> getRoleMenuIds(Long roleId);
 
     /**
      * 设置角色菜单

+ 25 - 24
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java

@@ -3,6 +3,14 @@ package cn.iocoder.yudao.module.system.service.permission;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
@@ -12,13 +20,6 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
 import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
-import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
@@ -94,10 +95,10 @@ public class PermissionServiceImpl implements PermissionService {
      */
     @Override
     @PostConstruct
+    @TenantIgnore // 初始化缓存,无需租户过滤
     public void initLocalCache() {
-        Date now = new Date();
         // 获取角色与菜单的关联列表,如果有更新
-        List<RoleMenuDO> roleMenuList = this.loadRoleMenuIfUpdate(maxUpdateTime);
+        List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(maxUpdateTime);
         if (CollUtil.isEmpty(roleMenuList)) {
             return;
         }
@@ -111,8 +112,7 @@ public class PermissionServiceImpl implements PermissionService {
         });
         roleMenuCache = roleMenuCacheBuilder.build();
         menuRoleCache = menuRoleCacheBuilder.build();
-        assert roleMenuList.size() > 0; // 断言,避免告警
-        maxUpdateTime = now;
+        maxUpdateTime = CollectionUtils.getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
         log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
     }
 
@@ -128,7 +128,7 @@ public class PermissionServiceImpl implements PermissionService {
      * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
      * @return 角色与菜单的关联列表
      */
-    private List<RoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) {
+    protected List<RoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) {
         // 第一步,判断是否要更新。
         if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
             log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
@@ -143,21 +143,22 @@ public class PermissionServiceImpl implements PermissionService {
     }
 
     @Override
-    public List<MenuDO> getRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
-                                              Collection<Integer> menusStatuses) {
+    public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
+                                                 Collection<Integer> menusStatuses) {
         // 任一一个参数为空时,不返回任何菜单
         if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) {
             return Collections.emptyList();
         }
-        // 判断角色是否包含管理员
+
+        // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
         List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);
-        boolean hasAdmin = roleService.hasAnyAdmin(roleList);
-        // 获得角色拥有的菜单关联
-        if (hasAdmin) { // 管理员,获取到全部
-            return menuService.listMenusFromCache(menuTypes, menusStatuses);
+        if (roleService.hasAnySuperAdmin(roleList)) {
+            return menuService.getMenuListFromCache(menuTypes, menusStatuses);
         }
+
+        // 获得角色拥有的菜单关联
         List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);
-        return menuService.listMenusFromCache(menuIds, menuTypes, menusStatuses);
+        return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
     }
 
     @Override
@@ -174,10 +175,10 @@ public class PermissionServiceImpl implements PermissionService {
     }
 
     @Override
-    public Set<Long> listRoleMenuIds(Long roleId) {
+    public Set<Long> getRoleMenuIds(Long roleId) {
         // 如果是管理员的情况下,获取全部菜单编号
         RoleDO role = roleService.getRole(roleId);
-        if (roleService.hasAnyAdmin(Collections.singletonList(role))) {
+        if (roleService.hasAnySuperAdmin(Collections.singletonList(role))) {
             return CollectionUtils.convertSet(menuService.getMenus(), MenuDO::getId);
         }
         // 如果是非管理员的情况下,获得拥有的菜单编号
@@ -302,7 +303,7 @@ public class PermissionServiceImpl implements PermissionService {
             return false;
         }
         // 判断是否是超管。如果是,当然符合条件
-        if (roleService.hasAnyAdmin(roleIds)) {
+        if (roleService.hasAnySuperAdmin(roleIds)) {
             return true;
         }
 
@@ -337,7 +338,7 @@ public class PermissionServiceImpl implements PermissionService {
             return false;
         }
         // 判断是否是超管。如果是,当然符合条件
-        if (roleService.hasAnyAdmin(roleIds)) {
+        if (roleService.hasAnySuperAdmin(roleIds)) {
             return true;
         }
         Set<String> userRoles = CollectionUtils.convertSet(roleService.getRolesFromCache(roleIds),

+ 4 - 4
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java

@@ -90,12 +90,12 @@ public interface RoleService {
     List<RoleDO> getRolesFromCache(Collection<Long> ids);
 
     /**
-     * 判断角色数组中,是否有管理员
+     * 判断角色数组中,是否有超级管理员
      *
      * @param roleList 角色数组
      * @return 是否有管理员
      */
-    boolean hasAnyAdmin(Collection<RoleDO> roleList);
+    boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
 
     /**
      * 判断角色编号数组中,是否有管理员
@@ -103,8 +103,8 @@ public interface RoleService {
      * @param ids 角色编号数组
      * @return 是否有管理员
      */
-    default boolean hasAnyAdmin(Set<Long> ids) {
-        return hasAnyAdmin(getRolesFromCache(ids));
+    default boolean hasAnySuperAdmin(Set<Long> ids) {
+        return hasAnySuperAdmin(getRolesFromCache(ids));
     }
 
     /**

+ 7 - 10
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java

@@ -6,8 +6,8 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
@@ -19,7 +19,6 @@ import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.lang.Nullable;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -78,19 +77,17 @@ public class RoleServiceImpl implements RoleService {
      */
     @Override
     @PostConstruct
+    @TenantIgnore // 忽略自动多租户,全局初始化缓存
     public void initLocalCache() {
         // 获取角色列表,如果有更新
-        List<RoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime);
+        List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime);
         if (CollUtil.isEmpty(roleList)) {
             return;
         }
 
         // 写入缓存
-        ImmutableMap.Builder<Long, RoleDO> builder = ImmutableMap.builder();
-        roleList.forEach(sysRoleDO -> builder.put(sysRoleDO.getId(), sysRoleDO));
-        roleCache = builder.build();
-        assert roleList.size() > 0; // 断言,避免告警
-        maxUpdateTime = roleList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
+        maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
         log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
     }
 
@@ -216,11 +213,11 @@ public class RoleServiceImpl implements RoleService {
     }
 
     @Override
-    public boolean hasAnyAdmin(Collection<RoleDO> roleList) {
+    public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) {
         if (CollectionUtil.isEmpty(roleList)) {
             return false;
         }
-        return roleList.stream().anyMatch(roleDO -> RoleCodeEnum.ADMIN.getKey().equals(roleDO.getCode()));
+        return roleList.stream().anyMatch(roleDO -> RoleCodeEnum.SUPER_ADMIN.getCode().equals(roleDO.getCode()));
     }
 
     @Override

+ 6 - 8
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java

@@ -1,6 +1,10 @@
 package cn.iocoder.yudao.module.system.service.sms;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
+import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
@@ -8,10 +12,6 @@ import cn.iocoder.yudao.module.system.convert.sms.SmsChannelConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
 import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
-import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -19,13 +19,12 @@ import org.springframework.stereotype.Service;
 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 
 /**
  * 短信渠道Service实现类
@@ -74,8 +73,7 @@ public class SmsChannelServiceImpl implements SmsChannelService {
         propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
 
         // 写入缓存
-        assert smsChannels.size() > 0; // 断言,避免告警
-        maxUpdateTime = smsChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime);
         log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
     }
 

+ 11 - 15
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java

@@ -3,24 +3,23 @@ package cn.iocoder.yudao.module.system.service.sms;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
+import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
+import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
 import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
-import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
-import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
-import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
-import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -31,8 +30,8 @@ import javax.annotation.Resource;
 import java.util.*;
 import java.util.regex.Pattern;
 
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
 /**
  * 短信模板 Service 实现类
@@ -89,11 +88,8 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
         }
 
         // 写入缓存
-        ImmutableMap.Builder<String, SmsTemplateDO> builder = ImmutableMap.builder();
-        smsTemplateList.forEach(sysSmsTemplateDO -> builder.put(sysSmsTemplateDO.getCode(), sysSmsTemplateDO));
-        smsTemplateCache = builder.build();
-        assert smsTemplateList.size() > 0; // 断言,避免告警
-        maxUpdateTime = smsTemplateList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
+        maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime);
         log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
     }
 

+ 14 - 3
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java

@@ -1,15 +1,18 @@
 package cn.iocoder.yudao.module.system.service.tenant;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
@@ -45,12 +48,18 @@ public class TenantPackageServiceImpl implements TenantPackageService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) {
         // 校验存在
-        this.validateTenantPackageExists(updateReqVO.getId());
+        TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
         // 更新
         TenantPackageDO updateObj = TenantPackageConvert.INSTANCE.convert(updateReqVO);
         tenantPackageMapper.updateById(updateObj);
+        // 如果菜单发生变化,则修改每个租户的菜单
+        if (!CollUtil.isEqualList(tenantPackage.getMenuIds(), updateReqVO.getMenuIds())) {
+            List<TenantDO> tenants = tenantService.getTenantListByPackageId(tenantPackage.getId());
+            tenants.forEach(tenant -> tenantService.updateTenantRoleMenu(tenant.getId(), updateReqVO.getMenuIds()));
+        }
     }
 
     @Override
@@ -63,10 +72,12 @@ public class TenantPackageServiceImpl implements TenantPackageService {
         tenantPackageMapper.deleteById(id);
     }
 
-    private void validateTenantPackageExists(Long id) {
-        if (tenantPackageMapper.selectById(id) == null) {
+    private TenantPackageDO validateTenantPackageExists(Long id) {
+        TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);
+        if (tenantPackage == null) {
             throw exception(TENANT_PACKAGE_NOT_EXISTS);
         }
+        return tenantPackage;
     }
 
     private void validateTenantUsed(Long id) {

+ 22 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java

@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
  * 租户 Service 接口
@@ -19,6 +20,11 @@ import java.util.List;
  */
 public interface TenantService extends TenantFrameworkService {
 
+    /**
+     * 初始化租户的本地缓存
+     */
+    void initLocalCache();
+
     /**
      * 创建租户
      *
@@ -34,6 +40,14 @@ public interface TenantService extends TenantFrameworkService {
      */
     void updateTenant(@Valid TenantUpdateReqVO updateReqVO);
 
+    /**
+     * 更新租户的角色菜单
+     *
+     * @param tenantId 租户编号
+     * @param menuIds 菜单编号数组
+     */
+    void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds);
+
     /**
      * 删除租户
      *
@@ -89,4 +103,12 @@ public interface TenantService extends TenantFrameworkService {
      */
     Integer getTenantCountByPackageId(Long packageId);
 
+    /**
+     * 获得使用指定套餐的租户数组
+     *
+     * @param packageId 租户套餐编号
+     * @return 租户数组
+     */
+    List<TenantDO> getTenantListByPackageId(Long packageId);
+
 }

+ 147 - 18
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -1,5 +1,8 @@
 package cn.iocoder.yudao.module.system.service.tenant;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -11,21 +14,27 @@ import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantEx
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
+import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@@ -37,8 +46,27 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
  */
 @Service
 @Validated
+@Slf4j
 public class TenantServiceImpl implements TenantService {
 
+    /**
+     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
+     * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
+     */
+    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
+
+    /**
+     * 角色缓存
+     * key:角色编号 {@link RoleDO#getId()}
+     *
+     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
+     */
+    private volatile Map<Long, TenantDO> tenantCache;
+    /**
+     * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
+     */
+    private volatile Date maxUpdateTime;
+
     @Resource
     private TenantMapper tenantMapper;
 
@@ -51,15 +79,61 @@ public class TenantServiceImpl implements TenantService {
     @Resource
     private PermissionService permissionService;
 
+    @Resource
+    private TenantProducer tenantProducer;
+
+    /**
+     * 初始化 {@link #tenantCache} 缓存
+     */
+    @Override
+    @PostConstruct
+    public void initLocalCache() {
+        // 获取租户列表,如果有更新
+        List<TenantDO> tenantList = loadTenantIfUpdate(maxUpdateTime);
+        if (CollUtil.isEmpty(tenantList)) {
+            return;
+        }
+
+        // 写入缓存
+        tenantCache = CollectionUtils.convertImmutableMap(tenantList, TenantDO::getId);
+        maxUpdateTime = CollectionUtils.getMaxValue(tenantList, TenantDO::getUpdateTime);
+        log.info("[initLocalCache][初始化 Tenant 数量为 {}]", tenantList.size());
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void schedulePeriodicRefresh() {
+        initLocalCache();
+    }
+
+    /**
+     * 如果租户发生变化,从数据库中获取最新的全量租户。
+     * 如果未发生变化,则返回空
+     *
+     * @param maxUpdateTime 当前租户的最大更新时间
+     * @return 租户列表
+     */
+    private List<TenantDO> loadTenantIfUpdate(Date maxUpdateTime) {
+        // 第一步,判断是否要更新。
+        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
+            log.info("[loadTenantIfUpdate][首次加载全量租户]");
+        } else { // 判断数据库中是否有更新的租户
+            if (tenantMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
+                return null;
+            }
+            log.info("[loadTenantIfUpdate][增量加载全量租户]");
+        }
+        // 第二步,如果有更新,则从数据库加载所有租户
+        return tenantMapper.selectList();
+    }
+
     @Override
     public List<Long> getTenantIds() {
-        List<TenantDO> tenants = tenantMapper.selectList();
-        return CollectionUtils.convertList(tenants, TenantDO::getId);
+        return new ArrayList<>(tenantCache.keySet());
     }
 
     @Override
     public void validTenant(Long id) {
-        TenantDO tenant = tenantMapper.selectById(id);
+        TenantDO tenant = tenantCache.get(id);
         if (tenant == null) {
             throw exception(TENANT_NOT_EXISTS);
         }
@@ -75,7 +149,7 @@ public class TenantServiceImpl implements TenantService {
     @Transactional(rollbackFor = Exception.class)
     public Long createTenant(TenantCreateReqVO createReqVO) {
         // 校验套餐被禁用
-        tenantPackageService.validTenantPackage(createReqVO.getPackageId());
+        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
 
         // 创建租户
         TenantDO tenant = TenantConvert.INSTANCE.convert(createReqVO);
@@ -83,13 +157,19 @@ public class TenantServiceImpl implements TenantService {
 
         TenantUtils.execute(tenant.getId(), () -> {
             // 创建角色
-            Long roleId = createRole();
+            Long roleId = createRole(tenantPackage);
             // 创建用户,并分配角色
             Long userId = createUser(roleId, createReqVO);
             // 修改租户的管理员
             tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
         });
-        // 返回
+        // 发送刷新消息
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCommit() {
+                tenantProducer.sendTenantRefreshMessage();
+            }
+        });
         return tenant.getId();
     }
 
@@ -101,36 +181,80 @@ public class TenantServiceImpl implements TenantService {
         return userId;
     }
 
-    private Long createRole() {
+    private Long createRole(TenantPackageDO tenantPackage) {
+        // 创建角色
         RoleCreateReqVO reqVO = new RoleCreateReqVO();
-        reqVO.setName(RoleCodeEnum.ADMIN.name()).setCode(RoleCodeEnum.ADMIN.getKey()).setSort(0);
-        return roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
+        reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
+                .setSort(0).setRemark("系统自动生成");
+        Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
+        // 分配权限
+        permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds());
+        return roleId;
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateTenant(TenantUpdateReqVO updateReqVO) {
         // 校验存在
-        this.validateTenantExists(updateReqVO.getId());
+        TenantDO tenant = validateTenantExists(updateReqVO.getId());
         // 校验套餐被禁用
-        tenantPackageService.validTenantPackage(updateReqVO.getPackageId());
+        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId());
 
-        // 更新
+        // 更新租户
         TenantDO updateObj = TenantConvert.INSTANCE.convert(updateReqVO);
         tenantMapper.updateById(updateObj);
+        // 如果套餐发生变化,则修改其角色的权限
+        if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {
+            updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
+        }
+        // 发送刷新消息
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCommit() {
+                tenantProducer.sendTenantRefreshMessage();
+            }
+        });
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
+        TenantUtils.execute(tenantId, () -> {
+            // 获得所有角色
+            List<RoleDO> roles = roleService.getRoles(null);
+            roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配",
+                    role.getId(), role.getTenantId(), tenantId)); // 兜底校验
+            // 重新分配每个角色的权限
+            roles.forEach(role -> {
+                // 如果是租户管理员,重新分配其权限为租户套餐的权限
+                if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) {
+                    permissionService.assignRoleMenu(role.getId(), menuIds);
+                    log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds);
+                    return;
+                }
+                // 如果是其他角色,则去掉超过套餐的权限
+                Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId());
+                roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
+                permissionService.assignRoleMenu(role.getId(), roleMenuIds);
+                log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
+            });
+        });
     }
 
     @Override
     public void deleteTenant(Long id) {
         // 校验存在
-        this.validateTenantExists(id);
+        validateTenantExists(id);
         // 删除
         tenantMapper.deleteById(id);
     }
 
-    private void validateTenantExists(Long id) {
-        if (tenantMapper.selectById(id) == null) {
+    private TenantDO validateTenantExists(Long id) {
+        TenantDO tenant = tenantMapper.selectById(id);
+        if (tenant == null) {
             throw exception(TENANT_NOT_EXISTS);
         }
+        return tenant;
     }
 
     @Override
@@ -163,4 +287,9 @@ public class TenantServiceImpl implements TenantService {
         return tenantMapper.selectCountByPackageId(packageId);
     }
 
+    @Override
+    public List<TenantDO> getTenantListByPackageId(Long packageId) {
+        return tenantMapper.selectListByPackageId(packageId);
+    }
+
 }

+ 2 - 2
yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java

@@ -243,7 +243,7 @@ public class MenuServiceTest extends BaseDbUnitTest {
         menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2);
         mockCacheMap.put(menuDO.getId(), menuDO);
 
-        List<MenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(MenuTypeEnum.MENU.getType()),
+        List<MenuDO> menuDOS = sysMenuService.getMenuListFromCache(Collections.singletonList(MenuTypeEnum.MENU.getType()),
                 Collections.singletonList(CommonStatusEnum.DISABLE.getStatus()));
         assertEquals(menuDOS.size(), idMenuMap.size());
         menuDOS.forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
@@ -270,7 +270,7 @@ public class MenuServiceTest extends BaseDbUnitTest {
         menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2);
         mockCacheMap.put(menuDO.getId(), menuDO);
 
-        List<MenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(1L),
+        List<MenuDO> menuDOS = sysMenuService.getMenuListFromCache(Collections.singletonList(1L),
                 Collections.singletonList(MenuTypeEnum.MENU.getType()), Collections.singletonList(1));
         assertEquals(menuDOS.size(), idMenuMap.size());
         menuDOS.forEach(menu -> assertPojoEquals(idMenuMap.get(menu.getId()), menu));

+ 2 - 2
yudao-server/src/main/resources/application.yaml

@@ -79,8 +79,8 @@ yudao:
       - cn.iocoder.yudao.module.tool.enums.ErrorCodeConstants
   tenant: # 多租户相关配置项
     enable: true
-    ignore-urls: /admin-api/system/captcha/get-image, /admin-api/infra/file/get/*
-    ignore-tables: infra_config, infra_file, infra_job, infra_job_log, infra_job_log, system_tenant, system_tenant_package, system_dict_data, system_dict_type, system_error_code, system_menu, system_sms_channel, tool_codegen_column, tool_codegen_table, tool_test_demo, tables, columns
+    ignore-urls: /admin-api/system/tenant/get-id-by-name, /admin-api/system/captcha/get-image, /admin-api/infra/file/get/*
+    ignore-tables: infra_config, infra_file, infra_job, infra_job_log, infra_job_log, system_tenant, system_tenant_package, system_dict_data, system_dict_type, system_error_code, system_menu, system_sms_channel, system_sms_template, tool_codegen_column, tool_codegen_table, tool_test_demo, tables, columns
   sms-code: # 短信验证码相关的配置项
     expire-times: 10m
     send-frequency: 1m

+ 2 - 6
yudao-ui-admin/src/views/system/tenantPackage/index.vue

@@ -200,6 +200,8 @@ export default {
       this.reset();
       this.open = true;
       this.title = "添加租户套餐";
+      // 设置为非严格,继续使用半选中
+      this.menuCheckStrictly = false;
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
@@ -222,12 +224,6 @@ export default {
     /** 获得菜单 */
     getMenus() {
       listSimpleMenus().then(response => {
-        // 移除 BUTTON 类型,暂支只需要发 DIR、MENU 类型
-        for (let i = response.data.length -1; i >= 0 ; i--) {
-          if (response.data[i].type === SystemMenuTypeEnum.BUTTON) {
-            response.data.splice(i, 1);
-          }
-        }
         // 处理 menuOptions 参数
         this.menuOptions = [];
         // 只需要配置

+ 2 - 1
更新日志.md

@@ -29,8 +29,9 @@ TODO
 * 【新增】后端 `yudao.tenant.enable` 配置项,前端 `VUE_APP_TENANT_ENABLE` 配置项,用于开关租户功能。 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09)
 * 【优化】调整默认所有表开启多租户的特性,可通过 `yudao.tenant.ignore-tables` 配置项进行忽略,替代原本默认不开启的策略 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09)
 * 【新增】通过 `yudao.tenant.ignore-urls` 配置忽略多租户的请求,例如说 ,例如说短信回调、支付回调等 Open API [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09)
+* 【新增】新增 `@TenantIgnore` 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4d53944771c66b563da1e3d68d3ba43405af8a06)
 * 【新增】租户套餐的管理,可配置每个租户的可使用的功能权限 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/6b6d676a6baa2dad16ae9bf03d5002209064c8cc)
-* 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 []()
+* 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/2598c033a95d4b61d5f5ab3da5f1414f25c510d6)
 
 ### 🐞 Bug Fixes