feat: 权限缓存加载适配
This commit is contained in:
@@ -46,7 +46,6 @@ public class AuthController {
|
||||
private final UserService userService;
|
||||
private final TenantService tenantService;
|
||||
private final TenantProperties tenantProperties;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Operation(summary = "获取验证码")
|
||||
@GetMapping("/captcha")
|
||||
@@ -75,8 +74,8 @@ public class AuthController {
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
// 多租户模式:未指定租户ID,查询该用户名在所有租户下的记录
|
||||
List<User> users = userService.listUsersByUsername(username);
|
||||
// 多租户模式:未指定租户ID,查询该用户名在所有租户下的账户
|
||||
List<User> users = userService.findUserAcrossAllTenants(username);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return Result.failed("用户不存在");
|
||||
|
||||
@@ -48,18 +48,5 @@ public class TenantProperties {
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.youlai.boot.security.service;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -24,6 +26,7 @@ import java.util.*;
|
||||
public class PermissionService {
|
||||
|
||||
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 角色编码集合
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
public Set<String> getRolePermsFormCache(Set<String> roleCodes) {
|
||||
// 检查输入是否为空
|
||||
if (CollectionUtil.isEmpty(roleCodes)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// 获取当前租户ID并构建缓存Key
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
String cacheKey = buildRolePermsCacheKey(tenantId);
|
||||
|
||||
Set<String> perms = new HashSet<>();
|
||||
// 从缓存中一次性获取所有角色的权限
|
||||
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) {
|
||||
if (rolePermsObj instanceof Set) {
|
||||
|
||||
@@ -43,12 +43,12 @@ public class TenantController {
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
@Operation(summary = "获取当前用户的租户列表")
|
||||
@Operation(summary = "获取当前用户可访问的租户列表")
|
||||
@GetMapping
|
||||
public Result<List<TenantVO>> getTenantList() {
|
||||
public Result<List<TenantVO>> getAccessibleTenants() {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
List<TenantVO> tenantList = tenantService.getTenantListByUserId(userId);
|
||||
log.info("获取用户 {} 的租户列表,共 {} 个租户", userId, tenantList.size());
|
||||
List<TenantVO> tenantList = tenantService.getAccessibleTenants(userId);
|
||||
log.debug("用户 {} 可访问 {} 个租户", userId, tenantList.size());
|
||||
return Result.success(tenantList);
|
||||
}
|
||||
|
||||
@@ -88,11 +88,10 @@ public class TenantController {
|
||||
|
||||
log.info("用户 {} 请求切换租户:{} -> {}", userId, fromTenantId, tenantId);
|
||||
|
||||
// 验证用户是否有权限访问该租户
|
||||
boolean hasPermission = tenantService.hasTenantPermission(userId, tenantId);
|
||||
if (!hasPermission) {
|
||||
log.warn("用户 {} 无权限访问租户 {}", userId, tenantId);
|
||||
return Result.failed("无权限访问该租户");
|
||||
// 验证用户是否可以访问该租户
|
||||
if (!tenantService.canAccessTenant(userId, tenantId)) {
|
||||
log.warn("用户 {} 无权访问租户 {}", userId, tenantId);
|
||||
return Result.failed("无权访问该租户");
|
||||
}
|
||||
|
||||
// 验证租户是否存在且正常
|
||||
|
||||
@@ -13,6 +13,11 @@ import java.util.Set;
|
||||
@Data
|
||||
public class RolePermsBO {
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,13 @@ public class User extends BaseEntity {
|
||||
*/
|
||||
private String username;
|
||||
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
|
||||
/**
|
||||
* 性别((1-男 2-女 0-保密)
|
||||
*/
|
||||
|
||||
@@ -15,12 +15,15 @@ import java.util.List;
|
||||
public interface TenantService extends IService<Tenant> {
|
||||
|
||||
/**
|
||||
* 根据用户ID查询用户所属的租户列表
|
||||
* 获取用户可访问的租户列表
|
||||
* <p>
|
||||
* 通过用户名查询该用户在所有租户下的账户,返回可访问的租户列表
|
||||
* </p>
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 租户列表
|
||||
* @return 可访问的租户列表
|
||||
*/
|
||||
List<TenantVO> getTenantListByUserId(Long userId);
|
||||
List<TenantVO> getAccessibleTenants(Long userId);
|
||||
|
||||
/**
|
||||
* 根据租户ID查询租户信息
|
||||
@@ -39,11 +42,14 @@ public interface TenantService extends IService<Tenant> {
|
||||
Long getTenantIdByDomain(String domain);
|
||||
|
||||
/**
|
||||
* 验证用户是否有权限访问指定租户
|
||||
* 检查用户是否可以访问指定租户
|
||||
* <p>
|
||||
* 验证该用户名在目标租户下是否存在账户
|
||||
* </p>
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param tenantId 租户ID
|
||||
* @return true-有权限,false-无权限
|
||||
* @return true-可访问,false-不可访问
|
||||
*/
|
||||
boolean hasTenantPermission(Long userId, Long tenantId);
|
||||
boolean canAccessTenant(Long userId, Long tenantId);
|
||||
}
|
||||
|
||||
@@ -83,12 +83,15 @@ public interface UserService extends IService<User> {
|
||||
UserAuthCredentials getAuthCredentialsByUsernameAndTenant(String username, Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据用户名查询该用户在所有租户下的记录(用于多租户登录时判断是否需要选择租户)
|
||||
* 跨租户查询用户账户列表
|
||||
* <p>
|
||||
* 查询该用户名在所有租户下的账户记录,用于多租户登录时判断是否需要选择租户
|
||||
* </p>
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 用户列表(每个租户一条记录)
|
||||
* @return 用户账户列表(每个租户一条记录)
|
||||
*/
|
||||
List<User> listUsersByUsername(String username);
|
||||
List<User> findUserAcrossAllTenants(String username);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,11 @@ package com.youlai.boot.system.service.impl;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
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.model.bo.RolePermsBO;
|
||||
import com.youlai.boot.system.model.entity.RoleMenu;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.system.service.RoleMenuService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -18,7 +19,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 角色菜单服务实现类
|
||||
* 角色菜单服务实现类(多租户优化版)
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.5.0
|
||||
@@ -29,76 +30,159 @@ import java.util.Set;
|
||||
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
/**
|
||||
* 初始化权限缓存
|
||||
* 构建租户权限缓存key
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 缓存key
|
||||
* - 多租户开启: system:role:perms:{tenantId}
|
||||
* - 多租户关闭: system:role:perms
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initRolePermsCache() {
|
||||
log.info("初始化权限缓存... ");
|
||||
refreshRolePermsCache();
|
||||
private String buildRolePermsCacheKey(Long tenantId) {
|
||||
// 判断是否启用多租户
|
||||
if (!tenantProperties.getEnabled() || tenantId == null) {
|
||||
// 单租户模式或多租户未开启:使用原有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
|
||||
public void refreshRolePermsCache() {
|
||||
// 清理权限缓存
|
||||
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, "*");
|
||||
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
String cacheKey = buildRolePermsCacheKey(tenantId);
|
||||
|
||||
// 清理当前租户权限缓存
|
||||
redisTemplate.delete(cacheKey);
|
||||
|
||||
// 重新加载当前租户权限
|
||||
List<RolePermsBO> list = this.baseMapper.getRolePermsList(null);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
list.forEach(item -> {
|
||||
String roleCode = item.getRoleCode();
|
||||
Set<String> perms = item.getPerms();
|
||||
if (CollectionUtil.isNotEmpty(perms)) {
|
||||
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, roleCode, perms);
|
||||
redisTemplate.opsForHash().put(cacheKey, roleCode, perms);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
if (tenantId == null) {
|
||||
log.info("权限缓存刷新完成(单租户模式)");
|
||||
} else {
|
||||
log.info("租户[{}]权限缓存刷新完成", tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存 (角色编码变更时调用)
|
||||
* 刷新单个角色权限缓存
|
||||
*/
|
||||
@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
|
||||
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);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
if (rolePerms != null) {
|
||||
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
|
||||
public Set<String> getRolePermsByRoleCodes(Set<String> roles) {
|
||||
// 直接查询数据库(保持原有逻辑)
|
||||
return this.baseMapper.listRolePerms(roles);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> impleme
|
||||
private final UserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public List<TenantVO> getTenantListByUserId(Long userId) {
|
||||
public List<TenantVO> getAccessibleTenants(Long userId) {
|
||||
// 临时忽略租户过滤,查询所有租户
|
||||
TenantContextHolder.setIgnoreTenant(true);
|
||||
try {
|
||||
@@ -123,7 +123,7 @@ public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTenantPermission(Long userId, Long tenantId) {
|
||||
public boolean canAccessTenant(Long userId, Long tenantId) {
|
||||
TenantContextHolder.setIgnoreTenant(true);
|
||||
try {
|
||||
// 先根据用户ID查询用户信息(获取 username)
|
||||
|
||||
@@ -271,15 +271,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> listUsersByUsername(String username) {
|
||||
// 临时忽略租户过滤,查询该用户名在所有租户下的记录
|
||||
public List<User> findUserAcrossAllTenants(String username) {
|
||||
// 临时忽略租户过滤,查询该用户名在所有租户下的账户记录
|
||||
TenantContextHolder.setIgnoreTenant(true);
|
||||
try {
|
||||
return this.list(
|
||||
new LambdaQueryWrapper<User>()
|
||||
.eq(User::getUsername, username)
|
||||
.eq(User::getIsDeleted, 0)
|
||||
.orderByAsc(User::getTenantId) // 按租户ID排序,优先返回较小的租户ID
|
||||
.orderByAsc(User::getTenantId)
|
||||
);
|
||||
} finally {
|
||||
TenantContextHolder.setIgnoreTenant(false);
|
||||
|
||||
Reference in New Issue
Block a user