1. 新增 api 调用日志记录,历史版本回滚

2. 新增用户角色权限监督功能
This commit is contained in:
chenbowen
2025-10-31 09:28:59 +08:00
parent b618f833d1
commit ddee4da72a
43 changed files with 2454 additions and 65 deletions

View File

@@ -1,12 +1,23 @@
package com.zt.plat.module.system.controller.admin.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionUserSupervisionRespVO;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDO;
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
import com.zt.plat.module.system.enums.permission.MenuTypeEnum;
import com.zt.plat.module.system.service.permission.MenuService;
import com.zt.plat.module.system.service.permission.PermissionService;
import com.zt.plat.module.system.service.permission.RoleService;
import com.zt.plat.module.system.service.tenant.TenantService;
import com.zt.plat.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,7 +27,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@@ -34,6 +46,12 @@ public class PermissionController {
private PermissionService permissionService;
@Resource
private TenantService tenantService;
@Resource
private AdminUserService userService;
@Resource
private RoleService roleService;
@Resource
private MenuService menuService;
@Operation(summary = "获得角色拥有的菜单编号")
@Parameter(name = "roleId", description = "角色编号", required = true)
@@ -79,4 +97,285 @@ public class PermissionController {
return success(true);
}
@Operation(summary = "获得用户权限监督图")
@GetMapping("/user-permission-supervision")
@PreAuthorize("@ss.hasPermission('system:permission:user-permission-supervision')")
public CommonResult<PermissionUserSupervisionRespVO> getUserPermissionSupervision(@RequestParam("userId") Long userId) {
AdminUserDO user = userService.getUser(userId);
if (user == null) {
return success(null);
}
PermissionUserSupervisionRespVO respVO = new PermissionUserSupervisionRespVO();
PermissionUserSupervisionRespVO.Node root = new PermissionUserSupervisionRespVO.Node();
root.setId("user-" + user.getId());
root.setLabel(Optional.ofNullable(user.getNickname()).map(nickname -> nickname + "(" + user.getUsername() + ")").orElse(user.getUsername()));
root.setType("user");
root.setStatus(user.getStatus());
respVO.setRoot(root);
Set<Long> assignedRoleIds = permissionService.getUserRoleIdListByUserId(userId);
if (CollUtil.isEmpty(assignedRoleIds)) {
root.setTotalPermissionCount(0);
root.setIncrementalPermissionCount(0);
return success(respVO);
}
Set<Long> allRoleIds = roleService.getAllParentAndSelfRoleIds(assignedRoleIds);
List<RoleDO> roles = roleService.getRoleList(allRoleIds);
if (CollUtil.isEmpty(roles)) {
root.setTotalPermissionCount(0);
root.setIncrementalPermissionCount(0);
return success(respVO);
}
Map<Long, RoleDO> roleMap = CollectionUtils.convertMap(roles, RoleDO::getId);
List<RoleDO> rootRoles = roles.stream()
.filter(role -> role.getParentId() == null || role.getParentId() <= 0 || !roleMap.containsKey(role.getParentId()))
.sorted(getRoleComparator())
.collect(Collectors.toList());
Map<Long, List<RoleDO>> childrenMap = new HashMap<>();
for (RoleDO role : roles) {
Long parentId = role.getParentId();
if (parentId != null && parentId > 0 && roleMap.containsKey(parentId)) {
childrenMap.computeIfAbsent(parentId, id -> new ArrayList<>()).add(role);
}
}
childrenMap.values().forEach(list -> list.sort(getRoleComparator()));
Map<Long, Set<Long>> roleMenuMap = new HashMap<>();
Set<Long> aggregatedMenuIds = new LinkedHashSet<>();
for (RoleDO role : roles) {
Set<Long> menuIds = new LinkedHashSet<>(permissionService.getRoleMenuListByRoleId(Collections.singleton(role.getId())));
roleMenuMap.put(role.getId(), menuIds);
aggregatedMenuIds.addAll(menuIds);
}
List<MenuDO> menuList = menuService.getMenuList(aggregatedMenuIds);
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus()));
Map<Long, MenuDO> menuMap = CollectionUtils.convertMap(menuList, MenuDO::getId);
List<PermissionUserSupervisionRespVO.Node> roleNodes = rootRoles.stream()
.map(role -> buildRoleNode(role, childrenMap, roleMenuMap, menuMap, assignedRoleIds, new LinkedHashSet<>()))
.collect(Collectors.toList());
root.setChildren(roleNodes);
Set<Long> aggregatedEffectiveIds = aggregatedMenuIds.stream()
.map(menuMap::get)
.filter(Objects::nonNull)
.filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))
.map(MenuDO::getId)
.collect(Collectors.toCollection(LinkedHashSet::new));
root.setTotalPermissionCount(aggregatedEffectiveIds.size());
root.setIncrementalPermissionCount(aggregatedEffectiveIds.size());
return success(respVO);
}
private PermissionUserSupervisionRespVO.Node buildRoleNode(RoleDO role,
Map<Long, List<RoleDO>> childrenMap,
Map<Long, Set<Long>> roleMenuMap,
Map<Long, MenuDO> menuMap,
Set<Long> assignedRoleIds,
Set<Long> inheritedMenuIds) {
PermissionUserSupervisionRespVO.Node node = new PermissionUserSupervisionRespVO.Node();
node.setId("role-" + role.getId());
node.setLabel(role.getName());
node.setType("role");
node.setStatus(role.getStatus());
node.setCode(role.getCode());
node.setAssigned(assignedRoleIds.contains(role.getId()));
Set<Long> roleMenuIds = new LinkedHashSet<>(roleMenuMap.getOrDefault(role.getId(), Collections.emptySet()));
Set<Long> effectiveRoleMenuIds = roleMenuIds.stream()
.map(menuMap::get)
.filter(Objects::nonNull)
.filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))
.map(MenuDO::getId)
.collect(Collectors.toCollection(LinkedHashSet::new));
node.setTotalPermissionCount(effectiveRoleMenuIds.size());
Set<Long> incrementalMenuIds = new LinkedHashSet<>(effectiveRoleMenuIds);
incrementalMenuIds.removeAll(inheritedMenuIds);
node.setIncrementalPermissionCount(incrementalMenuIds.size());
Set<Long> nextInheritedMenuIds = new LinkedHashSet<>(inheritedMenuIds);
nextInheritedMenuIds.addAll(effectiveRoleMenuIds);
List<RoleDO> childRoles = childrenMap.getOrDefault(role.getId(), Collections.emptyList());
for (RoleDO childRole : childRoles) {
node.getChildren().add(buildRoleNode(childRole, childrenMap, roleMenuMap, menuMap, assignedRoleIds,
new LinkedHashSet<>(nextInheritedMenuIds)));
}
List<MenuDO> orphanButtons = new ArrayList<>();
List<PermissionUserSupervisionRespVO.Node> menuNodes = buildMenuChildren(role, incrementalMenuIds, menuMap, orphanButtons);
node.getChildren().addAll(menuNodes);
if (!orphanButtons.isEmpty()) {
orphanButtons.stream()
.sorted(getMenuComparator())
.map(button -> buildPermissionNode(role, button))
.forEach(node.getChildren()::add);
}
return node;
}
private Comparator<RoleDO> getRoleComparator() {
return Comparator
.comparing((RoleDO role) -> role.getSort() == null ? Integer.MAX_VALUE : role.getSort())
.thenComparing(RoleDO::getId);
}
private Comparator<MenuDO> getMenuComparator() {
return Comparator
.comparing((MenuDO menu) -> menu.getSort() == null ? Integer.MAX_VALUE : menu.getSort())
.thenComparing(MenuDO::getId);
}
private List<PermissionUserSupervisionRespVO.Node> buildMenuChildren(RoleDO role,
Set<Long> incrementalButtonIds,
Map<Long, MenuDO> menuMap,
List<MenuDO> orphanButtons) {
if (CollUtil.isEmpty(incrementalButtonIds)) {
return Collections.emptyList();
}
Map<Long, MenuTreeNode> menuNodeCache = new HashMap<>();
Map<Long, MenuTreeNode> rootMenuNodes = new LinkedHashMap<>();
for (Long buttonId : incrementalButtonIds) {
MenuDO button = menuMap.get(buttonId);
if (button == null) {
continue;
}
MenuDO parentMenu = findNearestNonButtonMenu(button, menuMap);
if (parentMenu == null) {
orphanButtons.add(button);
continue;
}
MenuTreeNode parentNode = ensureMenuPath(parentMenu, menuMap, menuNodeCache, rootMenuNodes);
if (parentNode == null) {
orphanButtons.add(button);
continue;
}
parentNode.getButtons().add(button);
}
return rootMenuNodes.values().stream()
.sorted((left, right) -> getMenuComparator().compare(left.getMenu(), right.getMenu()))
.map(menuTreeNode -> convertMenuTreeNode(role, menuTreeNode))
.collect(Collectors.toList());
}
private MenuTreeNode ensureMenuPath(MenuDO menu,
Map<Long, MenuDO> menuMap,
Map<Long, MenuTreeNode> menuNodeCache,
Map<Long, MenuTreeNode> rootMenuNodes) {
if (menu == null || MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
return null;
}
MenuTreeNode existing = menuNodeCache.get(menu.getId());
if (existing != null) {
return existing;
}
MenuTreeNode current = new MenuTreeNode(menu);
menuNodeCache.put(menu.getId(), current);
Long parentId = menu.getParentId();
if (parentId == null || parentId <= 0) {
rootMenuNodes.putIfAbsent(menu.getId(), current);
return current;
}
MenuDO parentMenu = menuMap.get(parentId);
MenuTreeNode parentNode = ensureMenuPath(parentMenu, menuMap, menuNodeCache, rootMenuNodes);
if (parentNode != null) {
parentNode.getChildren().putIfAbsent(menu.getId(), current);
} else {
rootMenuNodes.putIfAbsent(menu.getId(), current);
}
return current;
}
private MenuDO findNearestNonButtonMenu(MenuDO menu, Map<Long, MenuDO> menuMap) {
MenuDO current = menu;
while (current != null && MenuTypeEnum.BUTTON.getType().equals(current.getType())) {
current = menuMap.get(current.getParentId());
}
return current;
}
private PermissionUserSupervisionRespVO.Node convertMenuTreeNode(RoleDO role, MenuTreeNode treeNode) {
PermissionUserSupervisionRespVO.Node menuNode = new PermissionUserSupervisionRespVO.Node();
MenuDO menu = treeNode.getMenu();
menuNode.setId("menu-" + role.getId() + '-' + menu.getId());
menuNode.setLabel(menu.getName());
menuNode.setType("menu");
menuNode.setStatus(menu.getStatus());
List<PermissionUserSupervisionRespVO.Node> childMenuNodes = treeNode.getChildren().values().stream()
.sorted((left, right) -> getMenuComparator().compare(left.getMenu(), right.getMenu()))
.map(child -> convertMenuTreeNode(role, child))
.collect(Collectors.toList());
menuNode.getChildren().addAll(childMenuNodes);
List<PermissionUserSupervisionRespVO.Node> buttonNodes = treeNode.getButtons().stream()
.sorted(getMenuComparator())
.map(button -> buildPermissionNode(role, button))
.collect(Collectors.toList());
menuNode.getButtonChildren().addAll(buttonNodes);
int totalButtonCount = treeNode.getTotalButtonCount();
menuNode.setTotalPermissionCount(totalButtonCount);
menuNode.setIncrementalPermissionCount(totalButtonCount);
return menuNode;
}
private PermissionUserSupervisionRespVO.Node buildPermissionNode(RoleDO role, MenuDO button) {
PermissionUserSupervisionRespVO.Node permissionNode = new PermissionUserSupervisionRespVO.Node();
permissionNode.setId("permission-" + role.getId() + '-' + button.getId());
permissionNode.setLabel(button.getName());
permissionNode.setType("permission");
permissionNode.setStatus(button.getStatus());
permissionNode.setPermission(button.getPermission());
permissionNode.setTotalPermissionCount(1);
permissionNode.setIncrementalPermissionCount(1);
return permissionNode;
}
private static final class MenuTreeNode {
private final MenuDO menu;
private final LinkedHashMap<Long, MenuTreeNode> children = new LinkedHashMap<>();
private final List<MenuDO> buttons = new ArrayList<>();
private MenuTreeNode(MenuDO menu) {
this.menu = menu;
}
private MenuDO getMenu() {
return menu;
}
private LinkedHashMap<Long, MenuTreeNode> getChildren() {
return children;
}
private List<MenuDO> getButtons() {
return buttons;
}
private int getTotalButtonCount() {
int total = buttons.size();
for (MenuTreeNode child : children.values()) {
total += child.getTotalButtonCount();
}
return total;
}
}
}

