父菜单角色继承子菜单角色

This commit is contained in:
bunny 2025-06-01 19:07:44 +08:00
parent 1aeaeaea72
commit 34913542c0
11 changed files with 113 additions and 184 deletions

View File

@ -101,7 +101,7 @@ public class JobExecuteAspect {
scheduleExecuteLogMapper.insert(executeLog);
}
@Pointcut("execution(* cn.bunny.core.quartz.*.execute(..))")
@Pointcut("execution(* cn.bunny.services.quartz.*.execute(..))")
public void pointCut() {
}
}

View File

@ -1,15 +1,15 @@
package cn.bunny.services.core.event.listener.user;
import cn.bunny.services.core.cache.UserLoginVoBuilderCacheService;
import cn.bunny.domain.common.constant.RedisUserConstant;
import cn.bunny.domain.model.system.entity.RolePermission;
import cn.bunny.domain.model.system.entity.UserRole;
import cn.bunny.services.core.event.event.ClearAllUserCacheEvent;
import cn.bunny.services.core.event.event.UpdateUserinfoByPermissionIdsEvent;
import cn.bunny.services.core.event.event.UpdateUserinfoByRoleIdsEvent;
import cn.bunny.services.core.event.event.UpdateUserinfoByUserIdsEvent;
import cn.bunny.domain.common.constant.RedisUserConstant;
import cn.bunny.domain.model.system.entity.RolePermission;
import cn.bunny.domain.model.system.entity.UserRole;
import cn.bunny.services.mapper.system.RolePermissionMapper;
import cn.bunny.services.mapper.system.UserRoleMapper;
import cn.bunny.services.security.service.UserLoginVoBuilderCacheService;
import jakarta.annotation.Resource;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;

View File

@ -162,4 +162,93 @@ public class RouterServiceHelper {
.sorted(Comparator.comparing(routerVo -> routerVo.getMeta().getRank()))
.toList();
}
/**
* 首先构建一个从节点ID到节点对象的映射方便快速查找父节点
* <p>
* 然后遍历所有节点确保每个父节点包含其直接子节点的所有角色
* <p>
* 最后进行多次从下往上的传播确保角色信息从叶子节点一直传播到根节点这个过程会重复直到没有新的角色需要添加为止
*/
public void propagateRolesToParents(List<WebUserRouterVo> webUserRouterVoList) {
if (webUserRouterVoList == null || webUserRouterVoList.isEmpty()) return;
// 首先构建一个id到节点的映射方便查找父节点
Map<Long, WebUserRouterVo> idToNodeMap = new HashMap<>();
buildIdMap(webUserRouterVoList, idToNodeMap);
// 遍历所有节点将子节点的角色传播到父节点
for (WebUserRouterVo node : idToNodeMap.values()) {
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
// 获取当前节点的所有子节点
List<WebUserRouterVo> children = node.getChildren();
// 收集所有子节点的角色
RouterMeta parentMeta = node.getMeta();
if (parentMeta == null) {
parentMeta = new RouterMeta();
node.setMeta(parentMeta);
}
Set<String> allRoles = new HashSet<>();
if (parentMeta.getRoles() != null) {
allRoles.addAll(parentMeta.getRoles());
}
for (WebUserRouterVo child : children) {
if (child.getMeta() != null && child.getMeta().getRoles() != null) {
allRoles.addAll(child.getMeta().getRoles());
}
}
// 更新父节点的角色
if (!allRoles.isEmpty()) {
parentMeta.setRoles(new ArrayList<>(allRoles));
}
}
}
// 需要从下往上传播角色所以需要多次遍历直到没有变化
boolean changed;
do {
changed = false;
for (WebUserRouterVo node : idToNodeMap.values()) {
if (node.getParentId() != null && node.getParentId() != 0) {
WebUserRouterVo parent = idToNodeMap.get(node.getParentId());
if (parent != null) {
RouterMeta parentMeta = parent.getMeta();
RouterMeta childMeta = node.getMeta();
if (childMeta != null && childMeta.getRoles() != null) {
if (parentMeta == null) {
parentMeta = new RouterMeta();
parent.setMeta(parentMeta);
}
Set<String> parentRoles = parentMeta.getRoles() != null
? new HashSet<>(parentMeta.getRoles())
: new HashSet<>();
int originalSize = parentRoles.size();
parentRoles.addAll(childMeta.getRoles());
if (parentRoles.size() > originalSize) {
parentMeta.setRoles(new ArrayList<>(parentRoles));
changed = true;
}
}
}
}
}
} while (changed);
}
private void buildIdMap(List<WebUserRouterVo> nodes, Map<Long, WebUserRouterVo> idToNodeMap) {
for (WebUserRouterVo node : nodes) {
idToNodeMap.put(node.getId(), node);
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
buildIdMap(node.getChildren(), idToNodeMap);
}
}
}
}

View File

@ -1,26 +0,0 @@
package cn.bunny.services.security.handelr;
import cn.bunny.domain.common.model.vo.result.Result;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import java.io.IOException;
public class SecurityAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
// 错误消息
String localizedMessage = exception.getLocalizedMessage();
Result<String> result = Result.error(localizedMessage);
// 转成JSON
Object json = JSON.toJSON(result);
// 返回响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
}
}

