1. 新建租户、修改租户、修改租户套餐时,自动修改角色的权限

2. 租户的本地缓存,提升访问性能
3. 精简本地缓存的实现逻辑
pull/2/head
YunaiV 2022-02-23 00:38:49 +08:00
parent 4d53944771
commit e4be51b14a
33 changed files with 405 additions and 139 deletions

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.ImmutableMap;
import java.util.*; import java.util.*;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
@ -125,6 +126,15 @@ public class CollectionUtils {
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet()))); 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) { public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates); return org.springframework.util.CollectionUtils.containsAny(source, candidates);
} }
@ -140,6 +150,15 @@ public class CollectionUtils {
return from.stream().filter(predicate).findFirst().orElse(null); 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) { public static <T> void addIfNotNull(Collection<T> coll, T item) {
if (item == null) { if (item == null) {
return; return;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List; import java.util.Set;
/** /**
* Base VO VO 使 * Base VO VO 使
@ -26,6 +26,6 @@ public class TenantPackageBaseVO {
@ApiModelProperty(value = "关联的菜单编号", required = true) @ApiModelProperty(value = "关联的菜单编号", required = true)
@NotNull(message = "关联的菜单编号不能为空") @NotNull(message = "关联的菜单编号不能为空")
private List<Long> menuIds; private Set<Long> menuIds;
} }

View File

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

View File

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

View File

@ -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.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 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.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.RoleExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO; 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) { 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 调用,此时获取不到租户编号 @InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户。原因:该方法被异步 task 调用,此时获取不到租户编号

View File

@ -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.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@ -43,7 +45,14 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
} }
default Integer selectCountByPackageId(Long packageId) { 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);
} }

View File

@ -10,12 +10,17 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum RoleCodeEnum { 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;
} }

View File

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

View File

@ -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();
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -66,7 +66,7 @@ public interface MenuService {
* @param menusStatuses * @param menusStatuses
* @return * @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 * @param menusStatuses
* @return * @return
*/ */
List<MenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
Collection<Integer> menusStatuses); Collection<Integer> menusStatuses);
/** /**
* *

View File

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

View File

@ -33,8 +33,8 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
* @param menusStatuses * @param menusStatuses
* @return * @return
*/ */
List<MenuDO> getRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
Collection<Integer> menusStatuses); Collection<Integer> menusStatuses);
/** /**
* *
@ -51,7 +51,7 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
* @param roleId * @param roleId
* @return * @return
*/ */
Set<Long> listRoleMenuIds(Long roleId); Set<Long> getRoleMenuIds(Long roleId);
/** /**
* *

View File

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

View File

@ -90,12 +90,12 @@ public interface RoleService {
List<RoleDO> getRolesFromCache(Collection<Long> ids); List<RoleDO> getRolesFromCache(Collection<Long> ids);
/** /**
* *
* *
* @param roleList * @param roleList
* @return * @return
*/ */
boolean hasAnyAdmin(Collection<RoleDO> roleList); boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
/** /**
* *
@ -103,8 +103,8 @@ public interface RoleService {
* @param ids * @param ids
* @return * @return
*/ */
default boolean hasAnyAdmin(Set<Long> ids) { default boolean hasAnySuperAdmin(Set<Long> ids) {
return hasAnyAdmin(getRolesFromCache(ids)); return hasAnySuperAdmin(getRolesFromCache(ids));
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -1,15 +1,18 @@
package cn.iocoder.yudao.module.system.service.tenant; 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.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.TenantPackageCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; 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.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO;
import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert; 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.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper; import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -45,12 +48,18 @@ public class TenantPackageServiceImpl implements TenantPackageService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) { public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) {
// 校验存在 // 校验存在
this.validateTenantPackageExists(updateReqVO.getId()); TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
// 更新 // 更新
TenantPackageDO updateObj = TenantPackageConvert.INSTANCE.convert(updateReqVO); TenantPackageDO updateObj = TenantPackageConvert.INSTANCE.convert(updateReqVO);
tenantPackageMapper.updateById(updateObj); 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 @Override
@ -63,10 +72,12 @@ public class TenantPackageServiceImpl implements TenantPackageService {
tenantPackageMapper.deleteById(id); tenantPackageMapper.deleteById(id);
} }
private void validateTenantPackageExists(Long id) { private TenantPackageDO validateTenantPackageExists(Long id) {
if (tenantPackageMapper.selectById(id) == null) { TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);
if (tenantPackage == null) {
throw exception(TENANT_PACKAGE_NOT_EXISTS); throw exception(TENANT_PACKAGE_NOT_EXISTS);
} }
return tenantPackage;
} }
private void validateTenantUsed(Long id) { private void validateTenantUsed(Long id) {

View File

@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Service * Service
@ -19,6 +20,11 @@ import java.util.List;
*/ */
public interface TenantService extends TenantFrameworkService { public interface TenantService extends TenantFrameworkService {
/**
*
*/
void initLocalCache();
/** /**
* *
* *
@ -34,6 +40,14 @@ public interface TenantService extends TenantFrameworkService {
*/ */
void updateTenant(@Valid TenantUpdateReqVO updateReqVO); 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); Integer getTenantCountByPackageId(Long packageId);
/**
* 使
*
* @param packageId
* @return
*/
List<TenantDO> getTenantListByPackageId(Long packageId);
} }

View File

@ -1,5 +1,8 @@
package cn.iocoder.yudao.module.system.service.tenant; 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.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 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.TenantPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO; 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.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.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.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; 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.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService; import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; 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 org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.*;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@ -37,8 +46,27 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
*/ */
@Service @Service
@Validated @Validated
@Slf4j
public class TenantServiceImpl implements TenantService { 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 @Resource
private TenantMapper tenantMapper; private TenantMapper tenantMapper;
@ -51,15 +79,61 @@ public class TenantServiceImpl implements TenantService {
@Resource @Resource
private PermissionService permissionService; 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 @Override
public List<Long> getTenantIds() { public List<Long> getTenantIds() {
List<TenantDO> tenants = tenantMapper.selectList(); return new ArrayList<>(tenantCache.keySet());
return CollectionUtils.convertList(tenants, TenantDO::getId);
} }
@Override @Override
public void validTenant(Long id) { public void validTenant(Long id) {
TenantDO tenant = tenantMapper.selectById(id); TenantDO tenant = tenantCache.get(id);
if (tenant == null) { if (tenant == null) {
throw exception(TENANT_NOT_EXISTS); throw exception(TENANT_NOT_EXISTS);
} }
@ -75,7 +149,7 @@ public class TenantServiceImpl implements TenantService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Long createTenant(TenantCreateReqVO createReqVO) { public Long createTenant(TenantCreateReqVO createReqVO) {
// 校验套餐被禁用 // 校验套餐被禁用
tenantPackageService.validTenantPackage(createReqVO.getPackageId()); TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
// 创建租户 // 创建租户
TenantDO tenant = TenantConvert.INSTANCE.convert(createReqVO); TenantDO tenant = TenantConvert.INSTANCE.convert(createReqVO);
@ -83,13 +157,19 @@ public class TenantServiceImpl implements TenantService {
TenantUtils.execute(tenant.getId(), () -> { TenantUtils.execute(tenant.getId(), () -> {
// 创建角色 // 创建角色
Long roleId = createRole(); Long roleId = createRole(tenantPackage);
// 创建用户,并分配角色 // 创建用户,并分配角色
Long userId = createUser(roleId, createReqVO); Long userId = createUser(roleId, createReqVO);
// 修改租户的管理员 // 修改租户的管理员
tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId)); tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
}); });
// 返回 // 发送刷新消息
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
tenantProducer.sendTenantRefreshMessage();
}
});
return tenant.getId(); return tenant.getId();
} }
@ -101,36 +181,80 @@ public class TenantServiceImpl implements TenantService {
return userId; return userId;
} }
private Long createRole() { private Long createRole(TenantPackageDO tenantPackage) {
// 创建角色
RoleCreateReqVO reqVO = new RoleCreateReqVO(); RoleCreateReqVO reqVO = new RoleCreateReqVO();
reqVO.setName(RoleCodeEnum.ADMIN.name()).setCode(RoleCodeEnum.ADMIN.getKey()).setSort(0); reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
return roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType()); .setSort(0).setRemark("系统自动生成");
Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
// 分配权限
permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds());
return roleId;
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateTenant(TenantUpdateReqVO updateReqVO) { 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); TenantDO updateObj = TenantConvert.INSTANCE.convert(updateReqVO);
tenantMapper.updateById(updateObj); 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 @Override
public void deleteTenant(Long id) { public void deleteTenant(Long id) {
// 校验存在 // 校验存在
this.validateTenantExists(id); validateTenantExists(id);
// 删除 // 删除
tenantMapper.deleteById(id); tenantMapper.deleteById(id);
} }
private void validateTenantExists(Long id) { private TenantDO validateTenantExists(Long id) {
if (tenantMapper.selectById(id) == null) { TenantDO tenant = tenantMapper.selectById(id);
if (tenant == null) {
throw exception(TENANT_NOT_EXISTS); throw exception(TENANT_NOT_EXISTS);
} }
return tenant;
} }
@Override @Override
@ -163,4 +287,9 @@ public class TenantServiceImpl implements TenantService {
return tenantMapper.selectCountByPackageId(packageId); return tenantMapper.selectCountByPackageId(packageId);
} }
@Override
public List<TenantDO> getTenantListByPackageId(Long packageId) {
return tenantMapper.selectListByPackageId(packageId);
}
} }

