找回密码
 立即注册
首页 业界区 业界 Go 层级菜单树转 json 处理

Go 层级菜单树转 json 处理

浦乐 2025-6-2 23:25:36
本篇是一个补充知识点, 目的是为了下篇的后台管理系统中, 菜单权限的接口进行铺垫一下.
同时也是做个笔记, 因为在很多地方都会用这种 "树结构" 来实现很多权限, 层级, 菜单的处理哈.
在表设计层面通常是通过 id 和pid  来体现层级关系.

  • id 表示表的每行菜单的唯一标识
  • pid 标识这一行的 上级菜单id 是谁, 这个 id 一定是在 所有 id 中的
  • 假设我们约定, pid = 0 是顶级菜单
表结构设计

于是表设计就可以这样:
  1. -- 菜单树的表结构
  2. drop table if exists test_tree;
  3. create table test_tree (
  4.         id int auto_increment primary key comment '自增id'
  5.         , pid int not null default 0      comment '父级id'
  6.         , name varchar(100) not null      comment '名称'
  7.         , orders int not null default 0   comment '排序号'
  8. );
  9. -- 插入数据
  10. INSERT INTO test_tree (id, pid, name, orders) VALUES
  11. (1, 0, 'A1', 10),
  12. (2, 1, 'A1-1', 20),
  13. (3, 1, 'A1-2', 20),
  14. (4, 3, 'A1-2-1', 30),
  15. (5, 3, 'A1-2-2', 30),
  16. (6, 0, 'B1', 10),
  17. (7, 6, 'B1-1', 20),
  18. (8, 7, 'B1-1-1', 30),
  19. (9, 8, 'B1-1-1-1', 40);
  20. -- 递归查询某个 id 及其子节点
  21. WITH RECURSIVE subordinates AS (
  22.     SELECT id, pid, name, orders
  23.     FROM test_tree
  24.     WHERE ID = 1
  25.     UNION ALL
  26.     SELECT t.ID, t.PID, t.Name, t.`Orders`
  27.     FROM test_tree t
  28.     INNER JOIN subordinates s ON t.PID = s.ID
  29. )
  30. SELECT * FROM subordinates;
复制代码
  1. id, pid, orders
  2. 1        0        A1        10
  3. 2        1        A1-1        20
  4. 3        1        A1-2        20
  5. 4        3        A1-2-1        30
  6. 5        3        A1-2-2        30
复制代码
拼接为 json 树

目的是为了方便前端渲染层级菜单, 通过 children 来进行拓展.
Python版
  1. from typing import List, Dict
  2. def build_tree(menu_items: List[Dict], id='id', pid='pid') -> List[Dict]:
  3.   """将菜单层级数据的 id, pid 平铺为 json 方式的"""
  4.   menu_dict = { menu.get(id): menu for menu in menu_items }
  5.   tree = []
  6.   for menu in menu_items:
  7.     if not menu.get(pid):
  8.       tree.append(menu)  # 根节点
  9.     else:
  10.       # 非根节点, 将其添加到父节点的 child 中
  11.       parent_menu = menu_dict.get(menu[pid])
  12.       print(parent_menu)
  13.       if parent_menu:
  14.         if 'children' not in parent_menu:
  15.           parent_menu['children'] = []
  16.         parent_menu['children'].append(menu)
  17.    
  18.   return tree
复制代码
Go版
  1. package main
  2. import (
  3.         "encoding/json"
  4.         "fmt"
  5. )
  6. type Menu struct {
  7.         ID       int     `json:"id"`
  8.         PID      int     `json:"parent_id"`
  9.         Name     string  `json:"name"`
  10.         Order    int     `json:"order"`
  11.         Children []*Menu `json:"children"`
  12. }
  13. func BuildMenuTree(items []*Menu) []*Menu {
  14.         nodeMap := make(map[int]*Menu)
  15.         for _, node := range items {
  16.                 nodeMap[node.ID] = node
  17.         }
  18.         var tree []*Menu
  19.         for _, node := range items {
  20.                 // 已约定 pid = 0 则为顶层节点
  21.                 if node.PID == 0 {
  22.                         tree = append(tree, node)
  23.                 } else {
  24.                         // 找到父节点,将其挂载到其 children 中
  25.                         if parent, exist := nodeMap[node.PID]; exist {
  26.                                 parent.Children = append(parent.Children, node)
  27.                         }
  28.                 }
  29.         }
  30.         return tree
  31. }
