三十的博客

跟着官方学习 GVM 脚手架一般用法

发布时间
最后更新
阅读量 加载中...

记录下在跟着官方教程学习 GVM 的 auto-code 生成器时,项目的代码结构。

注意:

  • 当前 GVM 版本为 v2.8.2
  • 不是说明项目的所有代码结构,仅仅是关于实际开发的时候会经常接触到的代码结构。

自动代码模板配置

在后台菜单 系统工具 -> 模板配置 中可以配置项目的模板。

后台模板配置
后台模板配置
在这里我填写的模板名称是 demo ,选择的是包模板。

填写完相关信息后,观察 Git 仓库的代码结构,会发现新建了三个文件夹,以及修改了三处文件

新增部分代码

思考
新增的这些文件夹的包名都是 demo ,他们之间互相引用是否需要导包了呢?

答案1

在每个新增的文件夹中,都新增了一个 enter.go 文件,相当于模块的入口文件。

默认生成的 enter.go 文件内容如下:

go
// router/demo/enter.go

package demo

type RouterGroup struct {}

// api/v1/demo/enter.go

package demo

type ApiGroup struct {}

// service/demo/enter.go

package demo

type ServiceGroup struct {}

值得注意的是 RouterGroup ApiGroup ServiceGroup 这三个结构体命名是固定写法。

修改部分代码

修改部分代码
修改部分代码

如果所示,修改的部分就是在对应的新建包的上级包 enter.go 文件,添加对结构体的嵌入引入。

修改了文件:router/enter.go api/v1/enter.go service/enter.go

代码自动生成配置

代码自动生成配置需要依赖于自动代码模板配置。

我填写的配置参考如下:

自动化结构配置:

自动化结构配置
自动化结构配置

说明:

MyDemo:

mdo:

中文名称:

表名:

文件名称:

结构体字段配置(数据库字段):

结构体字段 Name 配置
结构体字段 Name 配置
结构体字段 Age 配置
结构体字段 Age 配置
结构体字段 Desc 配置
结构体字段 Desc 配置

前端

新增部分代码

  1. 在 src/api/demo/my-demos.js 下新增了该结构的 CRUD 一般方法。

  2. 在 src/view/demo/my-demos 文件夹下新增了 my-demos.vue 和 my-demosForm.vue 两个文件。

my-demos.vue 是一个完整的页面,包含了列表、新增、修改、删除等功能。

my-demosForm.vue 是新增的表单页面。

修改部分代码

会修改 src/pathInfo.json 文件,添加路由信息。

"/src/view/demo/my-demos/my-demos.vue": "MyDemo",
"/src/view/demo/my-demos/my-demosForm.vue": "MyDemoForm"

后端

路由入口

  1. 新增了文件 router/demo/my-demos.go
go
package demo

import (
	"github.com/flipped-aurora/gin-vue-admin/server/middleware"
	"github.com/gin-gonic/gin"
)

type MyDemoRouter struct {}

// InitMyDemoRouter 初始化 我的测试demo 路由信息
func (s *MyDemoRouter) InitMyDemoRouter(Router *gin.RouterGroup,PublicRouter *gin.RouterGroup) {
	mdoRouter := Router.Group("mdo").Use(middleware.OperationRecord())
	mdoRouterWithoutRecord := Router.Group("mdo")
	mdoRouterWithoutAuth := PublicRouter.Group("mdo")
	{
		mdoRouter.POST("createMyDemo", mdoApi.CreateMyDemo)   // 新建我的测试demo
		mdoRouter.DELETE("deleteMyDemo", mdoApi.DeleteMyDemo) // 删除我的测试demo
		mdoRouter.DELETE("deleteMyDemoByIds", mdoApi.DeleteMyDemoByIds) // 批量删除我的测试demo
		mdoRouter.PUT("updateMyDemo", mdoApi.UpdateMyDemo)    // 更新我的测试demo
	}
	{
		mdoRouterWithoutRecord.GET("findMyDemo", mdoApi.FindMyDemo)        // 根据ID获取我的测试demo
		mdoRouterWithoutRecord.GET("getMyDemoList", mdoApi.GetMyDemoList)  // 获取我的测试demo列表
	}
	{
	    mdoRouterWithoutAuth.GET("getMyDemoPublic", mdoApi.GetMyDemoPublic)  // 我的测试demo开放接口
	}
}
  1. 将方法 InitMyDemoRouter 在 initialize/router_biz.go 中引入。
go
package initialize

import (
	"github.com/flipped-aurora/gin-vue-admin/server/router"
	"github.com/gin-gonic/gin"
)

