本文最后更新于 2024-08-19,文章内容距离上一次更新已经过去了很久啦,可能已经过时了,请谨慎参考喵。

前情提要

https://blog.imbhj.com/archives/fsaSCzwq

新增菜单接口

首先呢是表设计,SQL语句如下:

CREATE TABLE `sys_menu` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `parent_id` int(11) DEFAULT NULL COMMENT '父级菜单ID',
    `menu_name` varchar(100) DEFAULT NULL COMMENT '菜单名称',
    `icon` varchar(100) DEFAULT NULL COMMENT '菜单图标',
    `value` varchar(100) DEFAULT NULL COMMENT '接口权限值',
    `menu_type` int(11) DEFAULT NULL COMMENT '菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)',
    `url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
    `menu_status` int(11) DEFAULT '1' COMMENT '启用状态:1-启用;2-禁用',
    `sort` int(11) DEFAULT NULL COMMENT '排序',
    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='菜单表';

实际上Navicat16的执行结果:

接下来定义菜单的结构体,新建 /model/sysMenu.go 文件:

// 菜单相关模型
// @author DaBaiLuoBo

package model

// 结构体
type SysMenu struct {

}

func (SysMenu) TableName() string {
    return "sys_menu"
}

新建菜单需要有一个创建时间的字段,会用到时间工具,新建 /utils/times.go 时间工具类:

// 时间工具类
// @author DaBaiLuoBo

package utils

import (
    "database/sql/driver"
    "fmt"
    "time"
)

type HTime struct {
    time.Time
}

var (
    formatTime = "2006-01-02 15:04:05"
)

func (t HTime) MarshalJSON() ([]byte, error) {
    formatted := fmt.Sprintf("\"%s\"", t.Format(formatTime))
    return []byte(formatted), nil
}
func (t *HTime) UnmarshalJSON(data []byte) (err error) {
    now, err := time.ParseInLocation(`"`+formatTime+`"`, string(data), time.Local)
    *t = HTime{Time: now}
    return
}

func (t HTime) Value() (driver.Value, error) {
    var zeroTime time.Time
    if t.Time.UnixNano() == zeroTime.UnixNano() {
        return nil, nil
    }
    return t.Time, nil
}

func (t *HTime) Scan(v interface{}) error {
    value, ok := v.(time.Time)
    if ok {
        *t = HTime{Time: value}
        return nil
    }
    return fmt.Errorf("Can Not Convert %v to timestamp", v)
}

回到 /model/sysMenu.go 文件:

// 菜单相关模型
// @author DaBaiLuoBo

package model

import "goblog-admin/utils"

// SysMenu 结构体定义,对应创建的表结构
type SysMenu struct {
    ID         uint        `gorm:"column:id;comment:'主键';primaryKey;NOT NULL" json:"id"`
    ParentID   uint        `gorm:"column:parent_id;comment:'父级菜单ID'" json:"parentId"`
    MenuName   string      `gorm:"column:menu_name;varchar(100);comment:'菜单名称'" json:"menuName"`
    Icon       string      `gorm:"column:icon;varchar(100);comment:'菜单图标'" json:"icon"`
    Value      string      `gorm:"column:value;varchar(100);comment:'接口权限值'" json:"value"`
    MenuType   uint        `gorm:"column:menu_type;comment:'菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)'" json:"menuType"`
    Url        string      `gorm:"column:url;varchar(100);comment:'菜单URL'" json:"url"`
    MenuStatus uint        `gorm:"column:menu_status;comment:'启用状态:1-启用;2-禁用'" json:"menuStatus"`
    Sort       uint        `gorm:"column:sort;comment:'排序'" json:"sort"`
    CreatTime  utils.HTime `gorm:"column:create_time;comment:'创建时间'" json:"createTime"`
    Children   []SysMenu   `gorm:"-" json:"children"` // 子集
}

func (SysMenu) TableName() string {
    return "sys_menu"
}

// AddSysMenuDto 新增菜单参数对象
type AddSysMenuDto struct {
    ParentID   uint   `json:"parentId"`   // 父级菜单ID
    MenuName   string `json:"menuName"`   // 菜单名称
    Icon       string `json:"icon"`       // 菜单图标
    Value      string `json:"value"`      // 接口权限值
    MenuType   uint   `json:"menuType"`   // 菜单类型
    Url        string `json:"url"`        // 菜单URL
    MenuStatus uint   `json:"menuStatus"` // 菜单状态
    Sort       uint   `json:"sort"`       // 排序
}

然后新建一个菜单的api接口 /api/sysMenu.go

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("name = ?", menuName).First(&sysMenu)
    return sysMenu
}

这里增加了一个ApiCode为:SysMenuIsExist 需要在 /result/code.go 里进行定义:

// 状态码/状态信息
// @author DaiBaiLuoBo

package result

// Codes 定义状态
type Codes struct {
    Message        map[uint]string
    Success        uint
    Failed         uint
    SysMenuIsExist uint
}

// ApiCode 状态码
var ApiCode = &Codes{
    Success:        200,
    Failed:         501,
    SysMenuIsExist: 600,
}

// 状态信息初始化
func init() {
    ApiCode.Message = map[uint]string{
        ApiCode.Success:        "成功",
        ApiCode.Failed:         "失败",
        ApiCode.SysMenuIsExist: "菜单名称已存在,请重新输入",
    }
}

// GetMessage 供给外部调用状态信息
func (c *Codes) GetMessage(code uint) string {
    message, ok := c.Message[code]
    // 如果不 ok,返回空,ok 则返回 message
    if !ok {
        return ""
    }
    return message
}

返回 /api/sysMenu.go 文件中,继续处理新增菜单:

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
    "goblog-admin/utils"
    "time"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
    // 菜单类型
    if dto.MenuType == 1 { // 菜单类型:目录
        sysMenu := &model.SysMenu{
            ParentID:   0,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 2 { // 菜单类型:菜单
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 3 { // 菜单类型:按钮
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            MenuType:   dto.MenuType,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    }
    result.Success(c, true)
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("name = ?", menuName).First(&sysMenu)
    return sysMenu
}

然后将接口增加到路由 /router/router.go 中:

router.POST("api/sysMenu/add", api.CreateSysMenu)

然后先初始化swag:(根目录下执行)

swag init

然后测试一下一级目录,打开 127.0.0.1:5001/swagger/index.html 看到接口,点击 Try it Out

点击 Execute 执行:

2658227191.png

新增菜单成功,查看一下后台打印信息和数据库:

784560556.png

2083301605.png

好的,没问题啊,再来测试一下第二级菜单:

再点击一下执行,模拟一下菜单名称重复的情况:

靠,发现还可以写入数据,查了一下,是有一个代码写错了,在 /api/sysMenu.go 文件中,修改下面一行:

core.Db.Where("menu_name = ?", menuName).First(&sysMenu)

删除数据库里重复的测试菜单数据,重新测试api:

{
  "icon": "testmenu",
  "menuName": "测试菜单",
  "menuStatus": 1,
  "menuType": 2,
  "parentId": 1,
  "sort": 1,
  "url": "test/testmenu",
  "value": "test"
}

执行后输出:

好,说明是没有问题的,再测试一下按钮:

{
  "icon": "testbutton",
  "menuName": "测试按钮",
  "menuStatus": 1,
  "menuType": 3,
  "parentId": 2,
  "sort": 1,
  "value": "test"
}

输出:

2663429619.png

2382821330.png

没什么问题

菜单查询列表

回到 /api/sysMenu.go 文件中,开始写菜单查询的功能:

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
    "goblog-admin/utils"
    "time"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
    // 菜单类型
    if dto.MenuType == 1 { // 菜单类型:目录
        sysMenu := &model.SysMenu{
            ParentID:   0,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 2 { // 菜单类型:菜单
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 3 { // 菜单类型:按钮
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            MenuType:   dto.MenuType,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    }
    result.Success(c, true)
}

// GetSysMenuList 查询菜单列表
// @Summary 查询菜单列表
// @Tags 菜单相关接口
// @Produce json
// @Description 查询菜单列表
// @Param menuName query string false "菜单名称"
// @Param menuStatus query string false "菜单状态"
// @Success 200 {object} result.Result
// @router /api/sysMenu/list [get]
func GetSysMenuList(c *gin.Context) {
    MenuName := c.Query("menuName")
    MenuStatus := c.Query("menuStatus")
    var sysMenu []model.SysMenu
    curDb := core.Db.Table("sys_menu").Order("sort")
    if MenuName != "" {
        curDb = curDb.Where("menu_name = ?", MenuName)
    }
    if MenuStatus != "" {
        curDb = curDb.Where("menu_status = ?", MenuStatus)
    }
    curDb.Find(&sysMenu)
    result.Success(c, sysMenu)
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("menu_name = ?", menuName).First(&sysMenu)
    return sysMenu
}

然后把新写的这个接口加入到路由 /router/router.go 中去:

// 路由初始化以及注册
// @author DaBaiLuoBo

package router

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    "goblog-admin/api"
    "goblog-admin/config"
)

// InitRouter 初始化先拿到Gin框架
func InitRouter() *gin.Engine {
    // 设置启动模式
    gin.SetMode(config.Config.System.Env)
    // 新建路由
    router := gin.New()
    // 设置跌机时恢复
    router.Use(gin.Recovery())
    // Register 注册
    register(router)
    // 返回路由
    return router
}

// Register 路由接口
func register(router *gin.Engine) {
    // todo 后续的所有接口 url 将在此配置
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    router.GET("/api/success", api.Success)
    router.GET("/api/failed", api.Failed)
    router.POST("/api/sysMenu/add", api.CreateSysMenu)
    router.GET("/api/sysMenu/list", api.GetSysMenuList)
}

然后执行 swag init ,初始化完毕后启动测试一下

在不传入参数的情况下先点击执行,可以看到三条数据已经查询出来了:

再测试一下传入参数查询:

也没问题哈

修改菜单

要修改首先就得根据ID去查询这个菜单的信息,接下来就写这个功能

还是在 /api/sysMenu.go 文件中:

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
    "goblog-admin/utils"
    "strconv"
    "time"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
    // 菜单类型
    if dto.MenuType == 1 { // 菜单类型:目录
        sysMenu := &model.SysMenu{
            ParentID:   0,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 2 { // 菜单类型:菜单
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 3 { // 菜单类型:按钮
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            MenuType:   dto.MenuType,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    }
    result.Success(c, true)
}

// GetSysMenuList 查询菜单列表
// @Summary 查询菜单列表
// @Tags 菜单相关接口
// @Produce json
// @Description 查询菜单列表
// @Param menuName query string false "菜单名称"
// @Param menuStatus query string false "菜单状态"
// @Success 200 {object} result.Result
// @router /api/sysMenu/list [get]
func GetSysMenuList(c *gin.Context) {
    MenuName := c.Query("menuName")
    MenuStatus := c.Query("menuStatus")
    var sysMenu []model.SysMenu
    curDb := core.Db.Table("sys_menu").Order("sort")
    if MenuName != "" {
        curDb = curDb.Where("menu_name = ?", MenuName)
    }
    if MenuStatus != "" {
        curDb = curDb.Where("menu_status = ?", MenuStatus)
    }
    curDb.Find(&sysMenu)
    result.Success(c, sysMenu)
}

// GetSysMenu 根据ID查询菜单
// @Summary 根据ID查询菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 根据ID查询菜单
// @Param id query int true "菜单ID"
// @Success 200 {object} result.Result
// @router /api/sysMenu/info [get]
func GetSysMenu(c *gin.Context) {
    Id, _ := strconv.Atoi(c.Query("id"))
    var sysMenu model.SysMenu
    core.Db.First(&sysMenu, Id)
    result.Success(c, sysMenu)
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("menu_name = ?", menuName).First(&sysMenu)
    return sysMenu
}

同样,在 /router/router.go 文件中增加路由:

// 路由初始化以及注册
// @author DaBaiLuoBo

package router

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    "goblog-admin/api"
    "goblog-admin/config"
)

// InitRouter 初始化先拿到Gin框架
func InitRouter() *gin.Engine {
    // 设置启动模式
    gin.SetMode(config.Config.System.Env)
    // 新建路由
    router := gin.New()
    // 设置跌机时恢复
    router.Use(gin.Recovery())
    // Register 注册
    register(router)
    // 返回路由
    return router
}

// Register 路由接口
func register(router *gin.Engine) {
    // todo 后续的所有接口 url 将在此配置
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    router.GET("/api/success", api.Success)
    router.GET("/api/failed", api.Failed)
    router.POST("/api/sysMenu/add", api.CreateSysMenu)
    router.GET("/api/sysMenu/list", api.GetSysMenuList)
    router.GET("/api/sysMenu/info", api.GetSysMenu)

}

然后执行 swag init ,初始化完毕后启动测试一下:

再测试一下2、3,都没问题,查询就写完了,接下来写修改菜单,首先先定义一个结构体,在 /model/sysMenu.go 文件中:

// 菜单相关模型
// @author DaBaiLuoBo

package model

import "goblog-admin/utils"

// SysMenu 结构体定义,对应创建的表结构
type SysMenu struct {
    ID         uint        `gorm:"column:id;comment:'主键';primaryKey;NOT NULL" json:"id"`                   // ID
    ParentID   uint        `gorm:"column:parent_id;comment:'父级菜单ID'" json:"parentId"`                      // 父级菜单ID
    MenuName   string      `gorm:"column:menu_name;varchar(100);comment:'菜单名称'" json:"menuName"`           // 菜单名称
    Icon       string      `gorm:"column:icon;varchar(100);comment:'菜单图标'" json:"icon"`                    // 菜单图标
    Value      string      `gorm:"column:value;varchar(100);comment:'接口权限值'" json:"value"`                 // 接口权限值
    MenuType   uint        `gorm:"column:menu_type;comment:'菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)'" json:"menuType"` // 菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)
    Url        string      `gorm:"column:url;varchar(100);comment:'菜单URL'" json:"url"`                     // 菜单URL
    MenuStatus uint        `gorm:"column:menu_status;comment:'启用状态:1-启用;2-禁用'" json:"menuStatus"`          // 启用状态:1-启用;2-禁用
    Sort       uint        `gorm:"column:sort;comment:'排序'" json:"sort"`                                   // 排序
    CreatTime  utils.HTime `gorm:"column:create_time;comment:'创建时间'" json:"createTime"`                    // 创建时间
    Children   []SysMenu   `gorm:"-" json:"children"`                                                      // 子集
}

func (SysMenu) TableName() string {
    return "sys_menu"
}

// AddSysMenuDto 新增菜单参数对象
type AddSysMenuDto struct {
    ParentID   uint   `json:"parentId"`   // 父级菜单ID
    MenuName   string `json:"menuName"`   // 菜单名称
    Icon       string `json:"icon"`       // 菜单图标
    Value      string `json:"value"`      // 接口权限值
    MenuType   uint   `json:"menuType"`   // 菜单类型
    Url        string `json:"url"`        // 菜单URL
    MenuStatus uint   `json:"menuStatus"` // 菜单状态
    Sort       uint   `json:"sort"`       // 排序
}

// UpdateSysMenuDto 修改菜单参数对象
type UpdateSysMenuDto struct {
    ID         uint   `json:"id"`         // ID
    ParentID   uint   `json:"parentId"`   // 父级菜单ID
    MenuName   string `json:"menuName"`   // 菜单名称
    Icon       string `json:"icon"`       // 菜单图标
    Value      string `json:"value"`      // 接口权限值
    MenuType   uint   `json:"menuType"`   // 菜单类型
    Url        string `json:"url"`        // 菜单URL
    MenuStatus uint   `json:"menuStatus"` // 菜单状态
    Sort       uint   `json:"sort"`       // 排序
}

继续下一步,在 /api/sysMenu.go 文件中:

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
    "goblog-admin/utils"
    "strconv"
    "time"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
    // 菜单类型
    if dto.MenuType == 1 { // 菜单类型:目录
        sysMenu := &model.SysMenu{
            ParentID:   0,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 2 { // 菜单类型:菜单
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 3 { // 菜单类型:按钮
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            MenuType:   dto.MenuType,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    }
    result.Success(c, true)
}

// GetSysMenuList 查询菜单列表
// @Summary 查询菜单列表
// @Tags 菜单相关接口
// @Produce json
// @Description 查询菜单列表
// @Param menuName query string false "菜单名称"
// @Param menuStatus query string false "菜单状态"
// @Success 200 {object} result.Result
// @router /api/sysMenu/list [get]
func GetSysMenuList(c *gin.Context) {
    MenuName := c.Query("menuName")
    MenuStatus := c.Query("menuStatus")
    var sysMenu []model.SysMenu
    curDb := core.Db.Table("sys_menu").Order("sort")
    if MenuName != "" {
        curDb = curDb.Where("menu_name = ?", MenuName)
    }
    if MenuStatus != "" {
        curDb = curDb.Where("menu_status = ?", MenuStatus)
    }
    curDb.Find(&sysMenu)
    result.Success(c, sysMenu)
}

// GetSysMenu 根据ID查询菜单
// @Summary 根据ID查询菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 根据ID查询菜单
// @Param id query int true "菜单ID"
// @Success 200 {object} result.Result
// @router /api/sysMenu/info [get]
func GetSysMenu(c *gin.Context) {
    Id, _ := strconv.Atoi(c.Query("id"))
    var sysMenu model.SysMenu
    core.Db.First(&sysMenu, Id)
    result.Success(c, sysMenu)
}

// UpdateSysMenu 修改菜单
// @Summary 修改菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 修改菜单
// @Param data body model.UpdateSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/update [put]
func UpdateSysMenu(c *gin.Context) {
    var dto model.UpdateSysMenuDto
    _ = c.BindJSON(&dto)
    var sysMenu model.SysMenu
    core.Db.First(&sysMenu, dto.ID)
    sysMenu.ParentID = dto.ParentID
    sysMenu.MenuName = dto.MenuName
    sysMenu.Icon = dto.Icon
    sysMenu.MenuType = dto.MenuType
    sysMenu.Url = dto.Url
    sysMenu.MenuStatus = dto.MenuStatus
    sysMenu.Sort = dto.Sort
    sysMenu.Value = dto.Value
    core.Db.Save(&sysMenu)
    result.Success(c, true)
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("menu_name = ?", menuName).First(&sysMenu)
    return sysMenu
}

然后还是相同的步骤,在 /router/router.go 文件中添加路由:

// 路由初始化以及注册
// @author DaBaiLuoBo

package router

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    "goblog-admin/api"
    "goblog-admin/config"
)

// InitRouter 初始化先拿到Gin框架
func InitRouter() *gin.Engine {
    // 设置启动模式
    gin.SetMode(config.Config.System.Env)
    // 新建路由
    router := gin.New()
    // 设置跌机时恢复
    router.Use(gin.Recovery())
    // Register 注册
    register(router)
    // 返回路由
    return router
}

// Register 路由接口
func register(router *gin.Engine) {
    // todo 后续的所有接口 url 将在此配置
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    router.GET("/api/success", api.Success)
    router.GET("/api/failed", api.Failed)
    router.POST("/api/sysMenu/add", api.CreateSysMenu)
    router.GET("/api/sysMenu/list", api.GetSysMenuList)
    router.GET("/api/sysMenu/info", api.GetSysMenu)
    router.PUT("/api/sysMenu/update", api.UpdateSysMenu)
}

然后初始化swag,再测试一下:

1984500949.png

再查看一下数据库:

也是没有问题的

菜单删除接口

首先先创建一个角色和菜单关系表,表结构如下:

CREATE TABLE `sys_role_menu` (
    `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
    `menu_id` int(11) DEFAULT NULL COMMENT '菜单ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='角色和菜单关系表';

