好消息!Gin+GORM-Gen开发框架已集成完成,正在进行测试和编写使用文档中,需要的开发朋友可以等待使用及订阅哦

使用文档

1.GORM-Gem:

​​​​https://gorm.io/zh_CN/gen/create.html​

2.GORM

​https://gorm.io/zh_CN/docs/index.html​

使用介绍

GORM Gen说明

Gen是由字节跳动无恒实验室与GORM作者联合研发的一个基于GORM的安全ORM框架,其主要通过代码生成方式实现GORM代码封装。使用Gen框架能够自动生成Model结构体和类型安全的CRUD代码,极大地减少样板代码的编写,提升开发效率。

Gen框架在GORM框架的基础上提供了以下能力:

  • 基于原始SQL语句生成可重用的CRUD API
  • 生成不使用interface{}的100%安全的DAO API
  • 依据数据库生成遵循GORM约定的结构体Model
  • 支持GORM的所有特性

简单来说,使用Gen框架后我们无需手动定义结构体Model,同时Gen框架也能帮我们生成类型安全的CRUD代码。

ORM目录结构及使用说明

本框架把数据库相关代码统一存放在app\dal目录中,dal目录是存放GORM Gen数据模型模型代码生成、数据库链接等相关操作,其中cmd目录是gen生成代码程序;model目录和query目录是用cmd中程序生成的;drivers目录是存放数据库驱动程序配置操作(如:MySQL驱动程序gorm_mysql.go、postgres动程序gorm_postgres.go等);db.go文件是数据库连接实例统一调用入口。

注意:mssql是SQL Server数据库(sqlserver)

生成数据库模型

在项目app\dal\cmd目录下,通过以下命令运行生成器:

复制代码
go run gen.go

运行后,GORM Gen 会根据数据库结构,在指定的OutPath目录下(即:app\dal)生成相应的模型代码(model)和查询代码(query)。注意:每次更改数据库(新增、删除数据表和添加、删除、修改数据表字段)都要重新运行go run gen.go命令进行更新数据库模型代码和查询代码,确保与数据库一致。

ORM使用说明

数据库配置

数据库配置统一放在resource\config\config.yaml文件中,数据配置如下:

复制代码
database: #数据库配置
  default:
    #地址
    hostname: 127.0.0.1
    #端口           
    hostport: 3306
    #账号                 
    username: root
    #密码              
    password: root
    #数据库名称           
    dbname: gofly_gen_test
    #表名前缀
    prefix: gf_
    type:          "mysql"                   #数据库类型(如:mariadb/tidb/mysql/pgsql/mssql/sqlite/oracle/clickhouse/dm) 
    sqlmode:       "NO_ENGINE_SUBSTITUTION"  #设置数据库sql_mode,当数据库类型mariadb/mysql可设置,不设置留空
    extra:         "&parseTime=True"         #不同数据库的额外特性配置,由底层数据库driver定义,mysql填:&parseTime=True,如果pgsql配置:不填
    role:          "master"                  #数据库主从角色(master/slave),不使用应用层的主从机制请均设置为master
    debug:         true                      #开启调试模式,且runEnv为 开发debug和测试模式test生效
    dryrun:        false                     #生成 SQL 但不执行,可以用于准备或测试生成的 SQL
    weight:        100                       #负载均衡权重,用于负载均衡控制(预留给多个数据库扩展)
    charset:       "utf8mb4"                 #数据库编码(如: utf8/utf8mb4/gbk/gb2312),一般设置为utf8mb4,低版本数据库设置utf8
    timezone:      "Local"                   #时区配置,例如:Local",如果pgsql配置:Asia/Shanghai
    maxIdle:       10                        #连接池最大闲置的连接数
    maxOpen:       100                       #连接池最大打开的连接数
    maxLifetime:   60                        #连接对象可重复使用的时间长度(以分钟为单位)

其中 default是数据库配置默认分组,如果还有其他数据库,则复选default下配置进行修改,并把default名称改成新数据库相关命名。

调用ORM模型操作数据库

这里说明一下,框架为开发者封装好数据库调用对象,统一封装在app\dal\db.go中,然后在app\dal\drivers数据连接不同数据的驱动配置,框架值默认Mysql数据,如果使用其他数据库参考Mysql配置好dsn和打开app\dal\drivers\drivers.go中对应switch类型。

Gen数据库操作使用

Gen DAO操作

框架为开发分组数据库连接和调用方法,使用Gen Query对象则使用方法如下:

  • 引入dal和model
Go 复制代码
import (
    "gofly/dao"
    "gofly/dao/model"
}
  • 通过:dal.Query().数据表模型+对应操作,使用数据表model,直接:model.数据表模型 完整代码如下:
