children 总是空是因为递归中未用指针更新父节点字段;需用 map 缓存节点、按 parentid 排序、权限过滤前置、children 初始化为 make([]*menu, 0) 并导出加 json tag。

Go 里用嵌套结构体 + 递归构建菜单树时,为什么 Children 总是空?
因为没做指针传递或深拷贝,递归过程中子节点被分配到局部变量副本里,父节点的 Children 字段压根没被更新。
- 确保菜单数据源是按
ParentID排好序的(升序),或至少先查出全量再本地组织;乱序会导致某次递归找不到父节点而跳过挂载 - 用
map[int]*Menu缓存所有已创建节点,键为ID,避免重复构造;每次找到父节点后,直接往其Children切片追加对应子节点指针 - 别在递归函数里 new 一个新
Menu然后 return —— 要操作的是 map 中那个唯一实例的Children字段
如何让 BuildMenuTree 支持动态权限过滤(比如用户看不到“系统设置”)?
不能等树建完再遍历删节点,那样会破坏父子引用关系,导致空指针或漏删子项;必须在挂载前就判断权限。
- 把权限检查逻辑提前到「决定是否将当前节点挂到父节点」这一步,也就是在向
parent.Children = append(parent.Children, node)执行前加一层if canAccess(node.Code) {...} -
canAccess函数接收的是菜单的权限标识符(如"sys:config"),不是 ID;数据库里要存这个字段,并和用户角色绑定的权限列表比对 - 注意:如果父节点被过滤掉,它的所有子节点也应跳过处理(否则会出现“孤儿节点”),所以递归调用前要先校验父节点是否已通过权限检查
用 json.Marshal 输出菜单树时,为什么前端收不到嵌套结构?
常见原因是结构体字段没加导出(首字母小写)或 JSON tag 写错,导致序列化后 Children 字段消失或为 null。
- 确认
Children字段是导出的:Children []*Menu `json:"children"`,不是children或Childrens - 如果用了
omitempty,而Children是 nil 切片(不是空切片),那整个字段会被忽略;初始化时统一用Children: make([]*Menu, 0),别留 nil - 检查是否有循环引用:比如误把父节点塞进子节点的某个字段,
json.Marshal会 panic 报json: unsupported type: map[interface {}]interface{}或直接静默失败
Golang 组合模式下,菜单结构体要不要嵌入 BaseModel?
要,但只嵌入字段,别嵌入方法。组合模式靠的是结构体字段拼装,不是方法继承。
立即学习“go语言免费学习笔记(深入)”;
- 嵌入
type BaseModel struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time }没问题,能复用 GORM 映射 - 千万别嵌入带
Save()、Delete()这类方法的类型——Go 不支持方法继承,嵌入后这些方法签名会出现在当前类型上,但 receiver 是嵌入类型本身,调用时实际操作的是嵌入字段的副本,不是外层结构体 - 权限相关字段(如
Code、Sort、IsHidden)建议都定义在菜单结构体里,别指望从基类“继承”过来;组合不是继承,字段得显式存在










