refactor: 会话失效、数据权限和实时推送重构

This commit is contained in:
Ray.Hao
2026-02-12 17:19:42 +08:00
parent 3a35b24476
commit faf6754bf4
44 changed files with 2145 additions and 515 deletions

View File

@@ -120,4 +120,14 @@ public class RoleController {
roleService.assignMenusToRole(roleId, menuIds);
return Result.success();
}
@Operation(summary = "获取角色的部门ID集合(自定义数据权限)")
@GetMapping("/{roleId}/dept-ids")
@PreAuthorize("@ss.hasPerm('sys:role:update')")
public Result<List<Long>> getRoleDeptIds(
@Parameter(description = "角色ID") @PathVariable Long roleId
) {
List<Long> deptIds = roleService.getRoleDeptIds(roleId);
return Result.success(deptIds);
}
}

View File

@@ -1,35 +0,0 @@
package com.youlai.boot.system.handler;
import com.youlai.boot.system.service.UserOnlineService;
import com.youlai.boot.platform.websocket.publisher.WebSocketPublisher;
import com.youlai.boot.platform.websocket.topic.WebSocketTopics;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 在线用户定时任务
*
* @since 2024/10/7
*
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class OnlineUserJobHandler {
private final UserOnlineService userOnlineService;
private final WebSocketPublisher webSocketPublisher;
// 每3分钟统计一次在线用户数减少服务器压力
@Scheduled(cron = "0 */3 * * * ?")
public void execute() {
log.info("定时任务:统计在线用户数");
// 推送在线用户数量到新主题
int count = userOnlineService.getOnlineUserCount();
webSocketPublisher.publish(WebSocketTopics.TOPIC_ONLINE_COUNT, count);
}
}

View File

@@ -0,0 +1,35 @@
package com.youlai.boot.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.system.model.entity.RoleDept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色部门关联持久层
*
* @author Ray.Hao
* @since 3.0.0
*/
@Mapper
public interface RoleDeptMapper extends BaseMapper<RoleDept> {
/**
* 根据角色ID获取部门ID列表
*
* @param roleId 角色ID
* @return 部门ID列表
*/
List<Long> getDeptIdsByRoleId(@Param("roleId") Long roleId);
/**
* 根据角色编码集合获取所有部门ID列表用于自定义数据权限
*
* @param roleCodes 角色编码集合
* @return 部门ID列表
*/
List<Long> getDeptIdsByRoleCodes(@Param("roleCodes") List<String> roleCodes);
}

View File

@@ -3,7 +3,10 @@ package com.youlai.boot.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.system.model.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -22,4 +25,15 @@ public interface RoleMapper extends BaseMapper<Role> {
* @return {@link Integer} 数据权限范围
*/
Integer getMaximumDataScope(Set<String> roles);
/**
* 获取角色的数据权限信息列表
* <p>
* 返回角色编码和数据权限范围的映射列表
*
* @param roleCodes 角色编码集合
* @return 角色数据权限信息列表 [{code: 'ADMIN', data_scope: 1}, ...]
*/
List<Map<String, Object>> getRoleDataScopeList(@Param("roleCodes") Set<String> roleCodes);
}

View File

@@ -0,0 +1,32 @@
package com.youlai.boot.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 角色部门关联实体
* <p>
* 用于存储角色自定义数据权限时可访问的部门ID列表
*
* @author Ray.Hao
* @since 3.0.0
*/
@TableName("sys_role_dept")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RoleDept {
/**
* 角色ID
*/
private Long roleId;
/**
* 部门ID
*/
private Long deptId;
}

View File

