feat: 权限缓存加载适配

This commit is contained in:
Ray.Hao
2025-12-12 22:26:43 +08:00
parent 0a594e2ce1
commit 3f05f77351
15 changed files with 210 additions and 91 deletions

View File

@@ -67,6 +67,13 @@ ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `sys_role` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL; UPDATE `sys_role` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 角色菜单关联表
ALTER TABLE `sys_role_menu`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `role_id`,
ADD INDEX `idx_role_menu_tenant_id` (`tenant_id`);
UPDATE `sys_role_menu` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 部门表 -- 部门表
ALTER TABLE `sys_dept` ALTER TABLE `sys_dept`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`, ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,

View File

@@ -36,6 +36,10 @@ ALTER TABLE `sys_user` ADD UNIQUE KEY `login_name` (`username`);
ALTER TABLE `sys_role` DROP INDEX `idx_tenant_id`; ALTER TABLE `sys_role` DROP INDEX `idx_tenant_id`;
ALTER TABLE `sys_role` DROP COLUMN `tenant_id`; ALTER TABLE `sys_role` DROP COLUMN `tenant_id`;
-- 角色菜单关联表
ALTER TABLE `sys_role_menu` DROP INDEX `idx_role_menu_tenant_id`;
ALTER TABLE `sys_role_menu` DROP COLUMN `tenant_id`;
-- 部门表 -- 部门表
ALTER TABLE `sys_dept` DROP INDEX `idx_tenant_id`; ALTER TABLE `sys_dept` DROP INDEX `idx_tenant_id`;
ALTER TABLE `sys_dept` DROP COLUMN `tenant_id`; ALTER TABLE `sys_dept` DROP COLUMN `tenant_id`;

View File

@@ -46,7 +46,6 @@ public class AuthController {
private final UserService userService; private final UserService userService;
private final TenantService tenantService; private final TenantService tenantService;
private final TenantProperties tenantProperties; private final TenantProperties tenantProperties;
private final PasswordEncoder passwordEncoder;
@Operation(summary = "获取验证码") @Operation(summary = "获取验证码")
@GetMapping("/captcha") @GetMapping("/captcha")
@@ -75,8 +74,8 @@ public class AuthController {
return Result.success(authenticationToken); return Result.success(authenticationToken);
} }
// 多租户模式未指定租户ID查询该用户名在所有租户下的记录 // 多租户模式未指定租户ID查询该用户名在所有租户下的账户
List<User> users = userService.listUsersByUsername(username); List<User> users = userService.findUserAcrossAllTenants(username);
if (users.isEmpty()) { if (users.isEmpty()) {
return Result.failed("用户不存在"); return Result.failed("用户不存在");

View File

@@ -48,18 +48,5 @@ public class TenantProperties {
*/ */
private String headerName = "tenant-id"; private String headerName = "tenant-id";
/**
* 初始化默认忽略的表
*/
public TenantProperties() {
// 系统表默认忽略多租户
ignoreTables.add("sys_tenant");
ignoreTables.add("sys_dict");
ignoreTables.add("sys_dict_item");
ignoreTables.add("sys_config");
// 代码生成表(平台共用,不做租户隔离)
ignoreTables.add("gen_table");
ignoreTables.add("gen_table_column");
}
} }

View File

@@ -3,6 +3,8 @@ package com.youlai.boot.security.service;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.config.property.TenantProperties;
import com.youlai.boot.security.util.SecurityUtils; import com.youlai.boot.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -24,6 +26,7 @@ import java.util.*;
public class PermissionService { public class PermissionService {
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
private final TenantProperties tenantProperties;
/** /**
* 判断当前登录用户是否拥有操作权限 * 判断当前登录用户是否拥有操作权限
@@ -67,21 +70,36 @@ public class PermissionService {
/** /**
* 从缓存中获取角色权限列表 * 构建租户权限缓存key
*
* @param tenantId 租户ID
* @return 缓存key
*/
private String buildRolePermsCacheKey(Long tenantId) {
if (!tenantProperties.getEnabled() || tenantId == null) {
return RedisConstants.System.ROLE_PERMS;
}
return RedisConstants.System.ROLE_PERMS + ":" + tenantId;
}
/**
* 从缓存中获取角色权限列表(兼容单租户和多租户)
* *
* @param roleCodes 角色编码集合 * @param roleCodes 角色编码集合
* @return 角色权限列表 * @return 角色权限列表
*/ */
public Set<String> getRolePermsFormCache(Set<String> roleCodes) { public Set<String> getRolePermsFormCache(Set<String> roleCodes) {
// 检查输入是否为空
if (CollectionUtil.isEmpty(roleCodes)) { if (CollectionUtil.isEmpty(roleCodes)) {
return Collections.emptySet(); return Collections.emptySet();
} }
// 获取当前租户ID并构建缓存Key
Long tenantId = TenantContextHolder.getTenantId();
String cacheKey = buildRolePermsCacheKey(tenantId);
Set<String> perms = new HashSet<>(); Set<String> perms = new HashSet<>();
// 从缓存中一次性获取所有角色的权限
Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes); Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes);
List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(RedisConstants.System.ROLE_PERMS, roleCodesAsObjects); List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(cacheKey, roleCodesAsObjects);
for (Object rolePermsObj : rolePermsList) { for (Object rolePermsObj : rolePermsList) {
if (rolePermsObj instanceof Set) { if (rolePermsObj instanceof Set) {

View File

@@ -43,12 +43,12 @@ public class TenantController {
* *
* @return 租户列表 * @return 租户列表
*/ */
@Operation(summary = "获取当前用户的租户列表") @Operation(summary = "获取当前用户可访问的租户列表")
@GetMapping @GetMapping
public Result<List<TenantVO>> getTenantList() { public Result<List<TenantVO>> getAccessibleTenants() {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
List<TenantVO> tenantList = tenantService.getTenantListByUserId(userId); List<TenantVO> tenantList = tenantService.getAccessibleTenants(userId);
log.info("获取用户 {} 的租户列表,共 {} 个租户", userId, tenantList.size()); log.debug("用户 {} 可访问 {} 个租户", userId, tenantList.size());
return Result.success(tenantList); return Result.success(tenantList);
} }
@@ -88,11 +88,10 @@ public class TenantController {
log.info("用户 {} 请求切换租户:{} -> {}", userId, fromTenantId, tenantId); log.info("用户 {} 请求切换租户:{} -> {}", userId, fromTenantId, tenantId);
// 验证用户是否有权限访问该租户 // 验证用户是否可以访问该租户
boolean hasPermission = tenantService.hasTenantPermission(userId, tenantId); if (!tenantService.canAccessTenant(userId, tenantId)) {
if (!hasPermission) { log.warn("用户 {} 无权访问租户 {}", userId, tenantId);
log.warn("用户 {} 无权访问租户 {}", userId, tenantId); return Result.failed("无权访问租户");
return Result.failed("无权限访问该租户");
} }
// 验证租户是否存在且正常 // 验证租户是否存在且正常

View File

@@ -13,6 +13,11 @@ import java.util.Set;
@Data @Data
public class RolePermsBO { public class RolePermsBO {
/**
* 租户ID
*/
private Long tenantId;
/** /**
* 角色编码 * 角色编码
*/ */

View File

@@ -18,11 +18,13 @@ public class User extends BaseEntity {
*/ */
private String username; private String username;
/** /**
* 昵称 * 昵称
*/ */
private String nickname; private String nickname;
/** /**
* 性别((1-男 2-女 0-保密) * 性别((1-男 2-女 0-保密)
*/ */

View File

@@ -15,12 +15,15 @@ import java.util.List;
public interface TenantService extends IService<Tenant> { public interface TenantService extends IService<Tenant> {
/** /**
* 根据用户ID查询用户所属的租户列表 * 获取用户可访问的租户列表
* <p>
* 通过用户名查询该用户在所有租户下的账户,返回可访问的租户列表
* </p>
* *
* @param userId 用户ID * @param userId 用户ID
* @return 租户列表 * @return 可访问的租户列表
*/ */
List<TenantVO> getTenantListByUserId(Long userId); List<TenantVO> getAccessibleTenants(Long userId);
/** /**
* 根据租户ID查询租户信息 * 根据租户ID查询租户信息
@@ -39,11 +42,14 @@ public interface TenantService extends IService<Tenant> {
Long getTenantIdByDomain(String domain); Long getTenantIdByDomain(String domain);
/** /**
* 验证用户是否有权限访问指定租户 * 检查用户是否可以访问指定租户
* <p>
* 验证该用户名在目标租户下是否存在账户
* </p>
* *
* @param userId 用户ID * @param userId 用户ID
* @param tenantId 租户ID * @param tenantId 租户ID
* @return true-有权限false-无权限 * @return true-可访问false-不可访问
*/ */
boolean hasTenantPermission(Long userId, Long tenantId); boolean canAccessTenant(Long userId, Long tenantId);
} }

View File

@@ -83,12 +83,15 @@ public interface UserService extends IService<User> {
UserAuthCredentials getAuthCredentialsByUsernameAndTenant(String username, Long tenantId); UserAuthCredentials getAuthCredentialsByUsernameAndTenant(String username, Long tenantId);
/** /**
* 根据用户名查询用户在所有租户下的记录(用于多租户登录时判断是否需要选择租户) * 跨租户查询用户账户列表
* <p>
* 查询该用户名在所有租户下的账户记录,用于多租户登录时判断是否需要选择租户
* </p>
* *
* @param username 用户名 * @param username 用户名
* @return 用户列表(每个租户一条记录) * @return 用户账户列表(每个租户一条记录)
*/ */
List<User> listUsersByUsername(String username); List<User> findUserAcrossAllTenants(String username);
/** /**

View File

@@ -3,10 +3,11 @@ package com.youlai.boot.system.service.impl;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.config.property.TenantProperties;
import com.youlai.boot.system.mapper.RoleMenuMapper; import com.youlai.boot.system.mapper.RoleMenuMapper;
import com.youlai.boot.system.model.bo.RolePermsBO; import com.youlai.boot.system.model.bo.RolePermsBO;
import com.youlai.boot.system.model.entity.RoleMenu; import com.youlai.boot.system.model.entity.RoleMenu;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.system.service.RoleMenuService; import com.youlai.boot.system.service.RoleMenuService;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -18,7 +19,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* 角色菜单服务实现类 * 角色菜单服务实现类(多租户优化版)
* *
* @author Ray.Hao * @author Ray.Hao
* @since 2.5.0 * @since 2.5.0
@@ -29,76 +30,159 @@ import java.util.Set;
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService { public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService {
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
private final TenantProperties tenantProperties;
/** /**
* 初始化权限缓存 * 构建租户权限缓存key
*
* @param tenantId 租户ID
* @return 缓存key
* - 多租户开启: system:role:perms:{tenantId}
* - 多租户关闭: system:role:perms
*/ */
@PostConstruct private String buildRolePermsCacheKey(Long tenantId) {
public void initRolePermsCache() { // 判断是否启用多租户
log.info("初始化权限缓存... "); if (!tenantProperties.getEnabled() || tenantId == null) {
refreshRolePermsCache(); // 单租户模式或多租户未开启使用原有Key
return RedisConstants.System.ROLE_PERMS;
}
// 多租户模式开启Key按租户隔离
return RedisConstants.System.ROLE_PERMS + ":" + tenantId;
} }
/** /**
* 刷新权限缓存 * 启动时初始化权限缓存
*/
@PostConstruct
public void initRolePermsCache() {
log.info("开始初始化权限缓存...");
List<RolePermsBO> allRolePermsList = this.baseMapper.getRolePermsList(null);
if (CollectionUtil.isEmpty(allRolePermsList)) {
log.warn("权限数据为空,跳过缓存初始化");
return;
}
if (tenantProperties.getEnabled()) {
// 多租户模式:按租户分组缓存
allRolePermsList.forEach(rolePerms -> {
Long tenantId = rolePerms.getTenantId();
if (tenantId == null) {
log.warn("多租户模式下,角色[{}]缺少tenantId跳过", rolePerms.getRoleCode());
return;
}
String cacheKey = RedisConstants.System.ROLE_PERMS + ":" + tenantId;
String roleCode = rolePerms.getRoleCode();
Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(cacheKey, roleCode, perms);
}
});
log.info("权限缓存初始化完成(多租户模式),共{}条数据", allRolePermsList.size());
} else {
// 单租户模式:所有数据统一缓存
String cacheKey = RedisConstants.System.ROLE_PERMS;
allRolePermsList.forEach(rolePerms -> {
String roleCode = rolePerms.getRoleCode();
Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(cacheKey, roleCode, perms);
}
});
log.info("权限缓存初始化完成(单租户模式),共{}条数据", allRolePermsList.size());
}
}
/**
* 刷新当前租户权限缓存
*/ */
@Override @Override
public void refreshRolePermsCache() { public void refreshRolePermsCache() {
// 清理权限缓存 Long tenantId = TenantContextHolder.getTenantId();
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, "*"); String cacheKey = buildRolePermsCacheKey(tenantId);
// 清理当前租户权限缓存
redisTemplate.delete(cacheKey);
// 重新加载当前租户权限
List<RolePermsBO> list = this.baseMapper.getRolePermsList(null); List<RolePermsBO> list = this.baseMapper.getRolePermsList(null);
if (CollectionUtil.isNotEmpty(list)) { if (CollectionUtil.isNotEmpty(list)) {
list.forEach(item -> { list.forEach(item -> {
String roleCode = item.getRoleCode(); String roleCode = item.getRoleCode();
Set<String> perms = item.getPerms(); Set<String> perms = item.getPerms();
if (CollectionUtil.isNotEmpty(perms)) { if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, roleCode, perms); redisTemplate.opsForHash().put(cacheKey, roleCode, perms);
} }
}); });
} }
}
if (tenantId == null) {
/** log.info("权限缓存刷新完成(单租户模式)");
* 刷新权限缓存 } else {
*/ log.info("租户[{}]权限缓存刷新完成", tenantId);
@Override
public void refreshRolePermsCache(String roleCode) {
// 清理权限缓存
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, roleCode);
List<RolePermsBO> list = this.baseMapper.getRolePermsList(roleCode);
if (CollectionUtil.isNotEmpty(list)) {
RolePermsBO rolePerms = list.get(0);
if (rolePerms == null) {
return;
}
Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, roleCode, perms);
}
} }
} }
/** /**
* 刷新权限缓存 (角色编码变更时调用) * 刷新单个角色权限缓存
*/
@Override
public void refreshRolePermsCache(String roleCode) {
Long tenantId = TenantContextHolder.getTenantId();
String cacheKey = buildRolePermsCacheKey(tenantId);
// 清理指定角色缓存
redisTemplate.opsForHash().delete(cacheKey, roleCode);
// 重新加载指定角色权限
List<RolePermsBO> list = this.baseMapper.getRolePermsList(roleCode);
if (CollectionUtil.isNotEmpty(list)) {
RolePermsBO rolePerms = list.get(0);
if (rolePerms != null) {
Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(cacheKey, roleCode, perms);
}
}
}
if (tenantId == null) {
log.info("角色[{}]权限缓存刷新完成(单租户模式)", roleCode);
} else {
log.info("租户[{}]角色[{}]权限缓存刷新完成", tenantId, roleCode);
}
}
/**
* 刷新权限缓存(角色编码变更时调用)
*/ */
@Override @Override
public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) { public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) {
Long tenantId = TenantContextHolder.getTenantId();
String cacheKey = buildRolePermsCacheKey(tenantId);
// 清理旧角色权限缓存 // 清理旧角色权限缓存
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, oldRoleCode); redisTemplate.opsForHash().delete(cacheKey, oldRoleCode);
// 添加新角色权限缓存 // 添加新角色权限缓存
List<RolePermsBO> list = this.baseMapper.getRolePermsList(newRoleCode); List<RolePermsBO> list = this.baseMapper.getRolePermsList(newRoleCode);
if (CollectionUtil.isNotEmpty(list)) { if (CollectionUtil.isNotEmpty(list)) {
RolePermsBO rolePerms = list.get(0); RolePermsBO rolePerms = list.get(0);
if (rolePerms == null) { if (rolePerms != null) {
return; Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(cacheKey, newRoleCode, perms);
}
} }
}
Set<String> perms = rolePerms.getPerms();
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, newRoleCode, perms); if (tenantId == null) {
log.info("角色编码变更: {} -> {},权限缓存已更新(单租户模式)", oldRoleCode, newRoleCode);
} else {
log.info("租户[{}]角色编码变更: {} -> {},权限缓存已更新", tenantId, oldRoleCode, newRoleCode);
} }
} }
@@ -110,6 +194,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
*/ */
@Override @Override
public Set<String> getRolePermsByRoleCodes(Set<String> roles) { public Set<String> getRolePermsByRoleCodes(Set<String> roles) {
// 直接查询数据库(保持原有逻辑)
return this.baseMapper.listRolePerms(roles); return this.baseMapper.listRolePerms(roles);
} }

View File

@@ -32,7 +32,7 @@ public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> impleme
private final UserMapper userMapper; private final UserMapper userMapper;
@Override @Override
public List<TenantVO> getTenantListByUserId(Long userId) { public List<TenantVO> getAccessibleTenants(Long userId) {
// 临时忽略租户过滤,查询所有租户 // 临时忽略租户过滤,查询所有租户
TenantContextHolder.setIgnoreTenant(true); TenantContextHolder.setIgnoreTenant(true);
try { try {
@@ -123,7 +123,7 @@ public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> impleme
} }
@Override @Override
public boolean hasTenantPermission(Long userId, Long tenantId) { public boolean canAccessTenant(Long userId, Long tenantId) {
TenantContextHolder.setIgnoreTenant(true); TenantContextHolder.setIgnoreTenant(true);
try { try {
// 先根据用户ID查询用户信息获取 username // 先根据用户ID查询用户信息获取 username

View File

@@ -271,15 +271,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
} }
@Override @Override
public List<User> listUsersByUsername(String username) { public List<User> findUserAcrossAllTenants(String username) {
// 临时忽略租户过滤,查询该用户名在所有租户下的记录 // 临时忽略租户过滤,查询该用户名在所有租户下的账户记录
TenantContextHolder.setIgnoreTenant(true); TenantContextHolder.setIgnoreTenant(true);
try { try {
return this.list( return this.list(
new LambdaQueryWrapper<User>() new LambdaQueryWrapper<User>()
.eq(User::getUsername, username) .eq(User::getUsername, username)
.eq(User::getIsDeleted, 0) .eq(User::getIsDeleted, 0)
.orderByAsc(User::getTenantId) // 按租户ID排序优先返回较小的租户ID .orderByAsc(User::getTenantId)
); );
} finally { } finally {
TenantContextHolder.setIgnoreTenant(false); TenantContextHolder.setIgnoreTenant(false);

View File

@@ -288,9 +288,11 @@ youlai:
# 忽略多租户过滤的表名列表(系统表、租户表等不需要租户隔离的表) # 忽略多租户过滤的表名列表(系统表、租户表等不需要租户隔离的表)
ignore-tables: ignore-tables:
- sys_tenant # 租户表本身 - sys_tenant # 租户表本身
- sys_menu # 菜单表(功能入口定义,所有租户共享) - sys_menu # 菜单表(功能入口定义,所有租户共享)
- sys_dict # 字典表(通常共享) - sys_dict # 字典表(通常共享)
- sys_dict_item # 字典项表(通常共享) - sys_dict_item # 字典项表(通常共享)
- sys_config # 系统配置表(通常共享) - sys_config # 系统配置表(通常共享)
- gen_table # 代码生成表(平台共用)
- gen_table_column # 代码生成字段表(平台共用)
# ============================================ # ============================================

View File

@@ -17,6 +17,7 @@
<!-- 权限和拥有权限的角色的映射 --> <!-- 权限和拥有权限的角色的映射 -->
<resultMap id="PremRolesMap" type="com.youlai.boot.system.model.bo.RolePermsBO"> <resultMap id="PremRolesMap" type="com.youlai.boot.system.model.bo.RolePermsBO">
<result property="tenantId" column="tenant_id"/>
<result property="roleCode" column="role_code"/> <result property="roleCode" column="role_code"/>
<collection property="perms" ofType="string" javaType="java.util.Set"> <collection property="perms" ofType="string" javaType="java.util.Set">
<result column="perm"/> <result column="perm"/>
@@ -26,8 +27,9 @@
<!-- 获取权限和拥有权限的角色列表 --> <!-- 获取权限和拥有权限的角色列表 -->
<select id="getRolePermsList" resultMap="PremRolesMap"> <select id="getRolePermsList" resultMap="PremRolesMap">
SELECT SELECT
t3.perm, t2.tenant_id,
t2.`code` role_code t2.`code` role_code,
t3.perm
FROM FROM
`sys_role_menu` t1 `sys_role_menu` t1
INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.is_deleted = 0 AND t2.`status` = 1 INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.is_deleted = 0 AND t2.`status` = 1