func holder(routers ...*gin.RouterGroup) {
	_ = routers
	_ = router.RouterGroupApp
}
func initBizRouter(routers ...*gin.RouterGroup) {
	privateGroup := routers[0]
	publicGroup := routers[1]
	holder(publicGroup, privateGroup) // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。
	{
		demoRouter := router.RouterGroupApp.Demo
		demoRouter.InitMyDemoRouter(privateGroup, publicGroup)
	}
}

控制器层

  1. 新增了文件 api/v1/demo/my-demos.go
go
package demo

import (

	"github.com/flipped-aurora/gin-vue-admin/server/global"
    "github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
    "github.com/flipped-aurora/gin-vue-admin/server/model/demo"
    demoReq "github.com/flipped-aurora/gin-vue-admin/server/model/demo/request"
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
)

type MyDemoApi struct {}



// CreateMyDemo 创建我的测试demo
// @Tags MyDemo
// @Summary 创建我的测试demo
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body demo.MyDemo true "创建我的测试demo"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /mdo/createMyDemo [post]
func (mdoApi *MyDemoApi) CreateMyDemo(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

	var mdo demo.MyDemo
	err := c.ShouldBindJSON(&mdo)
	if err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	err = mdoService.CreateMyDemo(ctx,&mdo)
	if err != nil {
        global.GVA_LOG.Error("创建失败!", zap.Error(err))
		response.FailWithMessage("创建失败:" + err.Error(), c)
		return
	}
    response.OkWithMessage("创建成功", c)
}

// DeleteMyDemo 删除我的测试demo
// @Tags MyDemo
// @Summary 删除我的测试demo
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body demo.MyDemo true "删除我的测试demo"
// @Success 200 {object} response.Response{msg=string} "删除成功"
// @Router /mdo/deleteMyDemo [delete]
func (mdoApi *MyDemoApi) DeleteMyDemo(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

	ID := c.Query("ID")
	err := mdoService.DeleteMyDemo(ctx,ID)
	if err != nil {
        global.GVA_LOG.Error("删除失败!", zap.Error(err))
		response.FailWithMessage("删除失败:" + err.Error(), c)
		return
	}
	response.OkWithMessage("删除成功", c)
}

// DeleteMyDemoByIds 批量删除我的测试demo
// @Tags MyDemo
// @Summary 批量删除我的测试demo
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
// @Router /mdo/deleteMyDemoByIds [delete]
func (mdoApi *MyDemoApi) DeleteMyDemoByIds(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

	IDs := c.QueryArray("IDs[]")
	err := mdoService.DeleteMyDemoByIds(ctx,IDs)
	if err != nil {
        global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
		response.FailWithMessage("批量删除失败:" + err.Error(), c)
		return
	}
	response.OkWithMessage("批量删除成功", c)
}

// UpdateMyDemo 更新我的测试demo
// @Tags MyDemo
// @Summary 更新我的测试demo
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body demo.MyDemo true "更新我的测试demo"
// @Success 200 {object} response.Response{msg=string} "更新成功"
// @Router /mdo/updateMyDemo [put]
func (mdoApi *MyDemoApi) UpdateMyDemo(c *gin.Context) {
    // 从ctx获取标准context进行业务行为
    ctx := c.Request.Context()

	var mdo demo.MyDemo
	err := c.ShouldBindJSON(&mdo)
	if err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	err = mdoService.UpdateMyDemo(ctx,mdo)
	if err != nil {
        global.GVA_LOG.Error("更新失败!", zap.Error(err))
		response.FailWithMessage("更新失败:" + err.Error(), c)
		return
	}
	response.OkWithMessage("更新成功", c)
}

// FindMyDemo 用id查询我的测试demo
// @Tags MyDemo
// @Summary 用id查询我的测试demo
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param ID query uint true "用id查询我的测试demo"
// @Success 200 {object} response.Response{data=demo.MyDemo,msg=string} "查询成功"
// @Router /mdo/findMyDemo [get]
func (mdoApi *MyDemoApi) FindMyDemo(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

	ID := c.Query("ID")
	remdo, err := mdoService.GetMyDemo(ctx,ID)
	if err != nil {
        global.GVA_LOG.Error("查询失败!", zap.Error(err))
		response.FailWithMessage("查询失败:" + err.Error(), c)
		return
	}
	response.OkWithData(remdo, c)
}
// GetMyDemoList 分页获取我的测试demo列表
// @Tags MyDemo
// @Summary 分页获取我的测试demo列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query demoReq.MyDemoSearch true "分页获取我的测试demo列表"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /mdo/getMyDemoList [get]
func (mdoApi *MyDemoApi) GetMyDemoList(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

	var pageInfo demoReq.MyDemoSearch
	err := c.ShouldBindQuery(&pageInfo)
	if err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	list, total, err := mdoService.GetMyDemoInfoList(ctx,pageInfo)
	if err != nil {
	    global.GVA_LOG.Error("获取失败!", zap.Error(err))
        response.FailWithMessage("获取失败:" + err.Error(), c)
        return
    }
    response.OkWithDetailed(response.PageResult{
        List:     list,
        Total:    total,
        Page:     pageInfo.Page,
        PageSize: pageInfo.PageSize,
    }, "获取成功", c)
}