Go 复制代码
package demo

import (
  "fmt"
  "gofly/dao"
  "gofly/dao/model"
  "gofly/utils/gf"
)

// 这是一个GORM Gen数据库操作示例,开发业务可以参考
type Orm struct {
  NoNeedLogin []string //忽略登录接口配置-忽略全部传[*]
  NoNeedAuths []string //忽略RBAC权限认证接口配置-忽略全部传[*]
}

func init() {
  fpath := Orm{NoNeedLogin: []string{"*"}, NoNeedAuths: []string{"*"}}
  gf.Register(&fpath, fpath)
}
// 添加数据
func (api *Orm) Add(ctx *gf.GinCtx) {
  var json bookData
  if err := ctx.ShouldBind(&json); err != nil {
    gf.Failed().SetMsg(errerr.Error()).Regin(ctx)
    return
  }
  // 创建
  b1 := model.Book{
    Title:       json.Title,
    Author:      json.Author,
    Price:       json.Price,
  }
  err := dal.Query().Book.WithContext(ctx).Create(&b1)
  if err != nil {
    gf.Failed().SetMsg(fmt.Sprintf("创建数据失败:%v", err)).Regin(ctx)
    return
  }
  gf.Success().SetMsg("创建数据操作操作").SetData(b1).Regin(ctx)
}

更多使用方法可以参考:app\admin\demo\orm.go示例代码

GROM-Gen使用演示代码

demo\orm.go示例代码:

Go 复制代码
package demo

import (
	"fmt"
	"gofly/dao"
	"gofly/dao/model"
	"gofly/utils/gf"
	"gofly/utils/tools/initialize"
	"time"

	"gorm.io/gorm/clause"
	"gorm.io/hints"
)

// 这是一个GORM Gen数据库操作示例,开发业务可以参考
type Orm struct {
	NoNeedLogin []string //忽略登录接口配置-忽略全部传[*]
	NoNeedAuths []string //忽略RBAC权限认证接口配置-忽略全部传[*]
}

func init() {
	fpath := Orm{NoNeedLogin: []string{"*"}, NoNeedAuths: []string{"*"}}
	gf.Register(&fpath, fpath)
}

type bookData struct {
	Title  string `form:"title" json:"title" binding:"required" label:"标题"`
	Author string `form:"author" json:"author" binding:"required" label:"作者"`
	Price  int32  `form:"price" json:"price" binding:"required,gt=18" label:"价格"`
}