复制代码
Go 也是一样的逻辑, 只是代码编写上要复杂一点, 原因在于,

  • 它是静态编译型语言, 要确定类型, 同时结构体和 json 之间需要用到反射 reflect
  • Go 中数组是 值类型, 切片是对它的引用, 在处理中需要用到 指针, 不然会进行节点重复创建
  1. // 继续上面的测试
  2. func main() {
  3.         items := []*Menu{
  4.                 {ID: 1, PID: 0, Name: "A1", Order: 10},
  5.                 {ID: 2, PID: 1, Name: "A1-1", Order: 20},
  6.                 {ID: 3, PID: 1, Name: "A1-2", Order: 20},
  7.                 {ID: 4, PID: 3, Name: "A1-2-1", Order: 30},
  8.                 {ID: 5, PID: 3, Name: "A1-2-2", Order: 30},
  9.                 {ID: 6, PID: 0, Name: "B1", Order: 10},
  10.                 {ID: 7, PID: 6, Name: "B1-1", Order: 20},
  11.                 {ID: 8, PID: 7, Name: "B1-1-1", Order: 30},
  12.                 {ID: 9, PID: 8, Name: "B1-1-1-1", Order: 40},
  13.         }
  14.         tree := BuildMenuTree(items)
  15.         // 将树结构体 (指针, 切片, 数组, map 等) 转为 json
  16.         // prefix = "" 表示不用加前缀; indent = "  " 表示每层缩进2空格
  17.         jsonData, err := json.MarshalIndent(tree, "", "  ")
  18.         if err != nil {
  19.                 fmt.Println("转换j son 失败: ", err)
  20.                 return
  21.         }
  22.         fmt.Println(string(jsonData))
  23. }
复制代码
输出:
  1. [
  2.   {
  3.     "id": 1,
  4.     "parent_id": 0,
  5.     "name": "A1",
  6.     "order": 10,
  7.     "children": [
  8.       {
  9.         "id": 2,
  10.         "parent_id": 1,
  11.         "name": "A1-1",
  12.         "order": 20,
  13.         "children": null
  14.       },
  15.       {
  16.         "id": 3,
  17.         "parent_id": 1,
  18.         "name": "A1-2",
  19.         "order": 20,
  20.         "children": [
  21.           {
  22.             "id": 4,
  23.             "parent_id": 3,
  24.             "name": "A1-2-1",
  25.             "order": 30,
  26.             "children": null
  27.           },
  28.           {
  29.             "id": 5,
  30.             "parent_id": 3,
  31.             "name": "A1-2-2",
  32.             "order": 30,
  33.             "children": null
  34.           }
  35.         ]
  36.       }
  37.     ]
  38.   },
  39.   {
  40.     "id": 6,
  41.     "parent_id": 0,
  42.     "name": "B1",
  43.     "order": 10,
  44.     "children": [
  45.       {
  46.         "id": 7,
  47.         "parent_id": 6,
  48.         "name": "B1-1",
  49.         "order": 20,
  50.         "children": [
  51.           {
  52.             "id": 8,
  53.             "parent_id": 7,
  54.             "name": "B1-1-1",
  55.             "order": 30,
  56.             "children": [
  57.               {
  58.                 "id": 9,
  59.                 "parent_id": 8,
  60.                 "name": "B1-1-1-1",
  61.                 "order": 40,
  62.                 "children": null
  63.               }
  64.             ]
  65.           }
  66.         ]
  67.       }
  68.     ]
  69.   }
  70. ]
复制代码
用的频率还是蛮高的, 但凡涉及这种树的结构, 基本都会用到这种 id + parent_id 的方式, 同时也是 SQL 的一个必备知识点, 即 自关联 + 子查询, 这个技能必须要拿下. 真的是自从有了 AI , 似乎理解知识点都是轻而易举呢.

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册