Commit e4be51b14adfeba6d31424a30976d8729d441b9e
1 parent
4d539447
1. 新建租户、修改租户、修改租户套餐时,自动修改角色的权限
2. 租户的本地缓存,提升访问性能 3. 精简本地缓存的实现逻辑
Showing
33 changed files
with
405 additions
and
139 deletions
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; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.collection.CollectionUtil; |
| 5 | +import com.google.common.collect.ImmutableMap; | |
| 5 | 6 | |
| 6 | 7 | import java.util.*; |
| 7 | 8 | import java.util.function.BinaryOperator; |
| ... | ... | @@ -125,6 +126,15 @@ public class CollectionUtils { |
| 125 | 126 | return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet()))); |
| 126 | 127 | } |
| 127 | 128 | |
| 129 | + public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) { | |
| 130 | + if (CollUtil.isEmpty(from)) { | |
| 131 | + return Collections.emptyMap(); | |
| 132 | + } | |
| 133 | + ImmutableMap.Builder<K, T> builder = ImmutableMap.builder(); | |
| 134 | + from.forEach(item -> builder.put(keyFunc.apply(item), item)); | |
| 135 | + return builder.build(); | |
| 136 | + } | |
| 137 | + | |
| 128 | 138 | public static boolean containsAny(Collection<?> source, Collection<?> candidates) { |
| 129 | 139 | return org.springframework.util.CollectionUtils.containsAny(source, candidates); |
| 130 | 140 | } |
| ... | ... | @@ -140,6 +150,15 @@ public class CollectionUtils { |
| 140 | 150 | return from.stream().filter(predicate).findFirst().orElse(null); |
| 141 | 151 | } |
| 142 | 152 | |
| 153 | + public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) { | |
| 154 | + if (CollUtil.isEmpty(from)) { | |
| 155 | + return null; | |
| 156 | + } | |
| 157 | + assert from.size() > 0; // 断言,避免告警 | |
| 158 | + T t = from.stream().max(Comparator.comparing(valueFunc)).get(); | |
| 159 | + return valueFunc.apply(t); | |
| 160 | + } | |
| 161 | + | |
| 143 | 162 | public static <T> void addIfNotNull(Collection<T> coll, T item) { |
| 144 | 163 | if (item == null) { |
| 145 | 164 | return; | ... | ... |
yudao-module-pay/yudao-module-pay-impl/pom.xml
| ... | ... | @@ -33,6 +33,10 @@ |
| 33 | 33 | <groupId>cn.iocoder.boot</groupId> |
| 34 | 34 | <artifactId>yudao-spring-boot-starter-biz-pay</artifactId> |
| 35 | 35 | </dependency> |
| 36 | + <dependency> | |
| 37 | + <groupId>cn.iocoder.boot</groupId> | |
| 38 | + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | |
| 39 | + </dependency> | |
| 36 | 40 | |
| 37 | 41 | <!-- Web 相关 --> |
| 38 | 42 | <dependency> | ... | ... |
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; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 9 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 10 | +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | |
| 10 | 11 | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; |
| 12 | +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | |
| 13 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | |
| 11 | 14 | import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO; |
| 12 | 15 | import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO; |
| 13 | 16 | import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO; |
| 14 | 17 | import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelUpdateReqVO; |
| 15 | 18 | import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; |
| 16 | -import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | |
| 17 | -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | |
| 18 | 19 | import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO; |
| 19 | 20 | import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper; |
| 20 | 21 | import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; |
| ... | ... | @@ -27,12 +28,12 @@ import javax.annotation.PostConstruct; |
| 27 | 28 | import javax.annotation.Resource; |
| 28 | 29 | import javax.validation.Validator; |
| 29 | 30 | import java.util.Collection; |
| 30 | -import java.util.Comparator; | |
| 31 | 31 | import java.util.Date; |
| 32 | 32 | import java.util.List; |
| 33 | 33 | |
| 34 | 34 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 35 | -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; | |
| 35 | +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR; | |
| 36 | +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS; | |
| 36 | 37 | |
| 37 | 38 | /** |
| 38 | 39 | * 支付渠道 Service 实现类 |
| ... | ... | @@ -66,9 +67,10 @@ public class PayChannelServiceImpl implements PayChannelService { |
| 66 | 67 | |
| 67 | 68 | @Override |
| 68 | 69 | @PostConstruct |
| 70 | + @TenantIgnore // 忽略自动化租户,全局初始化本地缓存 | |
| 69 | 71 | public void initPayClients() { |
| 70 | 72 | // 获取支付渠道,如果有更新 |
| 71 | - List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime); | |
| 73 | + List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime); | |
| 72 | 74 | if (CollUtil.isEmpty(payChannels)) { |
| 73 | 75 | return; |
| 74 | 76 | } |
| ... | ... | @@ -78,8 +80,7 @@ public class PayChannelServiceImpl implements PayChannelService { |
| 78 | 80 | payChannel.getCode(), payChannel.getConfig())); |
| 79 | 81 | |
| 80 | 82 | // 写入缓存 |
| 81 | - assert payChannels.size() > 0; // 断言,避免告警 | |
| 82 | - maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 83 | + maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime); | |
| 83 | 84 | log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size()); |
| 84 | 85 | } |
| 85 | 86 | ... | ... |
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 { |
| 72 | 72 | // 获得角色列表 |
| 73 | 73 | List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds()); |
| 74 | 74 | // 获得菜单列表 |
| 75 | - List<MenuDO> menuList = permissionService.getRoleMenusFromCache( | |
| 75 | + List<MenuDO> menuList = permissionService.getRoleMenuListFromCache( | |
| 76 | 76 | getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 |
| 77 | 77 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), |
| 78 | 78 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); |
| ... | ... | @@ -84,7 +84,7 @@ public class AuthController { |
| 84 | 84 | @ApiOperation("获得登录用户的菜单列表") |
| 85 | 85 | public CommonResult<List<AuthMenuRespVO>> getMenus() { |
| 86 | 86 | // 获得用户拥有的菜单列表 |
| 87 | - List<MenuDO> menuList = permissionService.getRoleMenusFromCache( | |
| 87 | + List<MenuDO> menuList = permissionService.getRoleMenuListFromCache( | |
| 88 | 88 | getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 |
| 89 | 89 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 |
| 90 | 90 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 | ... | ... |
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 { |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | @GetMapping("/list") |
| 58 | - @ApiOperation("获取菜单列表") | |
| 58 | + @ApiOperation(value = "获取菜单列表", notes = "用于【菜单管理】界面") | |
| 59 | 59 | @PreAuthorize("@ss.hasPermission('system:menu:query')") |
| 60 | 60 | public CommonResult<List<MenuRespVO>> getMenus(MenuListReqVO reqVO) { |
| 61 | 61 | List<MenuDO> list = menuService.getMenus(reqVO); |
| ... | ... | @@ -64,13 +64,13 @@ public class MenuController { |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | 66 | @GetMapping("/list-all-simple") |
| 67 | - @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,主要用于前端的下拉选项") | |
| 67 | + @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,用于【角色分配菜单】功能的选项") | |
| 68 | 68 | public CommonResult<List<MenuSimpleRespVO>> getSimpleMenus() { |
| 69 | 69 | // 获得菜单列表,只要开启状态的 |
| 70 | 70 | MenuListReqVO reqVO = new MenuListReqVO(); |
| 71 | 71 | reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 72 | 72 | List<MenuDO> list = menuService.getMenus(reqVO); |
| 73 | - // 排序后,返回个诶前端 | |
| 73 | + // 排序后,返回给前端 | |
| 74 | 74 | list.sort(Comparator.comparing(MenuDO::getSort)); |
| 75 | 75 | return success(MenuConvert.INSTANCE.convertList02(list)); |
| 76 | 76 | } | ... | ... |
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 { |
| 37 | 37 | @GetMapping("/list-role-resources") |
| 38 | 38 | // @RequiresPermissions("system:permission:assign-role-menu") |
| 39 | 39 | public CommonResult<Set<Long>> listRoleMenus(Long roleId) { |
| 40 | - return success(permissionService.listRoleMenuIds(roleId)); | |
| 40 | + return success(permissionService.getRoleMenuIds(roleId)); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | @PostMapping("/assign-role-menu") | ... | ... |
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; |
| 4 | 4 | import lombok.Data; |
| 5 | 5 | |
| 6 | 6 | import javax.validation.constraints.NotNull; |
| 7 | -import java.util.List; | |
| 7 | +import java.util.Set; | |
| 8 | 8 | |
| 9 | 9 | /** |
| 10 | 10 | * 租户套餐 Base VO,提供给添加、修改、详细的子 VO 使用 |
| ... | ... | @@ -26,6 +26,6 @@ public class TenantPackageBaseVO { |
| 26 | 26 | |
| 27 | 27 | @ApiModelProperty(value = "关联的菜单编号", required = true) |
| 28 | 28 | @NotNull(message = "关联的菜单编号不能为空") |
| 29 | - private List<Long> menuIds; | |
| 29 | + private Set<Long> menuIds; | |
| 30 | 30 | |
| 31 | 31 | } | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java
| 1 | 1 | package cn.iocoder.yudao.module.system.dal.dataobject.permission; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 5 | 4 | import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler; |
| 6 | 5 | import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; |
| 6 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | |
| 7 | 7 | import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; |
| 8 | 8 | import com.baomidou.mybatisplus.annotation.TableField; |
| 9 | 9 | import com.baomidou.mybatisplus.annotation.TableId; |
| ... | ... | @@ -21,7 +21,7 @@ import java.util.Set; |
| 21 | 21 | @TableName(value = "system_role", autoResultMap = true) |
| 22 | 22 | @Data |
| 23 | 23 | @EqualsAndHashCode(callSuper = true) |
| 24 | -public class RoleDO extends BaseDO { | |
| 24 | +public class RoleDO extends TenantBaseDO { | |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | 27 | * 角色ID | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleMenuDO.java
| 1 | 1 | package cn.iocoder.yudao.module.system.dal.dataobject.permission; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | |
| 4 | 5 | import com.baomidou.mybatisplus.annotation.TableId; |
| 5 | 6 | import com.baomidou.mybatisplus.annotation.TableName; |
| 6 | 7 | import lombok.Data; |
| ... | ... | @@ -14,7 +15,7 @@ import lombok.EqualsAndHashCode; |
| 14 | 15 | @TableName("system_role_menu") |
| 15 | 16 | @Data |
| 16 | 17 | @EqualsAndHashCode(callSuper = true) |
| 17 | -public class RoleMenuDO extends BaseDO { | |
| 18 | +public class RoleMenuDO extends TenantBaseDO { | |
| 18 | 19 | |
| 19 | 20 | /** |
| 20 | 21 | * 自增主键 | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| 6 | 7 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO; |
| ... | ... | @@ -41,7 +42,7 @@ public interface RoleMapper extends BaseMapperX<RoleDO> { |
| 41 | 42 | } |
| 42 | 43 | |
| 43 | 44 | default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) { |
| 44 | - return selectList(new QueryWrapperX<RoleDO>().in("status", statuses)); | |
| 45 | + return selectList(new LambdaQueryWrapperX<RoleDO>().inIfPresent(RoleDO::getStatus, statuses)); | |
| 45 | 46 | } |
| 46 | 47 | |
| 47 | 48 | @InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户。原因:该方法被异步 task 调用,此时获取不到租户编号 | ... | ... |
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; |
| 7 | 7 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 8 | 8 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 9 | 9 | import org.apache.ibatis.annotations.Mapper; |
| 10 | +import org.apache.ibatis.annotations.Select; | |
| 10 | 11 | |
| 12 | +import java.util.Date; | |
| 11 | 13 | import java.util.List; |
| 12 | 14 | |
| 13 | 15 | /** |
| ... | ... | @@ -43,7 +45,14 @@ public interface TenantMapper extends BaseMapperX<TenantDO> { |
| 43 | 45 | } |
| 44 | 46 | |
| 45 | 47 | default Integer selectCountByPackageId(Long packageId) { |
| 46 | - return selectCount("package_id", packageId); | |
| 48 | + return selectCount(TenantDO::getPackageId, packageId); | |
| 47 | 49 | } |
| 48 | 50 | |
| 51 | + default List<TenantDO> selectListByPackageId(Long packageId) { | |
| 52 | + return selectList(TenantDO::getPackageId, packageId); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Select("SELECT id FROM system_tenant WHERE update_time > #{maxUpdateTime} LIMIT 1") | |
| 56 | + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime); | |
| 57 | + | |
| 49 | 58 | } | ... | ... |
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; |
| 10 | 10 | @AllArgsConstructor |
| 11 | 11 | public enum RoleCodeEnum { |
| 12 | 12 | |
| 13 | - ADMIN("admin"), // 超级管理员 | |
| 13 | + SUPER_ADMIN("super_admin", "超级管理员"), | |
| 14 | + TENANT_ADMIN("tenant_admin", "租户管理员"), | |
| 14 | 15 | ; |
| 15 | 16 | |
| 16 | 17 | /** |
| 17 | 18 | * 角色编码 |
| 18 | 19 | */ |
| 19 | - private final String key; | |
| 20 | + private final String code; | |
| 21 | + /** | |
| 22 | + * 名字 | |
| 23 | + */ | |
| 24 | + private final String name; | |
| 20 | 25 | |
| 21 | 26 | } | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsSendConsumer.java
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/tenant/TenantRefreshConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.consumer.tenant; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; | |
| 4 | +import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage; | |
| 5 | +import cn.iocoder.yudao.module.system.service.tenant.TenantService; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +import javax.annotation.Resource; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 针对 {@link cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage} 的消费者 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +@Slf4j | |
| 18 | +public class TenantRefreshConsumer extends AbstractChannelMessageListener<TenantRefreshMessage> { | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private TenantService tenantService; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public void onMessage(TenantRefreshMessage message) { | |
| 25 | + log.info("[onMessage][收到 Tenant 刷新消息]"); | |
| 26 | + tenantService.initLocalCache(); | |
| 27 | + } | |
| 28 | + | |
| 29 | +} | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/tenant/TenantRefreshMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.message.tenant; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.EqualsAndHashCode; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 租户数据刷新 Message | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@EqualsAndHashCode(callSuper = true) | |
| 14 | +public class TenantRefreshMessage extends AbstractChannelMessage { | |
| 15 | + | |
| 16 | + @Override | |
| 17 | + public String getChannel() { | |
| 18 | + return "system.tenant.refresh"; | |
| 19 | + } | |
| 20 | + | |
| 21 | +} | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/tenant/TenantProducer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.producer.tenant; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; | |
| 4 | +import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage; | |
| 5 | +import cn.iocoder.yudao.module.system.mq.message.tenant.TenantRefreshMessage; | |
| 6 | +import org.springframework.stereotype.Component; | |
| 7 | + | |
| 8 | +import javax.annotation.Resource; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * Tenant 租户相关消息的 Producer | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Component | |
| 16 | +public class TenantProducer { | |
| 17 | + | |
| 18 | + @Resource | |
| 19 | + private RedisMQTemplate redisMQTemplate; | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * 发送 {@link RoleRefreshMessage} 消息 | |
| 23 | + */ | |
| 24 | + public void sendTenantRefreshMessage() { | |
| 25 | + TenantRefreshMessage message = new TenantRefreshMessage(); | |
| 26 | + redisMQTemplate.send(message); | |
| 27 | + } | |
| 28 | + | |
| 29 | +} | ... | ... |
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; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 7 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 7 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | |
| 8 | 8 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; |
| 10 | 10 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO; |
| ... | ... | @@ -73,9 +73,10 @@ public class DeptServiceImpl implements DeptService { |
| 73 | 73 | |
| 74 | 74 | @Override |
| 75 | 75 | @PostConstruct |
| 76 | + @TenantIgnore // 初始化缓存,无需租户过滤 | |
| 76 | 77 | public synchronized void initLocalCache() { |
| 77 | 78 | // 获取部门列表,如果有更新 |
| 78 | - List<DeptDO> deptList = this.loadDeptIfUpdate(maxUpdateTime); | |
| 79 | + List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime); | |
| 79 | 80 | if (CollUtil.isEmpty(deptList)) { |
| 80 | 81 | return; |
| 81 | 82 | } |
| ... | ... | @@ -90,8 +91,7 @@ public class DeptServiceImpl implements DeptService { |
| 90 | 91 | // 设置缓存 |
| 91 | 92 | deptCache = builder.build(); |
| 92 | 93 | parentDeptCache = parentBuilder.build(); |
| 93 | - assert deptList.size() > 0; // 断言,避免告警 | |
| 94 | - maxUpdateTime = deptList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 94 | + maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime); | |
| 95 | 95 | log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size()); |
| 96 | 96 | } |
| 97 | 97 | |
| ... | ... | @@ -107,7 +107,7 @@ public class DeptServiceImpl implements DeptService { |
| 107 | 107 | * @param maxUpdateTime 当前部门的最大更新时间 |
| 108 | 108 | * @return 部门列表 |
| 109 | 109 | */ |
| 110 | - private List<DeptDO> loadDeptIfUpdate(Date maxUpdateTime) { | |
| 110 | + protected List<DeptDO> loadDeptIfUpdate(Date maxUpdateTime) { | |
| 111 | 111 | // 第一步,判断是否要更新。 |
| 112 | 112 | if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 |
| 113 | 113 | log.info("[loadMenuIfUpdate][首次加载全量部门]"); |
| ... | ... | @@ -118,7 +118,7 @@ public class DeptServiceImpl implements DeptService { |
| 118 | 118 | log.info("[loadMenuIfUpdate][增量加载全量部门]"); |
| 119 | 119 | } |
| 120 | 120 | // 第二步,如果有更新,则从数据库加载所有部门 |
| 121 | - return deptMapper.selectListIgnoreTenant(); | |
| 121 | + return deptMapper.selectList(); | |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | @Override | ... | ... |
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; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 6 | 7 | import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO; |
| 7 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 8 | 8 | import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCreateReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO; |
| 10 | 10 | import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO; |
| ... | ... | @@ -99,8 +99,7 @@ public class DictDataServiceImpl implements DictDataService { |
| 99 | 99 | }); |
| 100 | 100 | labelDictDataCache = labelDictDataBuilder.build(); |
| 101 | 101 | valueDictDataCache = valueDictDataBuilder.build(); |
| 102 | - assert dataList.size() > 0; // 断言,避免告警 | |
| 103 | - maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 102 | + maxUpdateTime = CollectionUtils.getMaxValue(dataList, DictDataDO::getUpdateTime); | |
| 104 | 103 | log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size()); |
| 105 | 104 | } |
| 106 | 105 | ... | ... |
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 { |
| 66 | 66 | * @param menusStatuses 菜单状态数组 |
| 67 | 67 | * @return 菜单列表 |
| 68 | 68 | */ |
| 69 | - List<MenuDO> listMenusFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses); | |
| 69 | + List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses); | |
| 70 | 70 | |
| 71 | 71 | /** |
| 72 | 72 | * 获得指定编号的菜单数组,从缓存中 |
| ... | ... | @@ -78,8 +78,8 @@ public interface MenuService { |
| 78 | 78 | * @param menusStatuses 菜单状态数组 |
| 79 | 79 | * @return 菜单数组 |
| 80 | 80 | */ |
| 81 | - List<MenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, | |
| 82 | - Collection<Integer> menusStatuses); | |
| 81 | + List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, | |
| 82 | + Collection<Integer> menusStatuses); | |
| 83 | 83 | |
| 84 | 84 | /** |
| 85 | 85 | * 获得权限对应的菜单数组 | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; |
| 5 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 6 | 6 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO; |
| 8 | 8 | 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; |
| 12 | 12 | import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum; |
| 13 | 13 | import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; |
| 14 | 14 | import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer; |
| 15 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 16 | 15 | import com.google.common.annotations.VisibleForTesting; |
| 17 | 16 | import com.google.common.collect.ImmutableMap; |
| 18 | 17 | import com.google.common.collect.ImmutableMultimap; |
| ... | ... | @@ -95,8 +94,7 @@ public class MenuServiceImpl implements MenuService { |
| 95 | 94 | }); |
| 96 | 95 | menuCache = menuCacheBuilder.build(); |
| 97 | 96 | permissionMenuCache = permMenuCacheBuilder.build(); |
| 98 | - assert menuList.size() > 0; // 断言,避免告警 | |
| 99 | - maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 97 | + maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime); | |
| 100 | 98 | log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size()); |
| 101 | 99 | } |
| 102 | 100 | |
| ... | ... | @@ -201,7 +199,7 @@ public class MenuServiceImpl implements MenuService { |
| 201 | 199 | } |
| 202 | 200 | |
| 203 | 201 | @Override |
| 204 | - public List<MenuDO> listMenusFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) { | |
| 202 | + public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) { | |
| 205 | 203 | // 任一一个参数为空,则返回空 |
| 206 | 204 | if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) { |
| 207 | 205 | return Collections.emptyList(); |
| ... | ... | @@ -213,8 +211,8 @@ public class MenuServiceImpl implements MenuService { |
| 213 | 211 | } |
| 214 | 212 | |
| 215 | 213 | @Override |
| 216 | - public List<MenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, | |
| 217 | - Collection<Integer> menusStatuses) { | |
| 214 | + public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, | |
| 215 | + Collection<Integer> menusStatuses) { | |
| 218 | 216 | // 任一一个参数为空,则返回空 |
| 219 | 217 | if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) { |
| 220 | 218 | return Collections.emptyList(); | ... | ... |
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 |
| 33 | 33 | * @param menusStatuses 菜单状态数组 |
| 34 | 34 | * @return 菜单列表 |
| 35 | 35 | */ |
| 36 | - List<MenuDO> getRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, | |
| 37 | - Collection<Integer> menusStatuses); | |
| 36 | + List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, | |
| 37 | + Collection<Integer> menusStatuses); | |
| 38 | 38 | |
| 39 | 39 | /** |
| 40 | 40 | * 获得用户拥有的角色编号集合 |
| ... | ... | @@ -51,7 +51,7 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D |
| 51 | 51 | * @param roleId 角色编号 |
| 52 | 52 | * @return 菜单编号集合 |
| 53 | 53 | */ |
| 54 | - Set<Long> listRoleMenuIds(Long roleId); | |
| 54 | + Set<Long> getRoleMenuIds(Long roleId); | |
| 55 | 55 | |
| 56 | 56 | /** |
| 57 | 57 | * 设置角色菜单 | ... | ... |
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; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.collection.CollectionUtil; |
| 5 | 5 | import cn.hutool.core.util.ArrayUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 9 | +import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; | |
| 10 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 11 | +import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; | |
| 12 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | |
| 13 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | |
| 6 | 14 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; |
| 7 | 15 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; |
| 8 | 16 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; |
| ... | ... | @@ -12,13 +20,6 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; |
| 12 | 20 | import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; |
| 13 | 21 | import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer; |
| 14 | 22 | import cn.iocoder.yudao.module.system.service.dept.DeptService; |
| 15 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 16 | -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 17 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 18 | -import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; | |
| 19 | -import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 20 | -import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; | |
| 21 | -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | |
| 22 | 23 | import com.google.common.collect.ImmutableMultimap; |
| 23 | 24 | import com.google.common.collect.Multimap; |
| 24 | 25 | import com.google.common.collect.Sets; |
| ... | ... | @@ -94,10 +95,10 @@ public class PermissionServiceImpl implements PermissionService { |
| 94 | 95 | */ |
| 95 | 96 | @Override |
| 96 | 97 | @PostConstruct |
| 98 | + @TenantIgnore // 初始化缓存,无需租户过滤 | |
| 97 | 99 | public void initLocalCache() { |
| 98 | - Date now = new Date(); | |
| 99 | 100 | // 获取角色与菜单的关联列表,如果有更新 |
| 100 | - List<RoleMenuDO> roleMenuList = this.loadRoleMenuIfUpdate(maxUpdateTime); | |
| 101 | + List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(maxUpdateTime); | |
| 101 | 102 | if (CollUtil.isEmpty(roleMenuList)) { |
| 102 | 103 | return; |
| 103 | 104 | } |
| ... | ... | @@ -111,8 +112,7 @@ public class PermissionServiceImpl implements PermissionService { |
| 111 | 112 | }); |
| 112 | 113 | roleMenuCache = roleMenuCacheBuilder.build(); |
| 113 | 114 | menuRoleCache = menuRoleCacheBuilder.build(); |
| 114 | - assert roleMenuList.size() > 0; // 断言,避免告警 | |
| 115 | - maxUpdateTime = now; | |
| 115 | + maxUpdateTime = CollectionUtils.getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime); | |
| 116 | 116 | log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size()); |
| 117 | 117 | } |
| 118 | 118 | |
| ... | ... | @@ -128,7 +128,7 @@ public class PermissionServiceImpl implements PermissionService { |
| 128 | 128 | * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间 |
| 129 | 129 | * @return 角色与菜单的关联列表 |
| 130 | 130 | */ |
| 131 | - private List<RoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) { | |
| 131 | + protected List<RoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) { | |
| 132 | 132 | // 第一步,判断是否要更新。 |
| 133 | 133 | if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 |
| 134 | 134 | log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]"); |
| ... | ... | @@ -143,21 +143,22 @@ public class PermissionServiceImpl implements PermissionService { |
| 143 | 143 | } |
| 144 | 144 | |
| 145 | 145 | @Override |
| 146 | - public List<MenuDO> getRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, | |
| 147 | - Collection<Integer> menusStatuses) { | |
| 146 | + public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, | |
| 147 | + Collection<Integer> menusStatuses) { | |
| 148 | 148 | // 任一一个参数为空时,不返回任何菜单 |
| 149 | 149 | if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) { |
| 150 | 150 | return Collections.emptyList(); |
| 151 | 151 | } |
| 152 | - // 判断角色是否包含管理员 | |
| 152 | + | |
| 153 | + // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部 | |
| 153 | 154 | List<RoleDO> roleList = roleService.getRolesFromCache(roleIds); |
| 154 | - boolean hasAdmin = roleService.hasAnyAdmin(roleList); | |
| 155 | - // 获得角色拥有的菜单关联 | |
| 156 | - if (hasAdmin) { // 管理员,获取到全部 | |
| 157 | - return menuService.listMenusFromCache(menuTypes, menusStatuses); | |
| 155 | + if (roleService.hasAnySuperAdmin(roleList)) { | |
| 156 | + return menuService.getMenuListFromCache(menuTypes, menusStatuses); | |
| 158 | 157 | } |
| 158 | + | |
| 159 | + // 获得角色拥有的菜单关联 | |
| 159 | 160 | List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds); |
| 160 | - return menuService.listMenusFromCache(menuIds, menuTypes, menusStatuses); | |
| 161 | + return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses); | |
| 161 | 162 | } |
| 162 | 163 | |
| 163 | 164 | @Override |
| ... | ... | @@ -174,10 +175,10 @@ public class PermissionServiceImpl implements PermissionService { |
| 174 | 175 | } |
| 175 | 176 | |
| 176 | 177 | @Override |
| 177 | - public Set<Long> listRoleMenuIds(Long roleId) { | |
| 178 | + public Set<Long> getRoleMenuIds(Long roleId) { | |
| 178 | 179 | // 如果是管理员的情况下,获取全部菜单编号 |
| 179 | 180 | RoleDO role = roleService.getRole(roleId); |
| 180 | - if (roleService.hasAnyAdmin(Collections.singletonList(role))) { | |
| 181 | + if (roleService.hasAnySuperAdmin(Collections.singletonList(role))) { | |
| 181 | 182 | return CollectionUtils.convertSet(menuService.getMenus(), MenuDO::getId); |
| 182 | 183 | } |
| 183 | 184 | // 如果是非管理员的情况下,获得拥有的菜单编号 |
| ... | ... | @@ -302,7 +303,7 @@ public class PermissionServiceImpl implements PermissionService { |
| 302 | 303 | return false; |
| 303 | 304 | } |
| 304 | 305 | // 判断是否是超管。如果是,当然符合条件 |
| 305 | - if (roleService.hasAnyAdmin(roleIds)) { | |
| 306 | + if (roleService.hasAnySuperAdmin(roleIds)) { | |
| 306 | 307 | return true; |
| 307 | 308 | } |
| 308 | 309 | |
| ... | ... | @@ -337,7 +338,7 @@ public class PermissionServiceImpl implements PermissionService { |
| 337 | 338 | return false; |
| 338 | 339 | } |
| 339 | 340 | // 判断是否是超管。如果是,当然符合条件 |
| 340 | - if (roleService.hasAnyAdmin(roleIds)) { | |
| 341 | + if (roleService.hasAnySuperAdmin(roleIds)) { | |
| 341 | 342 | return true; |
| 342 | 343 | } |
| 343 | 344 | Set<String> userRoles = CollectionUtils.convertSet(roleService.getRolesFromCache(roleIds), | ... | ... |
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 { |
| 90 | 90 | List<RoleDO> getRolesFromCache(Collection<Long> ids); |
| 91 | 91 | |
| 92 | 92 | /** |
| 93 | - * 判断角色数组中,是否有管理员 | |
| 93 | + * 判断角色数组中,是否有超级管理员 | |
| 94 | 94 | * |
| 95 | 95 | * @param roleList 角色数组 |
| 96 | 96 | * @return 是否有管理员 |
| 97 | 97 | */ |
| 98 | - boolean hasAnyAdmin(Collection<RoleDO> roleList); | |
| 98 | + boolean hasAnySuperAdmin(Collection<RoleDO> roleList); | |
| 99 | 99 | |
| 100 | 100 | /** |
| 101 | 101 | * 判断角色编号数组中,是否有管理员 |
| ... | ... | @@ -103,8 +103,8 @@ public interface RoleService { |
| 103 | 103 | * @param ids 角色编号数组 |
| 104 | 104 | * @return 是否有管理员 |
| 105 | 105 | */ |
| 106 | - default boolean hasAnyAdmin(Set<Long> ids) { | |
| 107 | - return hasAnyAdmin(getRolesFromCache(ids)); | |
| 106 | + default boolean hasAnySuperAdmin(Set<Long> ids) { | |
| 107 | + return hasAnySuperAdmin(getRolesFromCache(ids)); | |
| 108 | 108 | } |
| 109 | 109 | |
| 110 | 110 | /** | ... | ... |
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; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 9 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 10 | 9 | import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; |
| 10 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | |
| 11 | 11 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO; |
| 12 | 12 | import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO; |
| 13 | 13 | 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; |
| 19 | 19 | import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; |
| 20 | 20 | import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer; |
| 21 | 21 | import com.google.common.annotations.VisibleForTesting; |
| 22 | -import com.google.common.collect.ImmutableMap; | |
| 23 | 22 | import lombok.extern.slf4j.Slf4j; |
| 24 | 23 | import org.springframework.lang.Nullable; |
| 25 | 24 | import org.springframework.scheduling.annotation.Scheduled; |
| ... | ... | @@ -78,19 +77,17 @@ public class RoleServiceImpl implements RoleService { |
| 78 | 77 | */ |
| 79 | 78 | @Override |
| 80 | 79 | @PostConstruct |
| 80 | + @TenantIgnore // 忽略自动多租户,全局初始化缓存 | |
| 81 | 81 | public void initLocalCache() { |
| 82 | 82 | // 获取角色列表,如果有更新 |
| 83 | - List<RoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime); | |
| 83 | + List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime); | |
| 84 | 84 | if (CollUtil.isEmpty(roleList)) { |
| 85 | 85 | return; |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | 88 | // 写入缓存 |
| 89 | - ImmutableMap.Builder<Long, RoleDO> builder = ImmutableMap.builder(); | |
| 90 | - roleList.forEach(sysRoleDO -> builder.put(sysRoleDO.getId(), sysRoleDO)); | |
| 91 | - roleCache = builder.build(); | |
| 92 | - assert roleList.size() > 0; // 断言,避免告警 | |
| 93 | - maxUpdateTime = roleList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 89 | + roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId); | |
| 90 | + maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime); | |
| 94 | 91 | log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size()); |
| 95 | 92 | } |
| 96 | 93 | |
| ... | ... | @@ -216,11 +213,11 @@ public class RoleServiceImpl implements RoleService { |
| 216 | 213 | } |
| 217 | 214 | |
| 218 | 215 | @Override |
| 219 | - public boolean hasAnyAdmin(Collection<RoleDO> roleList) { | |
| 216 | + public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) { | |
| 220 | 217 | if (CollectionUtil.isEmpty(roleList)) { |
| 221 | 218 | return false; |
| 222 | 219 | } |
| 223 | - return roleList.stream().anyMatch(roleDO -> RoleCodeEnum.ADMIN.getKey().equals(roleDO.getCode())); | |
| 220 | + return roleList.stream().anyMatch(roleDO -> RoleCodeEnum.SUPER_ADMIN.getCode().equals(roleDO.getCode())); | |
| 224 | 221 | } |
| 225 | 222 | |
| 226 | 223 | @Override | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.system.service.sms; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 6 | +import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; | |
| 7 | +import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; | |
| 4 | 8 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO; |
| 5 | 9 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; |
| 6 | 10 | 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; |
| 8 | 12 | import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; |
| 9 | 13 | import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; |
| 10 | 14 | import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer; |
| 11 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 12 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 13 | -import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; | |
| 14 | -import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties; | |
| 15 | 15 | import lombok.extern.slf4j.Slf4j; |
| 16 | 16 | import org.springframework.scheduling.annotation.Scheduled; |
| 17 | 17 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -19,13 +19,12 @@ import org.springframework.stereotype.Service; |
| 19 | 19 | import javax.annotation.PostConstruct; |
| 20 | 20 | import javax.annotation.Resource; |
| 21 | 21 | import java.util.Collection; |
| 22 | -import java.util.Comparator; | |
| 23 | 22 | import java.util.Date; |
| 24 | 23 | import java.util.List; |
| 25 | 24 | |
| 25 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 26 | 26 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN; |
| 27 | 27 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; |
| 28 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 29 | 28 | |
| 30 | 29 | /** |
| 31 | 30 | * 短信渠道Service实现类 |
| ... | ... | @@ -74,8 +73,7 @@ public class SmsChannelServiceImpl implements SmsChannelService { |
| 74 | 73 | propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties)); |
| 75 | 74 | |
| 76 | 75 | // 写入缓存 |
| 77 | - assert smsChannels.size() > 0; // 断言,避免告警 | |
| 78 | - maxUpdateTime = smsChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 76 | + maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime); | |
| 79 | 77 | log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size()); |
| 80 | 78 | } |
| 81 | 79 | ... | ... |
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; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ReUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 6 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 7 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 9 | +import cn.iocoder.yudao.framework.sms.core.client.SmsClient; | |
| 10 | +import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; | |
| 11 | +import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult; | |
| 12 | +import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO; | |
| 7 | 13 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO; |
| 8 | 14 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO; |
| 9 | 15 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; |
| 10 | 16 | import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateUpdateReqVO; |
| 11 | 17 | import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert; |
| 18 | +import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; | |
| 12 | 19 | import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO; |
| 13 | 20 | import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper; |
| 14 | 21 | import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer; |
| 15 | -import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; | |
| 16 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 17 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 18 | -import cn.iocoder.yudao.framework.sms.core.client.SmsClient; | |
| 19 | -import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; | |
| 20 | -import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult; | |
| 21 | -import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO; | |
| 22 | 22 | import com.google.common.annotations.VisibleForTesting; |
| 23 | -import com.google.common.collect.ImmutableMap; | |
| 24 | 23 | import lombok.extern.slf4j.Slf4j; |
| 25 | 24 | import org.springframework.scheduling.annotation.Scheduled; |
| 26 | 25 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -31,8 +30,8 @@ import javax.annotation.Resource; |
| 31 | 30 | import java.util.*; |
| 32 | 31 | import java.util.regex.Pattern; |
| 33 | 32 | |
| 34 | -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | |
| 35 | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 34 | +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | |
| 36 | 35 | |
| 37 | 36 | /** |
| 38 | 37 | * 短信模板 Service 实现类 |
| ... | ... | @@ -89,11 +88,8 @@ public class SmsTemplateServiceImpl implements SmsTemplateService { |
| 89 | 88 | } |
| 90 | 89 | |
| 91 | 90 | // 写入缓存 |
| 92 | - ImmutableMap.Builder<String, SmsTemplateDO> builder = ImmutableMap.builder(); | |
| 93 | - smsTemplateList.forEach(sysSmsTemplateDO -> builder.put(sysSmsTemplateDO.getCode(), sysSmsTemplateDO)); | |
| 94 | - smsTemplateCache = builder.build(); | |
| 95 | - assert smsTemplateList.size() > 0; // 断言,避免告警 | |
| 96 | - maxUpdateTime = smsTemplateList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | |
| 91 | + smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode); | |
| 92 | + maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime); | |
| 97 | 93 | log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size()); |
| 98 | 94 | } |
| 99 | 95 | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.system.service.tenant; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 6 | import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO; |
| 8 | 9 | import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert; |
| 10 | +import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; | |
| 9 | 11 | import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; |
| 10 | 12 | import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper; |
| 11 | 13 | import org.springframework.context.annotation.Lazy; |
| 12 | 14 | import org.springframework.stereotype.Service; |
| 15 | +import org.springframework.transaction.annotation.Transactional; | |
| 13 | 16 | import org.springframework.validation.annotation.Validated; |
| 14 | 17 | |
| 15 | 18 | import javax.annotation.Resource; |
| ... | ... | @@ -45,12 +48,18 @@ public class TenantPackageServiceImpl implements TenantPackageService { |
| 45 | 48 | } |
| 46 | 49 | |
| 47 | 50 | @Override |
| 51 | + @Transactional(rollbackFor = Exception.class) | |
| 48 | 52 | public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) { |
| 49 | 53 | // 校验存在 |
| 50 | - this.validateTenantPackageExists(updateReqVO.getId()); | |
| 54 | + TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId()); | |
| 51 | 55 | // 更新 |
| 52 | 56 | TenantPackageDO updateObj = TenantPackageConvert.INSTANCE.convert(updateReqVO); |
| 53 | 57 | tenantPackageMapper.updateById(updateObj); |
| 58 | + // 如果菜单发生变化,则修改每个租户的菜单 | |
| 59 | + if (!CollUtil.isEqualList(tenantPackage.getMenuIds(), updateReqVO.getMenuIds())) { | |
| 60 | + List<TenantDO> tenants = tenantService.getTenantListByPackageId(tenantPackage.getId()); | |
| 61 | + tenants.forEach(tenant -> tenantService.updateTenantRoleMenu(tenant.getId(), updateReqVO.getMenuIds())); | |
| 62 | + } | |
| 54 | 63 | } |
| 55 | 64 | |
| 56 | 65 | @Override |
| ... | ... | @@ -63,10 +72,12 @@ public class TenantPackageServiceImpl implements TenantPackageService { |
| 63 | 72 | tenantPackageMapper.deleteById(id); |
| 64 | 73 | } |
| 65 | 74 | |
| 66 | - private void validateTenantPackageExists(Long id) { | |
| 67 | - if (tenantPackageMapper.selectById(id) == null) { | |
| 75 | + private TenantPackageDO validateTenantPackageExists(Long id) { | |
| 76 | + TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id); | |
| 77 | + if (tenantPackage == null) { | |
| 68 | 78 | throw exception(TENANT_PACKAGE_NOT_EXISTS); |
| 69 | 79 | } |
| 80 | + return tenantPackage; | |
| 70 | 81 | } |
| 71 | 82 | |
| 72 | 83 | private void validateTenantUsed(Long id) { | ... | ... |
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; |
| 11 | 11 | import javax.validation.Valid; |
| 12 | 12 | import java.util.Collection; |
| 13 | 13 | import java.util.List; |
| 14 | +import java.util.Set; | |
| 14 | 15 | |
| 15 | 16 | /** |
| 16 | 17 | * 租户 Service 接口 |
| ... | ... | @@ -20,6 +21,11 @@ import java.util.List; |
| 20 | 21 | public interface TenantService extends TenantFrameworkService { |
| 21 | 22 | |
| 22 | 23 | /** |
| 24 | + * 初始化租户的本地缓存 | |
| 25 | + */ | |
| 26 | + void initLocalCache(); | |
| 27 | + | |
| 28 | + /** | |
| 23 | 29 | * 创建租户 |
| 24 | 30 | * |
| 25 | 31 | * @param createReqVO 创建信息 |
| ... | ... | @@ -35,6 +41,14 @@ public interface TenantService extends TenantFrameworkService { |
| 35 | 41 | void updateTenant(@Valid TenantUpdateReqVO updateReqVO); |
| 36 | 42 | |
| 37 | 43 | /** |
| 44 | + * 更新租户的角色菜单 | |
| 45 | + * | |
| 46 | + * @param tenantId 租户编号 | |
| 47 | + * @param menuIds 菜单编号数组 | |
| 48 | + */ | |
| 49 | + void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds); | |
| 50 | + | |
| 51 | + /** | |
| 38 | 52 | * 删除租户 |
| 39 | 53 | * |
| 40 | 54 | * @param id 编号 |
| ... | ... | @@ -89,4 +103,12 @@ public interface TenantService extends TenantFrameworkService { |
| 89 | 103 | */ |
| 90 | 104 | Integer getTenantCountByPackageId(Long packageId); |
| 91 | 105 | |
| 106 | + /** | |
| 107 | + * 获得使用指定套餐的租户数组 | |
| 108 | + * | |
| 109 | + * @param packageId 租户套餐编号 | |
| 110 | + * @return 租户数组 | |
| 111 | + */ | |
| 112 | + List<TenantDO> getTenantListByPackageId(Long packageId); | |
| 113 | + | |
| 92 | 114 | } | ... | ... |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.system.service.tenant; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 5 | +import cn.hutool.core.util.ObjectUtil; | |
| 3 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 8 | 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 |
| 11 | 14 | import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; |
| 12 | 15 | import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO; |
| 13 | 16 | import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert; |
| 17 | +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | |
| 14 | 18 | import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; |
| 19 | +import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; | |
| 15 | 20 | import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper; |
| 16 | 21 | import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; |
| 17 | 22 | import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; |
| 23 | +import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer; | |
| 18 | 24 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| 19 | 25 | import cn.iocoder.yudao.module.system.service.permission.RoleService; |
| 20 | 26 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| 27 | +import lombok.extern.slf4j.Slf4j; | |
| 28 | +import org.springframework.scheduling.annotation.Scheduled; | |
| 21 | 29 | import org.springframework.stereotype.Service; |
| 22 | 30 | import org.springframework.transaction.annotation.Transactional; |
| 31 | +import org.springframework.transaction.support.TransactionSynchronization; | |
| 32 | +import org.springframework.transaction.support.TransactionSynchronizationManager; | |
| 23 | 33 | import org.springframework.validation.annotation.Validated; |
| 24 | 34 | |
| 35 | +import javax.annotation.PostConstruct; | |
| 25 | 36 | import javax.annotation.Resource; |
| 26 | -import java.util.Collection; | |
| 27 | -import java.util.Collections; | |
| 28 | -import java.util.List; | |
| 37 | +import java.util.*; | |
| 29 | 38 | |
| 30 | 39 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 31 | 40 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -37,8 +46,27 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; |
| 37 | 46 | */ |
| 38 | 47 | @Service |
| 39 | 48 | @Validated |
| 49 | +@Slf4j | |
| 40 | 50 | public class TenantServiceImpl implements TenantService { |
| 41 | 51 | |
| 52 | + /** | |
| 53 | + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 | |
| 54 | + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 | |
| 55 | + */ | |
| 56 | + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * 角色缓存 | |
| 60 | + * key:角色编号 {@link RoleDO#getId()} | |
| 61 | + * | |
| 62 | + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 | |
| 63 | + */ | |
| 64 | + private volatile Map<Long, TenantDO> tenantCache; | |
| 65 | + /** | |
| 66 | + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新 | |
| 67 | + */ | |
| 68 | + private volatile Date maxUpdateTime; | |
| 69 | + | |
| 42 | 70 | @Resource |
| 43 | 71 | private TenantMapper tenantMapper; |
| 44 | 72 | |
| ... | ... | @@ -51,15 +79,61 @@ public class TenantServiceImpl implements TenantService { |
| 51 | 79 | @Resource |
| 52 | 80 | private PermissionService permissionService; |
| 53 | 81 | |
| 82 | + @Resource | |
| 83 | + private TenantProducer tenantProducer; | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * 初始化 {@link #tenantCache} 缓存 | |
| 87 | + */ | |
| 88 | + @Override | |
| 89 | + @PostConstruct | |
| 90 | + public void initLocalCache() { | |
| 91 | + // 获取租户列表,如果有更新 | |
| 92 | + List<TenantDO> tenantList = loadTenantIfUpdate(maxUpdateTime); | |
| 93 | + if (CollUtil.isEmpty(tenantList)) { | |
| 94 | + return; | |
| 95 | + } | |
| 96 | + | |
| 97 | + // 写入缓存 | |
| 98 | + tenantCache = CollectionUtils.convertImmutableMap(tenantList, TenantDO::getId); | |
| 99 | + maxUpdateTime = CollectionUtils.getMaxValue(tenantList, TenantDO::getUpdateTime); | |
| 100 | + log.info("[initLocalCache][初始化 Tenant 数量为 {}]", tenantList.size()); | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | |
| 104 | + public void schedulePeriodicRefresh() { | |
| 105 | + initLocalCache(); | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * 如果租户发生变化,从数据库中获取最新的全量租户。 | |
| 110 | + * 如果未发生变化,则返回空 | |
| 111 | + * | |
| 112 | + * @param maxUpdateTime 当前租户的最大更新时间 | |
| 113 | + * @return 租户列表 | |
| 114 | + */ | |
| 115 | + private List<TenantDO> loadTenantIfUpdate(Date maxUpdateTime) { | |
| 116 | + // 第一步,判断是否要更新。 | |
| 117 | + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 | |
| 118 | + log.info("[loadTenantIfUpdate][首次加载全量租户]"); | |
| 119 | + } else { // 判断数据库中是否有更新的租户 | |
| 120 | + if (tenantMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) { | |
| 121 | + return null; | |
| 122 | + } | |
| 123 | + log.info("[loadTenantIfUpdate][增量加载全量租户]"); | |
| 124 | + } | |
| 125 | + // 第二步,如果有更新,则从数据库加载所有租户 | |
| 126 | + return tenantMapper.selectList(); | |
| 127 | + } | |
| 128 | + | |
| 54 | 129 | @Override |
| 55 | 130 | public List<Long> getTenantIds() { |
| 56 | - List<TenantDO> tenants = tenantMapper.selectList(); | |
| 57 | - return CollectionUtils.convertList(tenants, TenantDO::getId); | |
| 131 | + return new ArrayList<>(tenantCache.keySet()); | |
| 58 | 132 | } |
| 59 | 133 | |
| 60 | 134 | @Override |
| 61 | 135 | public void validTenant(Long id) { |
| 62 | - TenantDO tenant = tenantMapper.selectById(id); | |
| 136 | + TenantDO tenant = tenantCache.get(id); | |
| 63 | 137 | if (tenant == null) { |
| 64 | 138 | throw exception(TENANT_NOT_EXISTS); |
| 65 | 139 | } |
| ... | ... | @@ -75,7 +149,7 @@ public class TenantServiceImpl implements TenantService { |
| 75 | 149 | @Transactional(rollbackFor = Exception.class) |
| 76 | 150 | public Long createTenant(TenantCreateReqVO createReqVO) { |
| 77 | 151 | // 校验套餐被禁用 |
| 78 | - tenantPackageService.validTenantPackage(createReqVO.getPackageId()); | |
| 152 | + TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId()); | |
| 79 | 153 | |
| 80 | 154 | // 创建租户 |
| 81 | 155 | TenantDO tenant = TenantConvert.INSTANCE.convert(createReqVO); |
| ... | ... | @@ -83,13 +157,19 @@ public class TenantServiceImpl implements TenantService { |
| 83 | 157 | |
| 84 | 158 | TenantUtils.execute(tenant.getId(), () -> { |
| 85 | 159 | // 创建角色 |
| 86 | - Long roleId = createRole(); | |
| 160 | + Long roleId = createRole(tenantPackage); | |
| 87 | 161 | // 创建用户,并分配角色 |
| 88 | 162 | Long userId = createUser(roleId, createReqVO); |
| 89 | 163 | // 修改租户的管理员 |
| 90 | 164 | tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId)); |
| 91 | 165 | }); |
| 92 | - // 返回 | |
| 166 | + // 发送刷新消息 | |
| 167 | + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| 168 | + @Override | |
| 169 | + public void afterCommit() { | |
| 170 | + tenantProducer.sendTenantRefreshMessage(); | |
| 171 | + } | |
| 172 | + }); | |
| 93 | 173 | return tenant.getId(); |
| 94 | 174 | } |
| 95 | 175 | |
| ... | ... | @@ -101,36 +181,80 @@ public class TenantServiceImpl implements TenantService { |
| 101 | 181 | return userId; |
| 102 | 182 | } |
| 103 | 183 | |
| 104 | - private Long createRole() { | |
| 184 | + private Long createRole(TenantPackageDO tenantPackage) { | |
| 185 | + // 创建角色 | |
| 105 | 186 | RoleCreateReqVO reqVO = new RoleCreateReqVO(); |
| 106 | - reqVO.setName(RoleCodeEnum.ADMIN.name()).setCode(RoleCodeEnum.ADMIN.getKey()).setSort(0); | |
| 107 | - return roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType()); | |
| 187 | + reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()) | |
| 188 | + .setSort(0).setRemark("系统自动生成"); | |
| 189 | + Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType()); | |
| 190 | + // 分配权限 | |
| 191 | + permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds()); | |
| 192 | + return roleId; | |
| 108 | 193 | } |
| 109 | 194 | |
| 110 | 195 | @Override |
| 196 | + @Transactional(rollbackFor = Exception.class) | |
| 111 | 197 | public void updateTenant(TenantUpdateReqVO updateReqVO) { |
| 112 | 198 | // 校验存在 |
| 113 | - this.validateTenantExists(updateReqVO.getId()); | |
| 199 | + TenantDO tenant = validateTenantExists(updateReqVO.getId()); | |
| 114 | 200 | // 校验套餐被禁用 |
| 115 | - tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); | |
| 201 | + TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); | |
| 116 | 202 | |
| 117 | - // 更新 | |
| 203 | + // 更新租户 | |
| 118 | 204 | TenantDO updateObj = TenantConvert.INSTANCE.convert(updateReqVO); |
| 119 | 205 | tenantMapper.updateById(updateObj); |
| 206 | + // 如果套餐发生变化,则修改其角色的权限 | |
| 207 | + if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) { | |
| 208 | + updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds()); | |
| 209 | + } | |
| 210 | + // 发送刷新消息 | |
| 211 | + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| 212 | + @Override | |
| 213 | + public void afterCommit() { | |
| 214 | + tenantProducer.sendTenantRefreshMessage(); | |
| 215 | + } | |
| 216 | + }); | |
| 217 | + } | |
| 218 | + | |
| 219 | + @Override | |
| 220 | + @Transactional(rollbackFor = Exception.class) | |
| 221 | + public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) { | |
| 222 | + TenantUtils.execute(tenantId, () -> { | |
| 223 | + // 获得所有角色 | |
| 224 | + List<RoleDO> roles = roleService.getRoles(null); | |
| 225 | + roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配", | |
| 226 | + role.getId(), role.getTenantId(), tenantId)); // 兜底校验 | |
| 227 | + // 重新分配每个角色的权限 | |
| 228 | + roles.forEach(role -> { | |
| 229 | + // 如果是租户管理员,重新分配其权限为租户套餐的权限 | |
| 230 | + if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) { | |
| 231 | + permissionService.assignRoleMenu(role.getId(), menuIds); | |
| 232 | + log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds); | |
| 233 | + return; | |
| 234 | + } | |
| 235 | + // 如果是其他角色,则去掉超过套餐的权限 | |
| 236 | + Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId()); | |
| 237 | + roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds); | |
| 238 | + permissionService.assignRoleMenu(role.getId(), roleMenuIds); | |
| 239 | + log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds); | |
| 240 | + }); | |
| 241 | + }); | |
| 120 | 242 | } |
| 121 | 243 | |
| 122 | 244 | @Override |
| 123 | 245 | public void deleteTenant(Long id) { |
| 124 | 246 | // 校验存在 |
| 125 | - this.validateTenantExists(id); | |
| 247 | + validateTenantExists(id); | |
| 126 | 248 | // 删除 |
| 127 | 249 | tenantMapper.deleteById(id); |
| 128 | 250 | } |
| 129 | 251 | |
| 130 | - private void validateTenantExists(Long id) { | |
| 131 | - if (tenantMapper.selectById(id) == null) { | |
| 252 | + private TenantDO validateTenantExists(Long id) { | |
| 253 | + TenantDO tenant = tenantMapper.selectById(id); | |
| 254 | + if (tenant == null) { | |
| 132 | 255 | throw exception(TENANT_NOT_EXISTS); |
| 133 | 256 | } |
| 257 | + return tenant; | |
| 134 | 258 | } |
| 135 | 259 | |
| 136 | 260 | @Override |
| ... | ... | @@ -163,4 +287,9 @@ public class TenantServiceImpl implements TenantService { |
| 163 | 287 | return tenantMapper.selectCountByPackageId(packageId); |
| 164 | 288 | } |
| 165 | 289 | |
| 290 | + @Override | |
| 291 | + public List<TenantDO> getTenantListByPackageId(Long packageId) { | |
| 292 | + return tenantMapper.selectListByPackageId(packageId); | |
| 293 | + } | |
| 294 | + | |
| 166 | 295 | } | ... | ... |
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 { |
| 243 | 243 | menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2); |
| 244 | 244 | mockCacheMap.put(menuDO.getId(), menuDO); |
| 245 | 245 | |
| 246 | - List<MenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(MenuTypeEnum.MENU.getType()), | |
| 246 | + List<MenuDO> menuDOS = sysMenuService.getMenuListFromCache(Collections.singletonList(MenuTypeEnum.MENU.getType()), | |
| 247 | 247 | Collections.singletonList(CommonStatusEnum.DISABLE.getStatus())); |
| 248 | 248 | assertEquals(menuDOS.size(), idMenuMap.size()); |
| 249 | 249 | menuDOS.forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m)); |
| ... | ... | @@ -270,7 +270,7 @@ public class MenuServiceTest extends BaseDbUnitTest { |
| 270 | 270 | menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2); |
| 271 | 271 | mockCacheMap.put(menuDO.getId(), menuDO); |
| 272 | 272 | |
| 273 | - List<MenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(1L), | |
| 273 | + List<MenuDO> menuDOS = sysMenuService.getMenuListFromCache(Collections.singletonList(1L), | |
| 274 | 274 | Collections.singletonList(MenuTypeEnum.MENU.getType()), Collections.singletonList(1)); |
| 275 | 275 | assertEquals(menuDOS.size(), idMenuMap.size()); |
| 276 | 276 | menuDOS.forEach(menu -> assertPojoEquals(idMenuMap.get(menu.getId()), menu)); | ... | ... |
yudao-server/src/main/resources/application.yaml
| ... | ... | @@ -79,8 +79,8 @@ yudao: |
| 79 | 79 | - cn.iocoder.yudao.module.tool.enums.ErrorCodeConstants |
| 80 | 80 | tenant: # 多租户相关配置项 |
| 81 | 81 | enable: true |
| 82 | - ignore-urls: /admin-api/system/captcha/get-image, /admin-api/infra/file/get/* | |
| 83 | - 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 | |
| 82 | + ignore-urls: /admin-api/system/tenant/get-id-by-name, /admin-api/system/captcha/get-image, /admin-api/infra/file/get/* | |
| 83 | + 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 | |
| 84 | 84 | sms-code: # 短信验证码相关的配置项 |
| 85 | 85 | expire-times: 10m |
| 86 | 86 | send-frequency: 1m | ... | ... |
yudao-ui-admin/src/views/system/tenantPackage/index.vue
| ... | ... | @@ -200,6 +200,8 @@ export default { |
| 200 | 200 | this.reset(); |
| 201 | 201 | this.open = true; |
| 202 | 202 | this.title = "添加租户套餐"; |
| 203 | + // 设置为非严格,继续使用半选中 | |
| 204 | + this.menuCheckStrictly = false; | |
| 203 | 205 | }, |
| 204 | 206 | /** 修改按钮操作 */ |
| 205 | 207 | handleUpdate(row) { |
| ... | ... | @@ -222,12 +224,6 @@ export default { |
| 222 | 224 | /** 获得菜单 */ |
| 223 | 225 | getMenus() { |
| 224 | 226 | listSimpleMenus().then(response => { |
| 225 | - // 移除 BUTTON 类型,暂支只需要发 DIR、MENU 类型 | |
| 226 | - for (let i = response.data.length -1; i >= 0 ; i--) { | |
| 227 | - if (response.data[i].type === SystemMenuTypeEnum.BUTTON) { | |
| 228 | - response.data.splice(i, 1); | |
| 229 | - } | |
| 230 | - } | |
| 231 | 227 | // 处理 menuOptions 参数 |
| 232 | 228 | this.menuOptions = []; |
| 233 | 229 | // 只需要配置 | ... | ... |
更新日志.md
| ... | ... | @@ -29,8 +29,9 @@ TODO |
| 29 | 29 | * 【新增】后端 `yudao.tenant.enable` 配置项,前端 `VUE_APP_TENANT_ENABLE` 配置项,用于开关租户功能。 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09) |
| 30 | 30 | * 【优化】调整默认所有表开启多租户的特性,可通过 `yudao.tenant.ignore-tables` 配置项进行忽略,替代原本默认不开启的策略 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09) |
| 31 | 31 | * 【新增】通过 `yudao.tenant.ignore-urls` 配置忽略多租户的请求,例如说 ,例如说短信回调、支付回调等 Open API [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/79311ecc71f0c6beabe0e5f84e1423ce745a5f09) |
| 32 | +* 【新增】新增 `@TenantIgnore` 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4d53944771c66b563da1e3d68d3ba43405af8a06) | |
| 32 | 33 | * 【新增】租户套餐的管理,可配置每个租户的可使用的功能权限 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/6b6d676a6baa2dad16ae9bf03d5002209064c8cc) |
| 33 | -* 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 []() | |
| 34 | +* 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/2598c033a95d4b61d5f5ab3da5f1414f25c510d6) | |
| 34 | 35 | |
| 35 | 36 | ### 🐞 Bug Fixes |
| 36 | 37 | ... | ... |