// 添加数据
func (api *Orm) Add(ctx *gf.GinCtx) {
	var json bookData
	if err := ctx.ShouldBind(&json); err != nil {
		gf.Failed().SetMsg(initialize.TranslateErr(err)).Regin(ctx)
		return
	}
	// 创建
	b1 := model.Book{
		Title:       json.Title,
		Author:      json.Author,
		PublishDate: time.Date(2023, 11, 15, 12, 0, 0, 0, time.UTC),
		Price:       json.Price,
	}
	err := dao.Query().Book.WithContext(ctx).Create(&b1)
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("创建数据失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("创建数据操作操作").SetData(b1).Regin(ctx)
}

// 使用GORM Clauses 添加数据
func (api *Orm) AddClauses(ctx *gf.GinCtx) {
	var json bookData
	if err := ctx.ShouldBind(&json); err != nil {
		gf.Failed().SetMsg(initialize.TranslateErr(err)).Regin(ctx)
		return
	}
	// 创建
	b1 := model.Book{
		Title:       json.Title,
		Author:      json.Author,
		PublishDate: time.Date(2023, 11, 15, 12, 0, 0, 0, time.UTC),
		Price:       json.Price,
	}
	err := dao.Query().Book.WithContext(ctx).Clauses(clause.OnConflict{UpdateAll: true}).Create(&b1)
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("创建数据失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("使用GORM Clauses 添加数据操作").SetData(b1).Regin(ctx)
}

// 更新数据
func (api *Orm) UpData(ctx *gf.GinCtx) {
	title := ctx.DefaultPostForm("title", "")
	author := ctx.DefaultPostForm("author", "")
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	// 更新
	ret, err := dao.Query().Book.WithContext(ctx).
		Where(dao.Query().Book.ID.Eq(gf.Int64(id))).
		UpdateColumnSimple(dao.Query().Book.Title.Value(title), dao.Query().Book.Author.Value(author))
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("update book fail, err:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("更新数据操作").SetData(ret.RowsAffected).Regin(ctx)
}

// 获取数据
// 可以使用 Unscoped 找到被软删除的记录
func (api *Orm) GetData(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	book, err := dao.Query().Book.WithContext(ctx).Where(dao.Query().Book.ID.Eq(gf.Int64(id))).First()
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("获取数据失败:%v", err)).Regin(ctx)
		return
	}
	// fmt.Println("创建时间:", book.CreatedAt.Format("2006-01-02 15:04:05"))
	// maxId, err := dao.Use().MaxValByAsc("book", "price")
	roleDB := dao.Query().AdminAuthRole
	// var menu_id_str string
	// errs := roleDB.WithContext(ctx).Where(roleDB.ID.Eq(30)).Select(roleDB.Rules).Scan(&menu_id_str)
	var super_role int32
	errs := roleDB.WithContext(ctx).Where(roleDB.ID.In(1, 29), roleDB.Rules.Eq("*")).Select(roleDB.ID).Scan(&super_role)
	fmt.Println(errs)
	gf.Success().SetMsg("获取数据成功").SetData(book).SetExdata(super_role).Regin(ctx)
}

// hints查询优化器数据列表
// 可以使用 Unscoped 找到被软删除的记录
func (api *Orm) GetHintsList(ctx *gf.GinCtx) {
	// 10秒超时
	list, err := dao.Query().Book.WithContext(ctx).Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find()
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("获取数据失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("获取数据列表").SetData(list).Regin(ctx)
}

// 查询特定字段数据
func (api *Orm) GetField(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	var book map[string]interface{}
	err := dao.Query().Book.WithContext(ctx).Where(dao.Query().Book.ID.Eq(gf.Int64(id))).
		Select(dao.Query().Book.ID,
			dao.Query().Book.Title,
			dao.Query().Book.Author,
			dao.Query().Book.CreatedAt,
			dao.Query().Book.UpdatedAt,
		).Scan(&book)
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("获取数据失败:%v", err)).Regin(ctx)
		return
	}
	if createdAt, ok := book["created_at"].(time.Time); ok {
		book["created_at"] = createdAt.Format("2006-01-02 15:04:05")
	}
	if UpdatedAt, ok := book["updated_at"].(time.Time); ok {
		book["updated_at"] = UpdatedAt.Format("2006-01-02 15:04:05")
	}
	// dao.Query().Book.WithContext(ctx).Clauses()
	gf.Success().SetMsg("获取数据成功").SetData(book).Regin(ctx)
}

// 查询特定字段数据(2)
func (api *Orm) GetOmeField(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "30")
	roleDB := dao.Query().AdminAuthRole
	var menu_id_str string
	errs := roleDB.WithContext(ctx).Where(roleDB.ID.Eq(gf.Int32(id))).Select(roleDB.Rules).Scan(&menu_id_str)
	gf.Success().SetMsg("获取数据成功").SetData(menu_id_str).SetExdata(errs).Regin(ctx)
}

// 获取数据列表
// 可以使用 Unscoped 找到被软删除的记录
func (api *Orm) GetList(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	book, err := dao.Query().Book.WithContext(ctx).Where(dao.Query().Book.ID.Gt(gf.Int64(id))).Find()
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("获取数据失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("获取数据成功").SetData(book).SetExdata(id).Regin(ctx)
}

// 获取数据列表
// 使用动态查询条件
func (api *Orm) GetListWhere(ctx *gf.GinCtx) {
	bookDb := dao.Query().Book
	var whereMap []dao.Condition
	pageNo := 1
	pageSize := 10
	title := ctx.DefaultPostForm("title", "")
	author := ctx.DefaultPostForm("author", "")
	if title != "" {
		whereMap = append(whereMap, bookDb.Title.Like("%"+title+"%"))
	}
	if author != "" {
		whereMap = append(whereMap, bookDb.Author.Eq(author))
	}
	list, totalCount, err := bookDb.WithContext(ctx).Where(whereMap...).Order(bookDb.ID.Desc()).FindByPage(dao.Offset(pageNo, pageSize), pageSize)
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("获取数据失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("获取全部列表").SetData(gf.Map{
		"page":     pageNo,
		"pageSize": pageSize,
		"total":    totalCount,
		"items":    list}).Regin(ctx)
}

