Golang笔记|gin+gorm 实现递归菜单树状结构(RBAC权限控制)

1,487 阅读2分钟

应用场景: 在RBAC权限控制场景下,需要权限的树状结构展示数据,laravel的实现比较简单,使用模型递归关联即可,但没有用gin+gorm实现过, 查了网上资料解决了问题,但gorm2.0有些坑,需要注意,文章结尾说明,做下笔记~

场景示例图:

image.png

菜单表结构

type Menu struct {
   IdModel
   CreateAdminId int64  `gorm:"column:create_admin_id;type:int unsigned;not null" json:"create_admin_id"` // 创建菜单用户
   ParentId      int64  `gorm:"column:parent_id;type:int unsigned;not null" json:"parent_id"`             // 菜单父级
   Level         int64  `gorm:"column:level;type:int unsigned;not null" json:"level"`
   Title         string `gorm:"column:title;type:varchar(255);not null" json:"title"`                                 // 菜单标题
   Path          string `gorm:"column:path;type:varchar(255);not null" json:"path"`                                   // 菜单路径
   Component     string `gorm:"column:component;type:varchar(255);" json:"component"`                                 // 菜单组件
   Permission    string `gorm:"column:permission;type:varchar(255)" json:"permission"`                                // 菜单权限标识
   Icon          string `gorm:"column:icon;type:varchar(255)" json:"icon"`                                            // 菜单图标
   Remark        string `gorm:"column:remark;type:text;" json:"remark"`                                               // 备注
   Sort          int64  `gorm:"column:sort;type:int unsigned;default:0;not null" json:"sort"`                         // 排序
   Status        int64  `gorm:"column:status;type:tinyint unsigned;default:1;not null" json:"status"`                 // 状态 1. 正常 0 禁用
   Type          int64  `gorm:"column:type;type:tinyint unsigned;default:1;not null" json:"type"` // 类型 1. 菜单 2. 按钮
   TimeModel
}

输出结构体

type TreeMenu struct {
   models.Menu
   Children []*TreeMenu `json:"children" gorm:"-"`
}

生成树结构

func (m *menuService) GetChildrenList(menus []*modelsResponse.TreeMenu, parentId int64) []*modelsResponse.TreeMenu {
   // 定义子节点目录
   var nodes []*modelsResponse.TreeMenu
   // 判断反射值的有效性
   if reflect.ValueOf(menus).IsValid() {
      // 循环所有菜单
      for _, v := range menus {
         // 操作指定级别菜单 parentId为0是表示一级
         if v.ParentId == parentId {
            // 将子级菜单 不定长压入 children数组
            v.Children = append(v.Children, m.GetChildrenList(menus, v.ID)...)
            // 放入结果数组
            nodes = append(nodes, v)
         }
      }
   }
   return nodes
}

api接口输出

func (m *menuApi) GetTreeMenu(ctx *gin.Context) {
   var treeMenu []*modelsResponse.TreeMenu
   err := global.DB.Model(&models.Menu{}).Find(&treeMenu).Error
   if err != nil {
      response.Fail(err.Error(), ctx)
      return
   }
   // 从第一层开始递归 parentId为0的为第一层
   menu := service.MenuService.GetChildrenList(treeMenu, 0)
   // 接口输出
   response.Success(menu, ctx)
}

实现效果

image.png image.png

避坑指南

在gorm2.0下, 在定义输出结构体TreeMenu时,只是为了输出,而不想要去创建数据表,如果没有在额外字段Children后方加了gorm:"-",在接口查询时,使用TreeMenu接收时,gorm会以为children是表中的关联字段而因此报错,报错信息如下:

define a valid foreign key for relations or implement the Valuer/Scanner interface