实际上Navicat16的执行结果:

接下来创建一个 /model/sysRoleMenu.go 文件来定义表结构:

// 角色与菜单相关模型
// @author DaBaiLuoBo

package model

// SysRoleMenu 角色与菜单关系模型结构
type SysRoleMenu struct {
    RoleId uint `gorm:"column:role_id;comment:'角色ID';NOT NULL" json:"roleId"` // 角色ID
    MenuId uint `gorm:"column:menu_id;comment:'菜单ID';NOT NULL" json:"menuId"` // 菜单ID
}

func (SysRoleMenu) TableName() string {
    return "sys_role_menu"
}

再回到 /model/sysMenu.go 文件中,定义ID相关的结构:

// 菜单相关模型
// @author DaBaiLuoBo

package model

import "goblog-admin/utils"

// SysMenu 结构体定义,对应创建的表结构
type SysMenu struct {
    ID         uint        `gorm:"column:id;comment:'主键';primaryKey;NOT NULL" json:"id"`                   // ID
    ParentID   uint        `gorm:"column:parent_id;comment:'父级菜单ID'" json:"parentId"`                      // 父级菜单ID
    MenuName   string      `gorm:"column:menu_name;varchar(100);comment:'菜单名称'" json:"menuName"`           // 菜单名称
    Icon       string      `gorm:"column:icon;varchar(100);comment:'菜单图标'" json:"icon"`                    // 菜单图标
    Value      string      `gorm:"column:value;varchar(100);comment:'接口权限值'" json:"value"`                 // 接口权限值
    MenuType   uint        `gorm:"column:menu_type;comment:'菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)'" json:"menuType"` // 菜单类型:1-目录;2-菜单;3-按钮(接口绑定权限)
    Url        string      `gorm:"column:url;varchar(100);comment:'菜单URL'" json:"url"`                     // 菜单URL
    MenuStatus uint        `gorm:"column:menu_status;comment:'启用状态:1-启用;2-禁用'" json:"menuStatus"`          // 启用状态:1-启用;2-禁用
    Sort       uint        `gorm:"column:sort;comment:'排序'" json:"sort"`                                   // 排序
    CreatTime  utils.HTime `gorm:"column:create_time;comment:'创建时间'" json:"createTime"`                    // 创建时间
    Children   []SysMenu   `gorm:"-" json:"children"`                                                      // 子集
}