// 软删除数据
func (api *Orm) Del(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	book, err := dao.Query().Book.WithContext(ctx).Where(dao.Query().Book.ID.Eq(gf.Int64(id))).Delete()
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("删除失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("删除成功").SetData(book).SetExdata(id).Regin(ctx)
}

// 硬删除数据
// 使用 Unscoped来永久删除匹配的记录
func (api *Orm) DelReal(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	book, err := dao.Query().Book.WithContext(ctx).Unscoped().Where(dao.Query().Book.ID.Eq(gf.Int64(id))).Delete()
	if err != nil {
		gf.Failed().SetMsg(fmt.Sprintf("删除失败:%v", err)).Regin(ctx)
		return
	}
	gf.Success().SetMsg("删除成功").SetData(book).SetExdata(id).Regin(ctx)
}

// 原生 SQL 更新(执行Exec)
func (api *Orm) DoSql(ctx *gf.GinCtx) {
	title := ctx.DefaultPostForm("title", "")
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	// 原生 SQL 更新
	updateSQL := "UPDATE gf_book SET title = ? WHERE id = ?"
	result := dao.DB().Exec(updateSQL, title, id) // 参数:age, id
	// fmt.Println("影响行数:", result.RowsAffected)
	gf.Success().SetMsg("原生 SQL更新成功").SetData(result.RowsAffected).SetExdata(id).Regin(ctx)
}

// 原生 SQL 查询(Raw)
func (api *Orm) GetSql(ctx *gf.GinCtx) {
	id := ctx.DefaultPostForm("id", "")
	if id == "" {
		gf.Failed().SetMsg("请传参数id").Regin(ctx)
		return
	}
	// 原生 SQL 查询
	rawSQL := "SELECT * FROM gf_book WHERE id = ? AND deleted_at IS NULL"
	// 执行原生查询
	var book model.Book
	err := dao.DB().Raw(rawSQL, id).Scan(&book).Error
	if err != nil {
		gf.Failed().SetMsg(err.Error()).Regin(ctx)
		return
	}
	gf.Success().SetMsg("原生 SQL 查询成功").SetData(book).SetExdata(id).Regin(ctx)
}

// 获取使用字典数据接口
func (api *Orm) GetDicData(ctx *gf.GinCtx) {
	param, _ := gf.RequestParam(ctx)
	dicDB := dao.Query().DictionaryGroup

	var tablename string
	dicDB.WithContext(ctx).Where(dicDB.ID.Eq(gf.Int32(param["group_id"]))).Select(dicDB.Tablename).Scan(&tablename)
	getfield := "id,keyname as label,keyvalue as value"
	if gf.DbHaseField(tablename, "tagcolor") {
		getfield = "id,keyname as label,keyvalue as value,tagcolor as color"
	}
	rawSQL := "SELECT " + getfield + " FROM " + dao.Use().TableName(tablename) + " WHERE group_id = ? AND status = 0"
	// 执行原生查询
	var list []map[string]any
	err := dao.DB().Raw(rawSQL, param["group_id"]).Scan(&list).Error
	if err != nil {
		gf.Failed().SetMsg("获取字典数据失败!").SetData(err).Regin(ctx)
	} else {
		gf.Success().SetMsg("获取字典数据列表").SetData(list).Regin(ctx)
	}

}

// 动态表名查找数据
func (api *Orm) GetDynamicTable(ctx *gf.GinCtx) {
	group_id := ctx.DefaultPostForm("group_id", "")
	title := ctx.DefaultPostForm("title", "")
	if group_id == "" {
		gf.Failed().SetMsg("请传参数group_id").Regin(ctx)
		return
	}
	query := dao.DB().Table(dao.Use().TableName("dictionary_data"))
	query = query.Where("group_id = ? ", group_id)
	if title != "" {
		query = query.Where("keyname like ?", "%"+title+"%")
	}
	var total int64
	// 总数
	query.Count(&total)
	var list gf.List
	tx := query.Limit(1).Offset(0).Find(&list)
	gf.Success().SetMsg("获取字典数据列表").SetData(list).SetExdata(gf.Map{"err": tx.Error, "total": total}).Regin(ctx)
}
相关推荐
Soonyang Zhang1 天前
nccl分析(三)——GPU-Initiated Networking(gin)数据发送过程分析
gin·nccl
呆萌很2 天前
【Gin】中间件练习题
gin
会编程的土豆3 天前
Gin 核心概念速记
数据库·后端·gin·goland
GDAL3 天前
Gin c.HTML 完整教程
html·gin
会编程的土豆3 天前
Gin 核心概念 & 前后端交互笔记
笔记·交互·gin
会编程的土豆3 天前
Gin 中 `c.BindJSON` 与 `c.JSON` 详细讲解
c语言·json·gin
会编程的土豆3 天前
Gin 核心对象:`c *gin.Context` 详细解析
服务器·c语言·gin
会编程的土豆3 天前
Gin POST 请求完整流程笔记
chrome·笔记·gin
lolo大魔王5 天前
Go 后端实战|Gin + GORM V2 + MySQL 企业级 API 项目开发(完整版)
mysql·golang·gin