feat(base): GetUserMod 改为消费 UserBase 下发的模块树,移除硬编码菜单
build / build-push (push) Successful in 1m59s

与其他子应用(Notice / svc-hall / 事项库)统一:GetUserMod 不再返回本应用硬编码 MENUS,
改经平台 SDK YschBaseOpenApi.getUserModule(userId, schId) 取当前用户在本应用
(yh.open.app-key=100019)下的已授权模块树,层级由平台契约 BoaModule.children 承载,
本侧仅做前端 layout 契约的字段改名映射(toFrontendTree),不再本地组树。
设计概览 §9.4「菜单为本地路由约定、不进 app_module」的口径自此作废。

两处刻意的差异与取舍:

1) modId 取 custom_id 而非主键 id:front router/routes.ts 的 meta.modId 约定是 100~105,
   等于 app_module.custom_id,旧硬编码菜单发的也是这些值;若照抄其他子应用「modId=主键id」,
   前端 modId 兜底匹配会永久失配(仅剩 path 匹配可用)。故 custom_id 可解析为数字时取之,
   否则回退主键 id。
2) 不保留本地 MENUS 兜底:静默回退会把「UserBase 未配 app_module / 未授权」伪装成正常。
   配置缺失应表现为空菜单并被立刻发现。发布顺序必须先是 flow-center接入UserBase菜单.sql
   (含 app_module 写入与角色/组授权),再发本应用。

新增 FlowBaseControllerTest 4 例:层级原样保留与字段改名、children 恒为数组(含上游未展开
的 null 归零)、custom_id 非数字回退主键、path 缺失给空串且 null/空入参不抛 NPE。
映射方法放宽为包级可见以免为测试改写 YschBaseOpenApi 全局静态客户端、污染共享 JVM 内其它用例。
This commit is contained in:
chenxing
2026-09-15 18:44:16 +08:00
parent c1103aeb29
commit f116786846
2 changed files with 157 additions and 25 deletions
@@ -0,0 +1,89 @@
package com.yunhai.flowcenter.controller.api;
import com.yunhai.platform.utils.model.BoaModule;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* GetUserMod 映射单测:flow-center 已改为直接消费 UserBase 下发的模块树({@code BoaModule.children}),
* 本侧只做前端 layout 契约的字段改名,不再本地组树。<br>
* 另锁定两个易错点:modId 必须取 custom_id(对上 routes.ts 的 meta.modId 100~105),
* 以及 children 恒为数组(叶子为空列表、上游未展开的 null 也要归零),否则前端递归会炸。
*/
class FlowBaseControllerTest {
@Test
void toFrontendTree_shouldKeepServerHierarchyAndRenameFields() {
BoaModule parent = module(501, "100", "/workbench", 1);
BoaModule child = module(502, "101", "/my", 2);
parent.setChildren(new ArrayList<>(Collections.singletonList(child)));
BoaModule otherRoot = module(503, "105", "/admin/callbacks", 1);
List<Map<String, Object>> tree = FlowBaseController.toFrontendTree(Arrays.asList(parent, otherRoot));
assertEquals(2, tree.size(), "顶层只应有两个根,子节点不得浮到顶层");
Map<String, Object> first = tree.get(0);
assertEquals("/workbench", first.get("page"), "path → page(前端按 page 匹配菜单高亮)");
assertEquals(100, first.get("modId"), "modId 必须取 custom_id 而非主键 id(routes.ts meta.modId 约定)");
assertEquals(501, first.get("id"), "原始主键仍经 id 字段透出");
@SuppressWarnings("unchecked")
List<Map<String, Object>> children = (List<Map<String, Object>>) first.get("children");
assertEquals(1, children.size(), "层级应原样保留");
assertEquals(101, children.get(0).get("modId"));
assertEquals("/my", children.get(0).get("page"));
}
@Test
void toFrontendTree_shouldAlwaysEmitChildrenAsArray() {
BoaModule configuredLeaf = module(601, "102", "/admin/instances", 1);
configuredLeaf.setChildren(new ArrayList<>());
BoaModule notExpanded = module(602, "103", "/admin/defs", 1); // 上游未展开 → children 为 null
List<Map<String, Object>> tree = FlowBaseController.toFrontendTree(Arrays.asList(configuredLeaf, notExpanded));
for (Map<String, Object> node : tree) {
assertNotNull(node.get("children"), "children 不得为 null,前端要无判空递归");
assertTrue(((List<?>) node.get("children")).isEmpty(), "无子节点时应为空列表");
}
}
@Test
void toFrontendTree_shouldFallBackToPrimaryKeyWhenCustomIdNotNumeric() {
BoaModule m = module(701, "workbench-home", "/workbench", 1);
List<Map<String, Object>> tree = FlowBaseController.toFrontendTree(Collections.singletonList(m));
assertEquals(701, tree.get(0).get("modId"), "custom_id 非数字时回退模块主键 id");
}
@Test
void toFrontendTree_shouldTolerateNullPathAndEmptyInput() {
BoaModule noPath = module(801, "104", null, 1);
assertEquals("", FlowBaseController.toFrontendTree(Collections.singletonList(noPath)).get(0).get("page"),
"path 缺失应给空串而非 null(前端字符串比较不判空)");
assertTrue(FlowBaseController.toFrontendTree(Collections.emptyList()).isEmpty());
assertTrue(FlowBaseController.toFrontendTree(null).isEmpty(), "上游返回 null 不得抛 NPE");
}
private static BoaModule module(Integer id, String customId, String path, Integer level) {
BoaModule m = new BoaModule();
m.setId(id);
m.setCustomId(customId);
m.setName("模块" + customId);
m.setPath(path);
m.setLevel(level);
m.setSortNo(level * 10);
return m;
}
}