func (SysMenu) TableName() string {
    return "sys_menu"
}

// AddSysMenuDto 新增菜单参数对象
type AddSysMenuDto struct {
    ParentID   uint   `json:"parentId"`   // 父级菜单ID
    MenuName   string `json:"menuName"`   // 菜单名称
    Icon       string `json:"icon"`       // 菜单图标
    Value      string `json:"value"`      // 接口权限值
    MenuType   uint   `json:"menuType"`   // 菜单类型
    Url        string `json:"url"`        // 菜单URL
    MenuStatus uint   `json:"menuStatus"` // 菜单状态
    Sort       uint   `json:"sort"`       // 排序
}

// UpdateSysMenuDto 修改菜单参数对象
type UpdateSysMenuDto struct {
    ID         uint   `json:"id"`         // ID
    ParentID   uint   `json:"parentId"`   // 父级菜单ID
    MenuName   string `json:"menuName"`   // 菜单名称
    Icon       string `json:"icon"`       // 菜单图标
    Value      string `json:"value"`      // 接口权限值
    MenuType   uint   `json:"menuType"`   // 菜单类型
    Url        string `json:"url"`        // 菜单URL
    MenuStatus uint   `json:"menuStatus"` // 菜单状态
    Sort       uint   `json:"sort"`       // 排序
}