View File

@@ -0,0 +1,41 @@
package com.zt.plat.module.system.controller.admin.permission.vo.permission;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 用户权限监督图响应 VO
*/
@Data
public class PermissionUserSupervisionRespVO {
private Node root;
@Data
public static class Node {
private String id;
private String label;
private String type;
private Integer status;
private Boolean assigned;
private String code;
private String permission;
private Integer totalPermissionCount;
private Integer incrementalPermissionCount;
private List<Node> children = new ArrayList<>();
private List<Node> buttonChildren = new ArrayList<>();
}
}

View File

@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -38,4 +39,7 @@ public class UserPageReqVO extends PageParam {
@Schema(description = "角色编号", example = "1024")
private Long roleId;
@Schema(description = "用户编号集合", example = "[1, 2, 3]")
private List<Long> ids;
}

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
@@ -24,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList;
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap;
@@ -66,11 +68,13 @@ public class MenuServiceImpl implements MenuService {
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理简单有效
public void updateMenu(MenuSaveVO updateReqVO) {
// 校验更新的菜单是否存在
if (menuMapper.selectById(updateReqVO.getId()) == null) {
MenuDO oldMenu = menuMapper.selectById(updateReqVO.getId());
if (oldMenu == null) {
throw exception(MENU_NOT_EXISTS);
}
// 校验父菜单存在
@@ -83,6 +87,12 @@ public class MenuServiceImpl implements MenuService {
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
initMenuProperty(updateObj);
menuMapper.updateById(updateObj);
// 如果本次更新禁用了当前菜单,则联动禁用所有子菜单
if (!CommonStatusEnum.isDisable(oldMenu.getStatus())
&& CommonStatusEnum.isDisable(updateObj.getStatus())) {
cascadeDisableChildMenus(updateObj.getId());
}
}
@Override
@@ -190,6 +200,41 @@ public class MenuServiceImpl implements MenuService {
return menuMapper.selectBatchIds(ids);
}
// 禁用父级菜单时需级联禁用所有子菜单
private void cascadeDisableChildMenus(Long parentId) {
List<MenuDO> allMenus = menuMapper.selectList();
if (CollUtil.isEmpty(allMenus)) {
return;
}
Map<Long, List<MenuDO>> childrenMap = new HashMap<>();
for (MenuDO menu : allMenus) {
childrenMap.computeIfAbsent(menu.getParentId(), key -> new ArrayList<>()).add(menu);
}
Deque<Long> queue = new ArrayDeque<>();
queue.add(parentId);
List<Long> toDisableIds = new ArrayList<>();
while (!queue.isEmpty()) {
Long current = queue.poll();
List<MenuDO> children = childrenMap.get(current);
if (CollUtil.isEmpty(children)) {
continue;
}
for (MenuDO child : children) {
if (!CommonStatusEnum.isDisable(child.getStatus())) {
toDisableIds.add(child.getId());
}
queue.add(child.getId());
}
}
if (CollUtil.isEmpty(toDisableIds)) {
return;
}
menuMapper.update(null, new LambdaUpdateWrapper<MenuDO>()
.in(MenuDO::getId, toDisableIds)
.set(MenuDO::getStatus, CommonStatusEnum.DISABLE.getStatus()));
}
/**
* 校验父菜单是否合法
* <p>

View File

@@ -338,6 +338,18 @@ public class AdminUserServiceImpl implements AdminUserService {
Set<Long> userIds = reqVO.getRoleId() != null ?
permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;
if (CollUtil.isNotEmpty(reqVO.getIds())) {
Set<Long> idFilter = new HashSet<>(reqVO.getIds());
if (userIds == null) {
userIds = idFilter;
} else {
userIds.retainAll(idFilter);
}
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
}
// 分页查询
PageResult<AdminUserDO> pageResult = userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
fillUserDeptInfo(pageResult.getList());