@@ -7,6 +7,8 @@ import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import java.util.List;
@Schema(description = "角色表单对象")
@Data
public class RoleForm {
@@ -29,7 +31,10 @@ public class RoleForm {
@Range(max = 1, min = 0, message = "角色状态不正确")
private Integer status;
@Schema(description="数据权限")
@Schema(description="数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)")
private Integer dataScope;
@Schema(description="自定义数据权限部门ID列表(当dataScope=5时有效)")
private List<Long> deptIds;
}

View File

@@ -0,0 +1,47 @@
package com.youlai.boot.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.system.model.entity.RoleDept;
import java.util.List;
/**
* 角色部门关联服务接口
*
* @author Ray.Hao
* @since 4.1.0
*/
public interface RoleDeptService extends IService<RoleDept> {
/**
* 根据角色ID获取部门ID列表
*
* @param roleId 角色ID
* @return 部门ID列表
*/
List<Long> getDeptIdsByRoleId(Long roleId);
/**
* 根据角色编码集合获取所有部门ID列表用于自定义数据权限
*
* @param roleCodes 角色编码集合
* @return 部门ID列表
*/
List<Long> getDeptIdsByRoleCodes(List<String> roleCodes);
/**
* 保存角色部门关联
*
* @param roleId 角色ID
* @param deptIds 部门ID列表
*/
void saveRoleDepts(Long roleId, List<Long> deptIds);
/**
* 删除角色部门关联
*
* @param roleId 角色ID
*/
void deleteByRoleId(Long roleId);
}

View File

@@ -10,7 +10,7 @@ import java.util.Set;
/**
* 角色菜单业务接口
*
* @author haoxr
* @author Ray.Hao
* @since 2.5.0
*/
public interface RoleMenuService extends IService<RoleMenu> {
@@ -45,10 +45,12 @@ public interface RoleMenuService extends IService<RoleMenu> {
void refreshRolePermsCache(String oldRoleCode, String newRoleCode);
/**
* 获取角色权限集合
* 获取角色权限集合(带缓存)
* <p>
* 采用 Read-Through 缓存策略,缓存未命中时自动回源数据库
*
* @param roles 角色编码集合
* @param roleCodes 角色编码集合
* @return 权限集合
*/
Set<String> getRolePermsByRoleCodes(Set<String> roles);
Set<String> getRolePermsByRoleCodes(Set<String> roleCodes);
}

View File

@@ -3,6 +3,7 @@ package com.youlai.boot.system.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.security.model.RoleDataScope;
import com.youlai.boot.system.model.entity.Role;
import com.youlai.boot.common.model.Option;
import com.youlai.boot.system.model.form.RoleForm;
@@ -91,5 +92,22 @@ public interface RoleService extends IService<Role> {
*/
Integer getMaximumDataScope(Set<String> roles);
/**
* 获取角色的部门ID列表自定义数据权限
*
* @param roleId 角色ID
* @return 部门ID列表
*/
List<Long> getRoleDeptIds(Long roleId);
/**
* 获取用户所有角色的数据权限列表
* <p>
* 用于实现多角色数据权限合并(并集策略)
*
* @param roleCodes 角色编码集合
* @return 角色数据权限列表
*/
List<RoleDataScope> getRoleDataScopes(Set<String> roleCodes);
}

View File

@@ -13,7 +13,7 @@ import java.util.List;
/**
* 用户公告状态服务类
*
* @author youlaitech
* @author Theo
* @since 2024-08-28 16:56
*/
public interface UserNoticeService extends IService<UserNotice> {

View File

@@ -1,145 +0,0 @@
package com.youlai.boot.system.service;
import com.youlai.boot.platform.websocket.publisher.WebSocketPublisher;
import com.youlai.boot.platform.websocket.topic.WebSocketTopics;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 用户在线状态服务
* 负责维护用户的在线状态和相关统计
*
* @author Ray.Hao
* @since 3.0.0
*/
@Service
@Slf4j
public class UserOnlineService {
// 在线用户映射表key为用户名value为用户在线信息
private final Map<String, UserOnlineInfo> onlineUsers = new ConcurrentHashMap<>();
private final WebSocketPublisher webSocketPublisher;
public UserOnlineService(WebSocketPublisher webSocketPublisher) {
this.webSocketPublisher = webSocketPublisher;
}
/**
* 用户上线
*
* @param username 用户名
* @param sessionId WebSocket会话ID可选
*/
public void userConnected(String username, String sessionId) {
// 生成会话ID如果未提供
String actualSessionId = sessionId != null ? sessionId : "session-" + System.nanoTime();
UserOnlineInfo info = new UserOnlineInfo(username, actualSessionId, System.currentTimeMillis());
onlineUsers.put(username, info);
log.info("用户[{}]上线,当前在线用户数:{}", username, onlineUsers.size());
// 通知在线用户状态变更
notifyOnlineUsersChange();
}
/**
* 用户下线
*
* @param username 用户名
*/
public void userDisconnected(String username) {
onlineUsers.remove(username);
log.info("用户[{}]下线,当前在线用户数:{}", username, onlineUsers.size());
// 通知在线用户状态变更
notifyOnlineUsersChange();
}
/**
* 获取在线用户列表
*
* @return 在线用户名列表
*/
public List<UserOnlineDTO> getOnlineUsers() {
return onlineUsers.values().stream()
.map(info -> new UserOnlineDTO(info.getUsername(), info.getLoginTime()))
.collect(Collectors.toList());
}
/**
* 获取在线用户数量
*
* @return 在线用户数
*/
public int getOnlineUserCount() {
return onlineUsers.size();
}
/**
* 检查用户是否在线
*
* @param username 用户名
* @return 是否在线
*/
public boolean isUserOnline(String username) {
return onlineUsers.containsKey(username);
}
/**
* 通知所有客户端在线用户变更
*/
private void notifyOnlineUsersChange() {
// 发送简化版数据(仅数量)
sendOnlineUserCount();
}
/**
* 发送在线用户数量(简化版,不包含用户详情)
*/
private void sendOnlineUserCount() {
try {
// 直接发送数量,更轻量
int count = onlineUsers.size();
webSocketPublisher.publish(WebSocketTopics.TOPIC_ONLINE_COUNT, count);
log.debug("已发送在线用户数量: {}", count);
} catch (Exception e) {
log.error("发送在线用户数量失败", e);
}
}
/**
* 用户在线信息
*/
@Data
private static class UserOnlineInfo {
private final String username;
private final String sessionId;
private final long loginTime;
}
/**
* 用户在线Dto用于返回给前端
*/
@Data
public static class UserOnlineDTO {
private final String username;
private final long loginTime;
}
/**
* 在线用户变更事件
*/
@Data
private static class OnlineUsersChangeEvent {
private String type;
private int count;
private List<UserOnlineDTO> users;
private long timestamp;
}
}

View File

@@ -6,13 +6,19 @@ import com.youlai.boot.system.model.entity.UserRole;
import java.util.List;
/**
* 用户角色业务接口
*
* @author Ray.Hao
* @since 0.0.1
*/
public interface UserRoleService extends IService<UserRole> {
/**
* 保存用户角色
*
* @param userId
* @param roleIds
* @param userId 用户ID
* @param roleIds 角色ID列表
* @return
*/
void saveUserRoles(Long userId, List<Long> roleIds);

View File

@@ -25,7 +25,7 @@ public interface UserService extends IService<User> {
/**
* 用户分页列表
*
* @return {@link IPage<UserPageVo>} 用户分页列表
* @return {@link IPage<UserPageVO>} 用户分页列表
*/
IPage<UserPageVO> getUserPage(UserQuery queryParams);
@@ -82,7 +82,7 @@ public interface UserService extends IService<User> {
* 获取导出用户列表
*
* @param queryParams 查询参数
* @return {@link List<UserExportDto>} 导出用户列表
* @return {@link List<UserExportDTO>} 导出用户列表
*/
List<UserExportDTO> listExportUsers(UserQuery queryParams);
@@ -90,14 +90,14 @@ public interface UserService extends IService<User> {
/**
* 获取登录用户信息
*
* @return {@link CurrentUserDto} 登录用户信息
* @return {@link CurrentUserDTO} 登录用户信息
*/
CurrentUserDTO getCurrentUserInfo();
/**
* 获取个人中心用户信息
*
* @return {@link UserProfileVo} 个人中心用户信息
* @return {@link UserProfileVO} 个人中心用户信息
*/
UserProfileVO getUserProfile(Long userId);

View File

@@ -25,10 +25,8 @@ import com.youlai.boot.system.model.vo.UserNoticePageVO;
import com.youlai.boot.system.model.vo.NoticeDetailVO;
import com.youlai.boot.system.service.NoticeService;
import com.youlai.boot.system.service.UserNoticeService;
import com.youlai.boot.system.service.UserOnlineService;
import com.youlai.boot.system.service.UserService;
import com.youlai.boot.platform.websocket.publisher.WebSocketPublisher;
import com.youlai.boot.platform.websocket.topic.WebSocketTopics;
import com.youlai.boot.platform.websocket.service.WebSocketService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -53,14 +51,13 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
private final NoticeConverter noticeConverter;
private final UserNoticeService userNoticeService;
private final UserService userService;
private final WebSocketPublisher webSocketPublisher;
private final UserOnlineService userOnlineService;
private final WebSocketService webSocketService;
/**
* 获取通知公告分页列表
*
* @param queryParams 查询参数
* @return {@link IPage< NoticePageVo >} 通知公告分页列表
* @return {@link IPage< NoticePageVO >} 通知公告分页列表
*/
@Override
public IPage<NoticePageVO> getNoticePage(NoticeQuery queryParams) {
@@ -214,9 +211,10 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
Set<String> receivers = targetUserList.stream().map(User::getUsername).collect(Collectors.toSet());
Set<String> allOnlineUsers = userOnlineService.getOnlineUsers().stream()
.map(UserOnlineService.UserOnlineDTO::getUsername)
.collect(Collectors.toSet());
// 获取在线用户名集合
Set<String> allOnlineUsers = webSocketService.getOnlineUsers().stream()
.map(dto -> dto.getUsername())
.collect(Collectors.toSet());
// 找出在线用户的通知接收者
Set<String> onlineReceivers = new HashSet<>(CollectionUtil.intersection(receivers, allOnlineUsers));
@@ -227,7 +225,8 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
noticeDto.setType(notice.getType());
noticeDto.setPublishTime(notice.getPublishTime());
onlineReceivers.forEach(receiver -> webSocketPublisher.publishToUser(receiver, WebSocketTopics.USER_QUEUE_MESSAGE, noticeDto));
// 向在线接收者推送通知
onlineReceivers.forEach(receiver -> webSocketService.sendNotification(receiver, noticeDto));
}
return publishResult;
}
@@ -268,7 +267,7 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
/**
*
* @param id 通知公告ID
* @return NoticeDetailVo 通知公告详情
* @return NoticeDetailVO 通知公告详情
*/
@Override
public NoticeDetailVO getNoticeDetail(Long id) {

View File

@@ -0,0 +1,65 @@
package com.youlai.boot.system.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.system.mapper.RoleDeptMapper;
import com.youlai.boot.system.model.entity.RoleDept;
import com.youlai.boot.system.service.RoleDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
/**
* 角色部门关联服务实现
*
* @author Ray.Hao
* @since 3.0.0
*/
@Service
@RequiredArgsConstructor
public class RoleDeptServiceImpl extends ServiceImpl<RoleDeptMapper, RoleDept> implements RoleDeptService {
@Override
public List<Long> getDeptIdsByRoleId(Long roleId) {
if (roleId == null) {
return Collections.emptyList();
}
return this.baseMapper.getDeptIdsByRoleId(roleId);
}
@Override
public List<Long> getDeptIdsByRoleCodes(List<String> roleCodes) {
if (CollectionUtil.isEmpty(roleCodes)) {
return Collections.emptyList();
}
return this.baseMapper.getDeptIdsByRoleCodes(roleCodes);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveRoleDepts(Long roleId, List<Long> deptIds) {
if (roleId == null || CollectionUtil.isEmpty(deptIds)) {
return;
}
// 先删除原有关联
this.remove(new LambdaQueryWrapper<RoleDept>().eq(RoleDept::getRoleId, roleId));
// 批量插入新关联
List<RoleDept> roleDepts = deptIds.stream()
.map(deptId -> new RoleDept(roleId, deptId))
.toList();
this.saveBatch(roleDepts);
}
@Override
public void deleteByRoleId(Long roleId) {
if (roleId == null) {
return;
}
this.remove(new LambdaQueryWrapper<RoleDept>().eq(RoleDept::getRoleId, roleId));
}
}

View File

@@ -7,14 +7,13 @@ 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.system.service.RoleMenuService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
* 角色菜单服务实现类
@@ -29,44 +28,17 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
private final RedisTemplate<String, Object> redisTemplate;
/**
* 启动时初始化权限缓存
*/
@PostConstruct
public void initRolePermsCache() {
log.info("开始初始化权限缓存...");
List<RolePermsBO> allRolePermsList = this.baseMapper.getRolePermsList(null);
if (CollectionUtil.isEmpty(allRolePermsList)) {
log.warn("权限数据为空,跳过缓存初始化");
return;
}
// 所有数据统一缓存
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() {
String cacheKey = RedisConstants.System.ROLE_PERMS;
// 清理权限缓存
redisTemplate.delete(cacheKey);
// 重新加载权限
// 预热权限缓存,避免后续请求触发频繁回源
List<RolePermsBO> list = this.baseMapper.getRolePermsList(null);
if (CollectionUtil.isNotEmpty(list)) {
list.forEach(item -> {
@@ -77,7 +49,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
}
});
}
log.info("权限缓存刷新完成");
}
@@ -87,11 +59,11 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
@Override
public void refreshRolePermsCache(String roleCode) {
String cacheKey = RedisConstants.System.ROLE_PERMS;
// 清理指定角色缓存
redisTemplate.opsForHash().delete(cacheKey, roleCode);
// 重新加载指定角色权限
// 回源 DB 并更新缓存
List<RolePermsBO> list = this.baseMapper.getRolePermsList(roleCode);
if (CollectionUtil.isNotEmpty(list)) {
RolePermsBO rolePerms = list.get(0);
@@ -102,7 +74,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
}
}
}
log.info("角色[{}]权限缓存刷新完成", roleCode);
}
@@ -112,11 +84,12 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
@Override
public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) {
String cacheKey = RedisConstants.System.ROLE_PERMS;
// 清理旧角色权限缓存
redisTemplate.opsForHash().delete(cacheKey, oldRoleCode);
// 添加新角色权限缓存
redisTemplate.opsForHash().delete(cacheKey, newRoleCode);
// 回源 DB 并更新新角色编码缓存
List<RolePermsBO> list = this.baseMapper.getRolePermsList(newRoleCode);
if (CollectionUtil.isNotEmpty(list)) {
RolePermsBO rolePerms = list.get(0);
@@ -127,20 +100,72 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
}
}
}
log.info("角色编码变更: {} -> {},权限缓存已更新", oldRoleCode, newRoleCode);
log.info("角色编码变更: {} -> {}相关权限缓存刷新完成", oldRoleCode, newRoleCode);
}
/**
* 获取角色权限集合
* 获取角色权限集合(带缓存)
* <p>
* 采用 Read-Through 缓存策略:
* <ol>
* <li>优先从 Redis Hash 缓存读取</li>
* <li>缓存未命中时回源 DB 并写入缓存</li>
* </ol>
*
* @param roles 角色编码集合
* @param roleCodes 角色编码集合
* @return 权限集合
*/
@Override
public Set<String> getRolePermsByRoleCodes(Set<String> roles) {
// 直接查询数据库(保持原有逻辑)
return this.baseMapper.listRolePerms(roles);
public Set<String> getRolePermsByRoleCodes(Set<String> roleCodes) {
if (CollectionUtil.isEmpty(roleCodes)) {
return Collections.emptySet();
}
String cacheKey = RedisConstants.System.ROLE_PERMS;
Set<String> perms = new HashSet<>();
List<String> roleCodeList = new ArrayList<>(roleCodes);
// 1. 尝试从缓存批量获取
List<Object> cachedPermsList = redisTemplate.opsForHash().multiGet(cacheKey, new ArrayList<>(roleCodeList));
List<String> missingRoles = new ArrayList<>();
for (int i = 0; i < roleCodeList.size(); i++) {
Object cachedPerms = cachedPermsList.get(i);
String roleCode = roleCodeList.get(i);
if (cachedPerms == null) {
// 缓存未命中,记录需要回源的角色
missingRoles.add(roleCode);
continue;
}
// Redis JSON 序列化后Set 会以 Collection 形式反序列化
if (cachedPerms instanceof Collection<?> collection) {
collection.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.forEach(perms::add);
} else {
// 兼容单个权限字符串的极端情况
perms.add(cachedPerms.toString());
}
}
// 2. 回源 DB 并同步到缓存
if (!missingRoles.isEmpty()) {
for (String roleCode : missingRoles) {
Set<String> dbPerms = this.baseMapper.listRolePerms(Collections.singleton(roleCode));
if (dbPerms == null) {
dbPerms = Collections.emptySet();
}
// 写入缓存(空集也写入,防止缓存穿透)
redisTemplate.opsForHash().put(cacheKey, roleCode, dbPerms);
perms.addAll(dbPerms);
}
}
return perms;
}
/**

View File

@@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.common.enums.DataScopeEnum;
import com.youlai.boot.core.exception.BusinessException;
import com.youlai.boot.system.converter.RoleConverter;
import com.youlai.boot.system.mapper.RoleMapper;
@@ -18,6 +19,7 @@ import com.youlai.boot.system.model.vo.RolePageVO;
import com.youlai.boot.common.constant.SystemConstants;
import com.youlai.boot.common.model.Option;
import com.youlai.boot.security.util.SecurityUtils;
import com.youlai.boot.system.service.RoleDeptService;
import com.youlai.boot.system.service.RoleMenuService;
import com.youlai.boot.system.service.RoleService;
import com.youlai.boot.system.service.UserRoleService;
@@ -41,6 +43,7 @@ import java.util.Set;
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
private final RoleMenuService roleMenuService;
private final RoleDeptService roleDeptService;
private final UserRoleService userRoleService;
private final RoleConverter roleConverter;
@@ -48,7 +51,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
* 角色分页列表
*
* @param queryParams 角色查询参数
* @return {@link Page< RolePageVo >} 角色分页列表
* @return {@link Page< RolePageVO >} 角色分页列表
*/
@Override
public Page<RolePageVO> getRolePage(RoleQuery queryParams) {
@@ -99,6 +102,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
* @return {@link Boolean}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveRole(RoleForm roleForm) {
Long roleId = roleForm.getId();
@@ -123,6 +127,16 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
boolean result = this.saveOrUpdate(role);
if (result) {
// 保存自定义数据权限部门
Long savedRoleId = role.getId();
if (DataScopeEnum.CUSTOM.getValue().equals(roleForm.getDataScope())) {
// 自定义数据权限时,保存角色部门关联
roleDeptService.saveRoleDepts(savedRoleId, roleForm.getDeptIds());
} else {
// 非自定义数据权限时,删除原有部门关联
roleDeptService.deleteByRoleId(savedRoleId);
}
// 判断角色编码或状态是否修改,修改了则刷新权限缓存
if (oldRole != null
&& (
@@ -144,7 +158,13 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
@Override
public RoleForm getRoleForm(Long roleId) {
Role entity = this.getById(roleId);
return roleConverter.toForm(entity);
RoleForm roleForm = roleConverter.toForm(entity);
// 如果是自定义数据权限查询关联的部门ID列表
if (roleForm != null && DataScopeEnum.CUSTOM.getValue().equals(roleForm.getDataScope())) {
List<Long> deptIds = roleDeptService.getDeptIdsByRoleId(roleId);
roleForm.setDeptIds(deptIds);
}
return roleForm;
}
/**
@@ -254,4 +274,70 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
return dataScope;
}
/**
* 获取角色的部门ID列表自定义数据权限
*
* @param roleId 角色ID
* @return 部门ID列表
*/
@Override
public List<Long> getRoleDeptIds(Long roleId) {
return roleDeptService.getDeptIdsByRoleId(roleId);
}
/**
* 获取用户所有角色的数据权限列表
* <p>
* 用于实现多角色数据权限合并(并集策略)
*
* @param roleCodes 角色编码集合
* @return 角色数据权限列表
*/
@Override
public List<RoleDataScope> getRoleDataScopes(Set<String> roleCodes) {
if (CollectionUtil.isEmpty(roleCodes)) {
return List.of();
}
// 获取角色的数据权限信息
List<Map<String, Object>> roleDataScopeList = this.baseMapper.getRoleDataScopeList(roleCodes);
if (CollectionUtil.isEmpty(roleDataScopeList)) {
return List.of();
}
// 获取所有自定义数据权限的角色编码
List<String> customRoleCodes = roleDataScopeList.stream()
.filter(map -> DataScopeEnum.CUSTOM.getValue().equals(map.get("data_scope")))
.map(map -> (String) map.get("code"))
.toList();
// 批量获取自定义角色的部门ID
Map<String, List<Long>> customDeptIdsMap = new java.util.HashMap<>();
if (CollectionUtil.isNotEmpty(customRoleCodes)) {
// 查询每个角色关联的部门ID
for (String roleCode : customRoleCodes) {
// 根据角色编码获取角色ID
Role role = this.getOne(new LambdaQueryWrapper<Role>()
.eq(Role::getCode, roleCode)
.select(Role::getId));
if (role != null) {
List<Long> deptIds = roleDeptService.getDeptIdsByRoleId(role.getId());
customDeptIdsMap.put(roleCode, deptIds);
}
}
}
// 构建角色数据权限列表
return roleDataScopeList.stream()
.map(map -> {
String code = (String) map.get("code");
Integer dataScope = (Integer) map.get("data_scope");
if (DataScopeEnum.CUSTOM.getValue().equals(dataScope)) {
return RoleDataScope.custom(code, customDeptIdsMap.getOrDefault(code, List.of()));
}
return new RoleDataScope(code, dataScope, null);
})
.toList();
}
}

View File

@@ -15,8 +15,8 @@ import com.youlai.boot.common.model.Option;
import com.youlai.boot.platform.mail.service.MailService;
import com.youlai.boot.platform.sms.enums.SmsTypeEnum;
import com.youlai.boot.platform.sms.service.SmsService;
import com.youlai.boot.security.model.RoleDataScope;
import com.youlai.boot.security.model.UserAuthInfo;
import com.youlai.boot.security.service.PermissionService;
import com.youlai.boot.security.token.TokenManager;
import com.youlai.boot.security.util.SecurityUtils;
import com.youlai.boot.system.converter.UserConverter;
@@ -62,7 +62,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
private final RoleService roleService;
private final PermissionService permissionService;
private final RoleMenuService roleMenuService;
private final SmsService smsService;
@@ -81,7 +81,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
* 获取用户分页列表
*
* @param queryParams 查询参数
* @return {@link IPage<UserPageVo>} 用户分页列表
* @return {@link IPage<UserPageVO>} 用户分页列表
*/
@Override
public IPage<UserPageVO> getUserPage(UserQuery queryParams) {
@@ -215,9 +215,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
UserAuthInfo userAuthInfo = this.baseMapper.getAuthInfoByUsername(username);
if (userAuthInfo != null) {
Set<String> roles = userAuthInfo.getRoles();
// 获取最大范围的数据权限
Integer dataScope = roleService.getMaximumDataScope(roles);
userAuthInfo.setDataScope(dataScope);
// 获取数据权限列表(用于并集策略)
List<RoleDataScope> dataScopes = roleService.getRoleDataScopes(roles);
userAuthInfo.setDataScopes(dataScopes);
}
return userAuthInfo;
}
@@ -236,9 +236,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
UserAuthInfo userAuthInfo = this.baseMapper.getAuthInfoByMobile(mobile);
if (userAuthInfo != null) {
Set<String> roles = userAuthInfo.getRoles();
// 获取最大范围的数据权限
Integer dataScope = roleService.getMaximumDataScope(roles);
userAuthInfo.setDataScope(dataScope);
// 获取数据权限列表(用于并集策略)
List<RoleDataScope> dataScopes = roleService.getRoleDataScopes(roles);
userAuthInfo.setDataScopes(dataScopes);
}
return userAuthInfo;
}
@@ -247,7 +247,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
* 获取导出用户列表
*
* @param queryParams 查询参数
* @return {@link List<UserExportDto>} 导出用户列表
* @return {@link List<UserExportDTO>} 导出用户列表
*/
@Override
public List<UserExportDTO> listExportUsers(UserQuery queryParams) {
@@ -285,7 +285,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
/**
* 获取登录用户信息
*
* @return {@link CurrentUserDto} 用户信息
* @return {@link CurrentUserDTO} 用户信息
*/
@Override
public CurrentUserDTO getCurrentUserInfo() {
@@ -311,7 +311,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
// 用户权限集合
if (CollectionUtil.isNotEmpty(roles)) {
Set<String> perms = permissionService.getRolePermsFormCache(roles);
Set<String> perms = roleMenuService.getRolePermsByRoleCodes(roles);
userInfoVo.setPerms(perms);
}
return userInfoVo;
@@ -321,7 +321,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
* 获取个人中心用户信息
*
* @param userId 用户ID
* @return {@link UserProfileVo} 个人中心用户信息
* @return {@link UserProfileVO} 个人中心用户信息
*/
@Override
public UserProfileVO getUserProfile(Long userId) {