// GetMyDemoPublic 不需要鉴权的我的测试demo接口
// @Tags MyDemo
// @Summary 不需要鉴权的我的测试demo接口
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
// @Router /mdo/getMyDemoPublic [get]
func (mdoApi *MyDemoApi) GetMyDemoPublic(c *gin.Context) {
    // 创建业务用Context
    ctx := c.Request.Context()

    // 此接口不需要鉴权
    // 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
    mdoService.GetMyDemoPublic(ctx)
    response.OkWithDetailed(gin.H{
       "info": "不需要鉴权的我的测试demo接口信息",
    }, "获取成功", c)
}
  1. 将控制层的结构体 MyDemoApi 在包的入口文件 api/v1/demo/enter.go 中引入,同时引入相关的服务层。
go
package demo

import "github.com/flipped-aurora/gin-vue-admin/server/service"

type ApiGroup struct{ MyDemoApi }

var mdoService = service.ServiceGroupApp.DemoServiceGroup.MyDemoService
  1. 在 model/demo/request/my-demos.go 中创建请求参数结构体 MyDemoSearch
go
package request

import (
	"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
	"time"
)

type MyDemoSearch struct{
    CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
      Name  *string `json:"name" form:"name"`
      Age  *int `json:"age" form:"age"`
      Desc  *string `json:"desc" form:"desc"`
    request.PageInfo
}

服务层

  1. 新增文件 service/demo/my-demos.go ,在文件中新增 MyDemoService 结构体。
go
package demo

import (
	"context"
	"github.com/flipped-aurora/gin-vue-admin/server/global"
	"github.com/flipped-aurora/gin-vue-admin/server/model/demo"
    demoReq "github.com/flipped-aurora/gin-vue-admin/server/model/demo/request"
)

type MyDemoService struct {}
// CreateMyDemo 创建我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService) CreateMyDemo(ctx context.Context, mdo *demo.MyDemo) (err error) {
	err = global.GVA_DB.Create(mdo).Error
	return err
}

// DeleteMyDemo 删除我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService)DeleteMyDemo(ctx context.Context, ID string) (err error) {
	err = global.GVA_DB.Delete(&demo.MyDemo{},"id = ?",ID).Error
	return err
}

// DeleteMyDemoByIds 批量删除我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService)DeleteMyDemoByIds(ctx context.Context, IDs []string) (err error) {
	err = global.GVA_DB.Delete(&[]demo.MyDemo{},"id in ?",IDs).Error
	return err
}

// UpdateMyDemo 更新我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService)UpdateMyDemo(ctx context.Context, mdo demo.MyDemo) (err error) {
	err = global.GVA_DB.Model(&demo.MyDemo{}).Where("id = ?",mdo.ID).Updates(&mdo).Error
	return err
}

// GetMyDemo 根据ID获取我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService)GetMyDemo(ctx context.Context, ID string) (mdo demo.MyDemo, err error) {
	err = global.GVA_DB.Where("id = ?", ID).First(&mdo).Error
	return
}
// GetMyDemoInfoList 分页获取我的测试demo记录
// Author [yourname](https://github.com/yourname)
func (mdoService *MyDemoService)GetMyDemoInfoList(ctx context.Context, info demoReq.MyDemoSearch) (list []demo.MyDemo, total int64, err error) {
	limit := info.PageSize
	offset := info.PageSize * (info.Page - 1)
    // 创建db
	db := global.GVA_DB.Model(&demo.MyDemo{})
    var mdos []demo.MyDemo
    // 如果有条件搜索 下方会自动创建搜索语句
    if len(info.CreatedAtRange) == 2 {
     db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
    }

    if info.Name != nil && *info.Name != "" {
        db = db.Where("name = ?", *info.Name)
    }
    if info.Age != nil {
        db = db.Where("age = ?", *info.Age)
    }
    if info.Desc != nil && *info.Desc != "" {
        db = db.Where("desc = ?", *info.Desc)
    }
	err = db.Count(&total).Error
	if err!=nil {
    	return
    }

	if limit != 0 {
       db = db.Limit(limit).Offset(offset)
    }

	err = db.Find(&mdos).Error
	return  mdos, total, err
}
func (mdoService *MyDemoService)GetMyDemoPublic(ctx context.Context) {
    // 此方法为获取数据源定义的数据
    // 请自行实现
}

