Go+Vue3开发博客系统(二)
本文最后更新于 2024-08-19,文章内容距离上一次更新已经过去了很久啦,可能已经过时了,请谨慎参考喵。
前情提要
新增菜单接口
首先呢是表设计,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
执行:
新增菜单成功,查看一下后台打印信息和数据库:
好的,没问题啊,再来测试一下第二级菜单:
再点击一下执行,模拟一下菜单名称重复的情况:
靠,发现还可以写入数据,查了一下,是有一个代码写错了,在 /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"
}
输出:
没什么问题
菜单查询列表
回到 /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,再测试一下:
再查看一下数据库:
也是没有问题的
菜单删除接口
首先先创建一个角色和菜单关系表,表结构如下:
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之后启动项目:
这里测试ID数据为1和2都是存在子菜单,不能删除,ID为3的时候删除成功,删除3之后再删除2也就可以删除了,在刚才新建的表中手动添加一个数据:
这个时候再去删除ID为1:
菜单和角色已经绑定,无法删除,清空新表的数据,ID1就可以删除了