使用文档
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)
}