模型层

  1. 新增文件 model/demo/my-demos.go ,在文件中新增 MyDemo 结构体。
go
// 自动生成模板MyDemo
package demo
import (
	"github.com/flipped-aurora/gin-vue-admin/server/global"
)

// 我的测试demo 结构体  MyDemo
type MyDemo struct {
    global.GVA_MODEL
  Name  *string `json:"name" form:"name" gorm:"comment:姓名desc;column:name;size:20;"`  //姓名zh
  Age  *int `json:"age" form:"age" gorm:"default:0;comment:年龄desc;column:age;size:120;"`  //年龄zh
  Desc  *string `json:"desc" form:"desc" gorm:"comment:描述desc;column:desc;size:255;"`  //描述zh
}


// TableName 我的测试demo MyDemo自定义表名 my_dmos
func (MyDemo) TableName() string {
    return "my_dmos"
}
  1. 在之前预留的模型注册文件 initialize/gorm_biz.go 中新增 MyDemo 模型。
go
package initialize

import (
	"github.com/flipped-aurora/gin-vue-admin/server/global"
	"github.com/flipped-aurora/gin-vue-admin/server/model/demo"
)

func bizModel() error {
	db := global.GVA_DB
	// 确保所有已注册模型(包括插件模型)都完成迁移
	err := db.AutoMigrate(demo.MyDemo{})
	if err != nil {
		return err
	}
	return nil
}

手动创建前端页面

一级页面

  1. 在前端代码 src 文件夹下书写相关的前端代码。
vue
<!-- src/views/myPage/index.vue -->

<template>
  <div>我的1级页面</div>
</template>

<script setup lang="ts"></script>

<style></style>
  1. 在后台 超级管理员 -> 菜单管理 页面配置菜单。
配置菜单
配置菜单
  1. 在后台 超级管理员 -> 角色管理 页面给相应的角色分配菜单。
分配菜单
分配菜单
  1. 刷新页面,即可看见新增的菜单。
页面内容
页面内容

二级页面

  1. 在后台 超级管理员 -> 菜单管理 页面配置菜单。
配置菜单
配置菜单
注意这里的路由 Name 值不能重复。
  1. 修改需要作为二级页面的子页面的菜单编辑,将 父节点 ID 选择为新增的一级菜单。
配置菜单
配置菜单
  1. 在后台 超级管理员 -> 角色管理 页面给相应的角色重新分配分配菜单。
分配菜单
分配菜单
针对当前的菜单页面,在这里需要取消勾选再重新勾选。
  1. 刷新页面,即可看见新增的二级菜单。
页面内容
页面内容

上面的方法是采用使用 GVM 预设的 src/view/routerHolder.vue 二级页面框架。

如果需要定制的话也可以复制该文件到自己的文件夹中。在菜单配置中选择自己的文件。

按钮权限

  1. 在后台 超级管理员 -> 菜单管理 页面选择相应的页面配置可控按钮。
配置可控按钮
配置可控按钮
  1. 在后台 超级管理员 -> 角色管理 页面为相应的角色分配按钮权限。
分配按钮权限
分配按钮权限
  1. 在前端代码中使用按钮权限。
vue
<template>
  <div>我的1级页面</div>
  <span v-auth="btnAuth.a"> 权限a控制 </span>
  <span v-auth="btnAuth.b"> 权限b控制 </span>
</template>

<script setup>
import { useBtnAuth } from "@/utils/btnAuth";
const btnAuth = useBtnAuth();
console.log(btnAuth, btnAuth.a, btnAuth.b);
</script>

<style></style>
  1. 刷新页面,即可看见按钮权限生效。
按钮权限生效
按钮权限生效
还可以在代码中使用 v-auth.not 指令来判断取反。

字典

  1. 在后台 超级管理员 -> 字典管理 页面新增字典。
新增字典
新增字典
  1. 在新增的字典中新增字典数据。
新增字典数据
新增字典数据
  1. 在前端代码中使用字典数据。
vue
<template>
  <div>我的1级页面</div>

  <div v-for="item in cityDict" :key="item.value">
    {{ item }}
  </div>
  {{ showDictLabel(cityDict, "beijing") }}
</template>

<script setup>
import { getDict, showDictLabel } from "@/utils/dictionary";
import { ref, onMounted } from "vue";

const cityDict = ref([]);

onMounted(async () => {
  cityDict.value = await getDict("citys");
});
</script>

<style></style>
  1. 刷新页面,即可看见字典数据生效。
字典数据生效
字典数据生效

  1. 还是需要导包的。 ↩︎

#Gvm #开发规范 #Golang