View File

@ -1,26 +0,0 @@
package cn.bunny.services.security.handelr;
import cn.bunny.domain.common.model.vo.result.Result;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.io.IOException;
/**
* 登录成功
*/
public class SecurityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// 获取用户身份信息
Object principal = authentication.getPrincipal();
Result<Object> result = Result.success(principal);
// 返回
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(JSON.toJSON(result));
}
}

View File

@ -1,15 +1,15 @@
package cn.bunny.services.core.cache;
package cn.bunny.services.security.service;
import cn.bunny.services.core.utils.RoleHelper;
import cn.bunny.core.utils.JwtTokenUtil;
import cn.bunny.domain.common.constant.LocalDateTimeConstant;
import cn.bunny.domain.common.constant.RedisUserConstant;
import cn.bunny.domain.common.model.vo.LoginVo;
import cn.bunny.domain.model.system.entity.AdminUser;
import cn.bunny.domain.model.system.entity.Permission;
import cn.bunny.domain.model.system.entity.Role;
import cn.bunny.services.core.utils.RoleHelper;
import cn.bunny.services.mapper.system.PermissionMapper;
import cn.bunny.services.mapper.system.RoleMapper;
import cn.bunny.core.utils.JwtTokenUtil;
import jakarta.annotation.Resource;
import lombok.Value;
import org.springframework.beans.BeanUtils;

View File

