Go 层级菜单树转 json 处理

本篇是一个补充知识点, 目的是为了下篇的后台管理系统中, 菜单权限的接口进行铺垫一下.
同时也是做个笔记, 因为在很多地方都会用这种 "树结构" 来实现很多权限, 层级, 菜单的处理哈.

在表设计层面通常是通过 idpid 来体现层级关系.

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

表结构设计

于是表设计就可以这样:

-- 菜单树的表结构 drop table if exists test_tree; create table test_tree ( 	id int auto_increment primary key comment '自增id' 	, pid int not null default 0      comment '父级id' 	, name varchar(100) not null      comment '名称' 	, orders int not null default 0   comment '排序号' );  -- 插入数据 INSERT INTO test_tree (id, pid, name, orders) VALUES  (1, 0, 'A1', 10), (2, 1, 'A1-1', 20), (3, 1, 'A1-2', 20), (4, 3, 'A1-2-1', 30), (5, 3, 'A1-2-2', 30),  (6, 0, 'B1', 10), (7, 6, 'B1-1', 20), (8, 7, 'B1-1-1', 30), (9, 8, 'B1-1-1-1', 40);  -- 递归查询某个 id 及其子节点 WITH RECURSIVE subordinates AS (     SELECT id, pid, name, orders     FROM test_tree     WHERE ID = 1     UNION ALL     SELECT t.ID, t.PID, t.Name, t.`Orders`     FROM test_tree t     INNER JOIN subordinates s ON t.PID = s.ID ) SELECT * FROM subordinates; 
id, pid, orders 1	0	A1	10 2	1	A1-1	20 3	1	A1-2	20 4	3	A1-2-1	30 5	3	A1-2-2	30 

拼接为 json 树

目的是为了方便前端渲染层级菜单, 通过 children 来进行拓展.

Python版

from typing import List, Dict  def build_tree(menu_items: List[Dict], id='id', pid='pid') -> List[Dict]:   """将菜单层级数据的 id, pid 平铺为 json 方式的"""    menu_dict = { menu.get(id): menu for menu in menu_items }   tree = []    for menu in menu_items:     if not menu.get(pid):       tree.append(menu)  # 根节点     else:       # 非根节点, 将其添加到父节点的 child 中       parent_menu = menu_dict.get(menu[pid])       print(parent_menu)       if parent_menu:         if 'children' not in parent_menu:           parent_menu['children'] = []         parent_menu['children'].append(menu)        return tree 

Go版

package main  import ( 	"encoding/json" 	"fmt" )  type Menu struct { 	ID       int     `json:"id"` 	PID      int     `json:"parent_id"` 	Name     string  `json:"name"` 	Order    int     `json:"order"` 	Children []*Menu `json:"children"` }  func BuildMenuTree(items []*Menu) []*Menu { 	nodeMap := make(map[int]*Menu) 	for _, node := range items { 		nodeMap[node.ID] = node 	}  	var tree []*Menu 	for _, node := range items { 		// 已约定 pid = 0 则为顶层节点 		if node.PID == 0 { 			tree = append(tree, node) 		} else { 			// 找到父节点,将其挂载到其 children 中 			if parent, exist := nodeMap[node.PID]; exist { 				parent.Children = append(parent.Children, node) 			} 		} 	} 	return tree } 

Go 也是一样的逻辑, 只是代码编写上要复杂一点, 原因在于,

  • 它是静态编译型语言, 要确定类型, 同时结构体和 json 之间需要用到反射 reflect
  • Go 中数组是 值类型, 切片是对它的引用, 在处理中需要用到 指针, 不然会进行节点重复创建
// 继续上面的测试 func main() { 	items := []*Menu{  		{ID: 1, PID: 0, Name: "A1", Order: 10}, 		{ID: 2, PID: 1, Name: "A1-1", Order: 20}, 		{ID: 3, PID: 1, Name: "A1-2", Order: 20}, 		{ID: 4, PID: 3, Name: "A1-2-1", Order: 30}, 		{ID: 5, PID: 3, Name: "A1-2-2", Order: 30},  		{ID: 6, PID: 0, Name: "B1", Order: 10}, 		{ID: 7, PID: 6, Name: "B1-1", Order: 20}, 		{ID: 8, PID: 7, Name: "B1-1-1", Order: 30}, 		{ID: 9, PID: 8, Name: "B1-1-1-1", Order: 40}, 	}  	tree := BuildMenuTree(items)  	// 将树结构体 (指针, 切片, 数组, map 等) 转为 json 	// prefix = "" 表示不用加前缀; indent = "  " 表示每层缩进2空格 	jsonData, err := json.MarshalIndent(tree, "", "  ") 	if err != nil { 		fmt.Println("转换j son 失败: ", err) 		return 	} 	fmt.Println(string(jsonData)) } 

输出:

[   {     "id": 1,     "parent_id": 0,     "name": "A1",     "order": 10,     "children": [       {         "id": 2,         "parent_id": 1,         "name": "A1-1",         "order": 20,         "children": null       },       {         "id": 3,         "parent_id": 1,         "name": "A1-2",         "order": 20,         "children": [           {             "id": 4,             "parent_id": 3,             "name": "A1-2-1",             "order": 30,             "children": null           },           {             "id": 5,             "parent_id": 3,             "name": "A1-2-2",             "order": 30,             "children": null           }         ]       }     ]   },   {     "id": 6,     "parent_id": 0,     "name": "B1",     "order": 10,     "children": [       {         "id": 7,         "parent_id": 6,         "name": "B1-1",         "order": 20,         "children": [           {             "id": 8,             "parent_id": 7,             "name": "B1-1-1",             "order": 30,             "children": [               {                 "id": 9,                 "parent_id": 8,                 "name": "B1-1-1-1",                 "order": 40,                 "children": null               }             ]           }         ]       }     ]   } ] 

用的频率还是蛮高的, 但凡涉及这种树的结构, 基本都会用到这种 id + parent_id 的方式, 同时也是 SQL 的一个必备知识点, 即 自关联 + 子查询, 这个技能必须要拿下. 真的是自从有了 AI , 似乎理解知识点都是轻而易举呢.

发表评论

评论已关闭。

相关文章