// SysMenuIdDto ID参数
type SysMenuIdDto struct {
    ID uint `json:"id"` // ID
}

然后就可以在 /api/sysMenu.go 中写相关的接口啦:

// 菜单相关接口
// @author DaBaiLuoBo

package api

import (
    "github.com/gin-gonic/gin"
    "goblog-admin/core"
    "goblog-admin/model"
    "goblog-admin/result"
    "goblog-admin/utils"
    "strconv"
    "time"
)

// CreateSysMenu 新增菜单
// @Summary 新增菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 新增菜单
// @Param data body model.AddSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/add [post]
func CreateSysMenu(c *gin.Context) {
    // 绑定 JSON 数据(参数)
    var dto model.AddSysMenuDto
    // JSON 交互模式
    _ = c.BindJSON(&dto)
    // 查询菜单不能重复
    sysMenuByName := GetSysMenuByName(dto.MenuName)
    if sysMenuByName.ID != 0 {
        result.Failed(c, int(result.ApiCode.SysMenuIsExist), result.ApiCode.GetMessage(result.ApiCode.SysMenuIsExist))
        return
    }
    // 菜单类型
    if dto.MenuType == 1 { // 菜单类型:目录
        sysMenu := &model.SysMenu{
            ParentID:   0,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 2 { // 菜单类型:菜单
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            Icon:       dto.Icon,
            MenuType:   dto.MenuType,
            Url:        dto.Url,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    } else if dto.MenuType == 3 { // 菜单类型:按钮
        sysMenu := &model.SysMenu{
            ParentID:   dto.ParentID,
            MenuName:   dto.MenuName,
            MenuType:   dto.MenuType,
            Value:      dto.Value,
            MenuStatus: dto.MenuStatus,
            Sort:       dto.Sort,
            CreatTime:  utils.HTime{Time: time.Now()},
        }
        core.Db.Create(&sysMenu)
    }
    result.Success(c, true)
}

// GetSysMenuList 查询菜单列表
// @Summary 查询菜单列表
// @Tags 菜单相关接口
// @Produce json
// @Description 查询菜单列表
// @Param menuName query string false "菜单名称"
// @Param menuStatus query string false "菜单状态"
// @Success 200 {object} result.Result
// @router /api/sysMenu/list [get]
func GetSysMenuList(c *gin.Context) {
    MenuName := c.Query("menuName")
    MenuStatus := c.Query("menuStatus")
    var sysMenu []model.SysMenu
    curDb := core.Db.Table("sys_menu").Order("sort")
    if MenuName != "" {
        curDb = curDb.Where("menu_name = ?", MenuName)
    }
    if MenuStatus != "" {
        curDb = curDb.Where("menu_status = ?", MenuStatus)
    }
    curDb.Find(&sysMenu)
    result.Success(c, sysMenu)
}

// GetSysMenu 根据ID查询菜单
// @Summary 根据ID查询菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 根据ID查询菜单
// @Param id query int true "菜单ID"
// @Success 200 {object} result.Result
// @router /api/sysMenu/info [get]
func GetSysMenu(c *gin.Context) {
    Id, _ := strconv.Atoi(c.Query("id"))
    var sysMenu model.SysMenu
    core.Db.First(&sysMenu, Id)
    result.Success(c, sysMenu)
}

// UpdateSysMenu 修改菜单
// @Summary 修改菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 修改菜单
// @Param data body model.UpdateSysMenuDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/update [put]
func UpdateSysMenu(c *gin.Context) {
    var dto model.UpdateSysMenuDto
    _ = c.BindJSON(&dto)
    var sysMenu model.SysMenu
    core.Db.First(&sysMenu, dto.ID)
    sysMenu.ParentID = dto.ParentID
    sysMenu.MenuName = dto.MenuName
    sysMenu.Icon = dto.Icon
    sysMenu.MenuType = dto.MenuType
    sysMenu.Url = dto.Url
    sysMenu.MenuStatus = dto.MenuStatus
    sysMenu.Sort = dto.Sort
    sysMenu.Value = dto.Value
    core.Db.Save(&sysMenu)
    result.Success(c, true)
}

// DeleteSysMenu 根据ID删除菜单
// @Summary 根据ID删除菜单
// @Tags 菜单相关接口
// @Produce json
// @Description 根据ID删除菜单
// @Param data body model.SysMenuIdDto true "data"
// @Success 200 {object} result.Result
// @router /api/sysMenu/delete [delete]
func DeleteSysMenu(c *gin.Context) {
    var dto model.SysMenu
    _ = c.BindJSON(&dto)
    // 菜单已分配不能删除
    sysRoleMenu := GetSysRoleMenu(dto.ID)
    if sysRoleMenu.MenuId > 0 {
        result.Failed(c, int(result.ApiCode.DelSysMenuFailed), result.ApiCode.GetMessage(result.ApiCode.DelSysMenuFailed))
        return
    }
    // 存在子菜单不能删除
    sysChildMenu := GetChildSysMenu(dto.ID)
    if sysChildMenu.ParentID > 0 {
        result.Failed(c, int(result.ApiCode.DelSysChildMenuFailed), result.ApiCode.GetMessage(result.ApiCode.DelSysChildMenuFailed))
        return
    }
    // 可以删除
    core.Db.Delete(&model.SysMenu{}, dto.ID)
    result.Success(c, true)
}

// GetSysMenuByName 根据菜单名称查询菜单数据
func GetSysMenuByName(menuName string) (sysMenu model.SysMenu) {
    core.Db.Where("menu_name = ?", menuName).First(&sysMenu)
    return sysMenu
}

// GetSysRoleMenu 查询是否分配菜单
func GetSysRoleMenu(id uint) (sysRoleMenu model.SysRoleMenu) {
    core.Db.Where("menu_id = ?", id).First(&sysRoleMenu)
    return sysRoleMenu
}

// GetChildSysMenu 查询是否存在子菜单
func GetChildSysMenu(id uint) (sysMenu model.SysMenu) {
    core.Db.Where("parent_id = ?", id).First(&sysMenu)
    return sysMenu
}

然后在 /result/code.go 中定义一下新加的两个错误码:

// 状态码/状态信息
// @author DaiBaiLuoBo

package result

// Codes 定义状态
type Codes struct {
    Message               map[uint]string
    Success               uint
    Failed                uint
    SysMenuIsExist        uint
    DelSysMenuFailed      uint
    DelSysChildMenuFailed uint
}

// ApiCode 状态码
var ApiCode = &Codes{
    Success:               200,
    Failed:                501,
    SysMenuIsExist:        600,
    DelSysMenuFailed:      601,
    DelSysChildMenuFailed: 602,
}

// 状态信息初始化
func init() {
    ApiCode.Message = map[uint]string{
        ApiCode.Success:               "成功",
        ApiCode.Failed:                "失败",
        ApiCode.SysMenuIsExist:        "菜单名称已存在,请重新输入",
        ApiCode.DelSysMenuFailed:      "菜单已分配,不能删除",
        ApiCode.DelSysChildMenuFailed: "该菜单存在子菜单,不能删除",
    }
}

// GetMessage 供给外部调用状态信息
func (c *Codes) GetMessage(code uint) string {
    message, ok := c.Message[code]
    // 如果不 ok,返回空,ok 则返回 message
    if !ok {
        return ""
    }
    return message
}

最后,将接口加入到路由 /router/router.go 中去:

// 路由初始化以及注册
// @author DaBaiLuoBo

package router

import (
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    "goblog-admin/api"
    "goblog-admin/config"
)

// InitRouter 初始化先拿到Gin框架
func InitRouter() *gin.Engine {
    // 设置启动模式
    gin.SetMode(config.Config.System.Env)
    // 新建路由
    router := gin.New()
    // 设置跌机时恢复
    router.Use(gin.Recovery())
    // Register 注册
    register(router)
    // 返回路由
    return router
}

// Register 路由接口
func register(router *gin.Engine) {
    // todo 后续的所有接口 url 将在此配置
    router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    router.GET("/api/success", api.Success)
    router.GET("/api/failed", api.Failed)
    router.POST("/api/sysMenu/add", api.CreateSysMenu)
    router.GET("/api/sysMenu/list", api.GetSysMenuList)
    router.GET("/api/sysMenu/info", api.GetSysMenu)
    router.PUT("/api/sysMenu/update", api.UpdateSysMenu)
    router.DELETE("/api/sysMenu/delete", api.DeleteSysMenu)
}

当然还是得测试一下,初始化swag之后启动项目:

3699518352.png

这里测试ID数据为1和2都是存在子菜单,不能删除,ID为3的时候删除成功,删除3之后再删除2也就可以删除了,在刚才新建的表中手动添加一个数据:

这个时候再去删除ID为1:

293186313.png

菜单和角色已经绑定,无法删除,清空新表的数据,ID1就可以删除了