@ -1,10 +1,10 @@
package cn.bunny.services.service.system.impl;
import cn.bunny.services.core.event.event.UpdateUserinfoByRoleIdsEvent;
import cn.bunny.domain.model.system.dto.AssignPowersToRoleDto;
import cn.bunny.domain.model.system.entity.AdminUser;
import cn.bunny.domain.model.system.entity.RolePermission;
import cn.bunny.domain.model.system.entity.UserRole;
import cn.bunny.services.core.event.event.UpdateUserinfoByRoleIdsEvent;
import cn.bunny.services.mapper.system.RolePermissionMapper;
import cn.bunny.services.mapper.system.UserMapper;
import cn.bunny.services.mapper.system.UserRoleMapper;
@ -65,8 +65,7 @@ public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper,
// 删除这个角色下所有权限
List<Long> ids = List.of(roleId);
removeByIds(ids);
// baseMapper.deleteBatchRoleIds(ids);
baseMapper.deleteBatchRoleIds(ids);
// 保存分配数据
List<RolePermission> rolePermissionList = powerIds.stream().map(powerId -> {

View File

@ -1,6 +1,6 @@
package cn.bunny.services.service.system.impl;
import cn.bunny.services.core.utils.RouterServiceHelper;
import cn.bunny.core.exception.AuthCustomerException;
import cn.bunny.domain.common.enums.ResultCodeEnum;
import cn.bunny.domain.model.system.dto.RouterDto;
import cn.bunny.domain.model.system.entity.router.Router;
@ -11,7 +11,7 @@ import cn.bunny.domain.model.system.views.ViewRouterRole;
import cn.bunny.domain.model.system.vo.router.RouterManageVo;
import cn.bunny.domain.model.system.vo.router.RouterVo;
import cn.bunny.domain.model.system.vo.router.WebUserRouterVo;
import cn.bunny.core.exception.AuthCustomerException;
import cn.bunny.services.core.utils.RouterServiceHelper;
import cn.bunny.services.mapper.system.RolePermissionMapper;
import cn.bunny.services.mapper.system.RouterMapper;
import cn.bunny.services.mapper.system.RouterRoleMapper;
@ -78,6 +78,7 @@ public class RouterServiceImpl extends ServiceImpl<RouterMapper, Router> impleme
// 整理web用户所能看到的路由列表并检查当前用户是否是admin
List<WebUserRouterVo> webUserRouterVoList = routerServiceHelper.getWebUserRouterVos(routerList, routerRoleList, rolePermissionList);
routerServiceHelper.propagateRolesToParents(webUserRouterVoList);
// 添加 admin 管理路由权限
webUserRouterVoList.forEach(routerVo -> {

View File

@ -1,14 +1,9 @@
package cn.bunny.services.service.system.impl;
import cn.bunny.core.context.BaseContext;
import cn.bunny.services.core.cache.EmailCacheService;
import cn.bunny.services.core.cache.UserLoginVoBuilderCacheService;
import cn.bunny.services.core.event.event.ClearAllUserCacheEvent;
import cn.bunny.services.core.strategy.login.DefaultLoginStrategy;
import cn.bunny.services.core.strategy.login.EmailLoginStrategy;
import cn.bunny.services.core.strategy.login.LoginContext;
import cn.bunny.services.core.strategy.login.LoginStrategy;
import cn.bunny.services.core.template.email.ConcreteSenderEmailTemplate;
import cn.bunny.core.exception.AuthCustomerException;
import cn.bunny.core.utils.IpUtil;
import cn.bunny.core.utils.JwtTokenUtil;
import cn.bunny.domain.common.constant.RedisUserConstant;
import cn.bunny.domain.common.constant.UserConstant;
import cn.bunny.domain.common.enums.EmailTemplateEnums;
@ -22,13 +17,18 @@ import cn.bunny.domain.model.system.dto.user.LoginDto;
import cn.bunny.domain.model.system.dto.user.RefreshTokenDto;
import cn.bunny.domain.model.system.entity.AdminUser;
import cn.bunny.domain.model.system.vo.user.RefreshTokenVo;
import cn.bunny.core.exception.AuthCustomerException;
import cn.bunny.services.core.cache.EmailCacheService;
import cn.bunny.services.core.event.event.ClearAllUserCacheEvent;
import cn.bunny.services.core.strategy.login.DefaultLoginStrategy;
import cn.bunny.services.core.strategy.login.EmailLoginStrategy;
import cn.bunny.services.core.strategy.login.LoginContext;
import cn.bunny.services.core.strategy.login.LoginStrategy;
import cn.bunny.services.core.template.email.ConcreteSenderEmailTemplate;
import cn.bunny.services.mapper.configuration.EmailTemplateMapper;
import cn.bunny.services.mapper.log.UserLoginLogMapper;
import cn.bunny.services.mapper.system.UserMapper;
import cn.bunny.services.security.service.UserLoginVoBuilderCacheService;
import cn.bunny.services.service.system.UserLoginService;
import cn.bunny.core.utils.IpUtil;
import cn.bunny.core.utils.JwtTokenUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

View File

@ -1,79 +0,0 @@
package impl;
import cn.bunny.services.AuthServiceApplication;
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
import cn.bunny.domain.common.model.dto.scanner.ScannerControllerInfoVo;
import cn.bunny.domain.model.system.entity.Permission;
import cn.bunny.services.service.system.PermissionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Objects;
@SpringBootTest(classes = AuthServiceApplication.class, properties = "spring.profiles.active=dev")
public class BuildPermissionApiTest {
@Autowired
private PermissionService permissionService;
@Test
void test() {
List<ScannerControllerInfoVo> list = ControllerApiPermissionScanner.scanControllerInfo();
// 添加Springboot端点在服务监控中会用到
ScannerControllerInfoVo actuatorParent = ScannerControllerInfoVo.builder().powerCode("admin:actuator").summary("actuator端点访问").build();
ScannerControllerInfoVo actuatorChild = ScannerControllerInfoVo.builder().path("/api/actuator/**")
.summary("Springboot端点全部可以访问")
.description("系统监控使用")
.powerCode("actuator:all")
.build();
actuatorParent.setChildren(List.of(actuatorChild));
list.add(actuatorParent);
list.forEach(parent -> {
String summary = parent.getSummary();
String path = parent.getPath();
String httpMethod = parent.getHttpMethod();
String powerCodes = parent.getPowerCode();
// 设置 powerCode
String powerCode = Objects.isNull(powerCodes) ? "" : powerCodes;
Permission permission = new Permission();
permission.setParentId(0L);
permission.setPowerName(summary);
permission.setPowerCode(powerCode);
permission.setRequestMethod(httpMethod);
permission.setRequestUrl(path);
permissionService.save(permission);
// 保存后 permission Id 作为子级的父级Id
Long permissionId = permission.getId();
// 子级列表
List<Permission> permissionList = parent.getChildren().stream()
.map(children -> {
String childrenSummary = children.getSummary();
String childrenPath = children.getPath();
String childrenHttpMethod = children.getHttpMethod();
String childrenPowerCodes = children.getPowerCode();
// 设置 powerCode
String childrenPowerCode = Objects.isNull(childrenPowerCodes) ? "" : childrenPowerCodes;
Permission childPermission = new Permission();
childPermission.setParentId(permissionId);
childPermission.setPowerName(childrenSummary);
childPermission.setPowerCode(childrenPowerCode);
childPermission.setRequestMethod(childrenHttpMethod);
childPermission.setRequestUrl(childrenPath);
return childPermission;
}).toList();
permissionService.saveBatch(permissionList);
});
}
}

View File

@ -1,29 +0,0 @@
package impl;
import cn.bunny.services.aop.scanner.ControllerApiPermissionScanner;
import cn.bunny.domain.common.model.dto.scanner.ScannerControllerInfoVo;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import java.util.List;
public class ControllerScannerTest {
@Test
void test() {
List<ScannerControllerInfoVo> list = ControllerApiPermissionScanner.scanControllerInfo();
// 添加Springboot端点在服务监控中会用到
ScannerControllerInfoVo actuatorParent = ScannerControllerInfoVo.builder().powerCode("admin:actuator").summary("actuator端点访问").build();
ScannerControllerInfoVo actuatorChild = ScannerControllerInfoVo.builder().path("/api/actuator/**")
.summary("Springboot端点全部可以访问")
.description("系统监控使用")
.powerCode("actuator:all")
.build();
actuatorParent.setChildren(List.of(actuatorChild));
list.add(actuatorParent);
String jsonString = JSON.toJSONString(list);
System.out.println(jsonString);
}
}