View File

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

View File

@ -79,8 +79,8 @@ yudao:
- cn.iocoder.yudao.module.tool.enums.ErrorCodeConstants - cn.iocoder.yudao.module.tool.enums.ErrorCodeConstants
tenant: # 多租户相关配置项 tenant: # 多租户相关配置项
enable: true enable: true
ignore-urls: /admin-api/system/captcha/get-image, /admin-api/infra/file/get/* 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, tool_codegen_column, tool_codegen_table, tool_test_demo, tables, columns 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: # 短信验证码相关的配置项 sms-code: # 短信验证码相关的配置项
expire-times: 10m expire-times: 10m
send-frequency: 1m send-frequency: 1m

View File

@ -200,6 +200,8 @@ export default {
this.reset(); this.reset();
this.open = true; this.open = true;
this.title = "添加租户套餐"; this.title = "添加租户套餐";
// 使
this.menuCheckStrictly = false;
}, },
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
@ -222,12 +224,6 @@ export default {
/** 获得菜单 */ /** 获得菜单 */
getMenus() { getMenus() {
listSimpleMenus().then(response => { listSimpleMenus().then(response => {
// BUTTON DIRMENU
for (let i = response.data.length -1; i >= 0 ; i--) {
if (response.data[i].type === SystemMenuTypeEnum.BUTTON) {
response.data.splice(i, 1);
}
}
// menuOptions // menuOptions
this.menuOptions = []; this.menuOptions = [];
// //

View File

@ -29,8 +29,9 @@ TODO
* 【新增】后端 `yudao.tenant.enable` 配置项,前端 `VUE_APP_TENANT_ENABLE` 配置项,用于开关租户功能。 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09) * 【新增】后端 `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-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) * 【新增】通过 `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/6b6d676a6baa2dad16ae9bf03d5002209064c8cc)
* 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 []() * 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/2598c033a95d4b61d5f5ab3da5f1414f25c510d6)
### 🐞 Bug Fixes ### 🐞 Bug Fixes