task-141(Java 菜单树与 Flyway): 冻结 Java 菜单树响应 DTO 契约
新增纯 Java AdminMenuTreeBuilder:Node(key/name/route/children) 树组装与 route 规整,column_key 与 route 分离,可离线单测。 TDD: Task141Test 8 用例先 RED 后 GREEN。
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
package com.nanri.aiimage.modules.permission.service.support;
|
||||
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 后台菜单树纯组装器(module 08):column_key→key、route_path→route、parent 树、排序等均为纯逻辑,
|
||||
* 便于离线单测;树形结果不含 sort 冗余字段。不与当前请求/UA/查询参数联动。
|
||||
*/
|
||||
public final class AdminMenuTreeBuilder {
|
||||
|
||||
/** 冻结的树形节点 DTO:key=column_key、route=Vue path、children 有序。 */
|
||||
public record Node(Long id, String key, String name, Integer sort, String route, List<Node> children) {
|
||||
public Node {
|
||||
children = children == null ? List.of() : List.copyOf(children);
|
||||
}
|
||||
}
|
||||
|
||||
private AdminMenuTreeBuilder() {
|
||||
}
|
||||
|
||||
/** 由有效菜单集合组装树(仅 menu_type=admin)。空/无效输入回空列表。 */
|
||||
public static List<Node> build(List<PermissionMenuItemVo> menus) {
|
||||
if (menus == null || menus.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, Node> nodes = new LinkedHashMap<>();
|
||||
for (PermissionMenuItemVo menu : menus) {
|
||||
Node node = toNode(menu);
|
||||
if (node != null) {
|
||||
nodes.put(node.id(), node);
|
||||
}
|
||||
}
|
||||
List<Node> roots = new ArrayList<>();
|
||||
for (PermissionMenuItemVo menu : menus) {
|
||||
Node node = nodes.get(menu.getId());
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
if (menu.getParentId() != null && nodes.containsKey(menu.getParentId())) {
|
||||
Node parent = nodes.get(menu.getParentId());
|
||||
Node child = node;
|
||||
List<Node> children = new ArrayList<>(parent.children());
|
||||
children.add(child);
|
||||
nodes.put(parent.id(), new Node(parent.id(), parent.key(), parent.name(), parent.sort(), parent.route(), children));
|
||||
} else {
|
||||
roots.add(node);
|
||||
}
|
||||
}
|
||||
Comparator<Node> order = Comparator
|
||||
.comparingInt((Node n) -> n.sort() == null ? 0 : n.sort())
|
||||
.thenComparing(Node::key);
|
||||
return sortTree(roots, order);
|
||||
}
|
||||
|
||||
private static List<Node> sortTree(List<Node> roots, Comparator<Node> order) {
|
||||
List<Node> sorted = new ArrayList<>(roots);
|
||||
sorted.sort(order);
|
||||
List<Node> result = new ArrayList<>(sorted.size());
|
||||
for (Node node : sorted) {
|
||||
Node withChildren = node.children().isEmpty()
|
||||
? node
|
||||
: new Node(node.id(), node.key(), node.name(), node.sort(), node.route(), sortTree(node.children(), order));
|
||||
result.add(withChildren);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 单节点映射;缺 id 返回 null;分组/页面节点 route 由 stored route 规整。 */
|
||||
public static Node toNode(PermissionMenuItemVo menu) {
|
||||
if (menu == null || menu.getId() == null) {
|
||||
return null;
|
||||
}
|
||||
String key = menu.getColumnKey() == null ? String.valueOf(menu.getId()) : menu.getColumnKey();
|
||||
String name = menu.getName() == null || menu.getName().isBlank() ? "未命名菜单" : menu.getName();
|
||||
Integer sort = menu.getSortOrder() == null ? 0 : menu.getSortOrder();
|
||||
String route = normalizeRoute(menu.getRoutePath());
|
||||
return new Node(menu.getId(), key, name, sort, route, new ArrayList<>());
|
||||
}
|
||||
|
||||
/** route 规整:去空白、非 "/" 前缀补 "/",分组占位(group-*)保留原值不当作页面。 */
|
||||
public static String normalizeRoute(String storedRoute) {
|
||||
if (storedRoute == null) {
|
||||
return null;
|
||||
}
|
||||
String route = storedRoute.trim();
|
||||
if (route.isEmpty() || route.startsWith("group-")) {
|
||||
return route.isEmpty() ? null : route;
|
||||
}
|
||||
return route.startsWith("/") ? route : "/" + route;
|
||||
}
|
||||
|
||||
/** 只保留指定菜单域(menu_type=admin)的菜单项;null/空回空列表。 */
|
||||
public static List<PermissionMenuItemVo> filterByMenuType(List<PermissionMenuItemVo> menus, String menuType) {
|
||||
if (menus == null || menuType == null) {
|
||||
return List.of();
|
||||
}
|
||||
List<PermissionMenuItemVo> out = new ArrayList<>();
|
||||
for (PermissionMenuItemVo menu : menus) {
|
||||
if (menu != null && menuType.equals(menu.getMenuType())) {
|
||||
out.add(menu);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user