柚子快报邀请码778899分享:golang gin gorm

http://yzkb.51969.com/

目录

一. 先了解jen代码生成二. gorm.io/gorm 新版本 gorm + jen 生成表对应结构体介绍 gorm Migratorgorm Migrator 读取数据库表结构 + jen 生成对应结构体

三. github.com/jinzhu/gorm 老版本 gorm + jen 生成表结构

一. 先了解jen代码生成

有一个专门用于代码生成的三方库"github.com/dave/jennifer/jen",下方一个简单的示例 它可以让你以编程的方式创建和修改.go文件

package main

import "github.com/dave/jennifer/jen"

func main() {

//导包 "github.com/dave/jennifer/jen"

//1.通过jen可以创建文件,入参指定的是pacakge

f := jen.NewFile("main")

//2.在文件中添加结构体的定义

f.Type().Id("Person").Struct(

//2.1添加属性

jen.Id("Age").Int(),

//设置属性的tag

jen.Id("ID").Int64().Tag(map[string]string{

"gorm": "primary_key",

}),

jen.Id("Name").String().Tag(map[string]string{

"json": "name",

}),

)

//3.在文件中添加函数

f.Func().Id("main").Params().Block(

// 创建一个结构体变量

jen.Id("p").Op(":=").Id("Person").Values(

jen.Id("Name").Op(":").Lit("Alice"),

jen.Id("Age").Op(":").Lit(20),

),

// 调用函数

jen.Id("p").Dot("PrintInfo").Call(),

)

//4.在文件中给结构体绑定方法

f.Func().Params(jen.Id("p").Op("*").Id("Person")).Id("PrintInfo").Params().Block(

jen.Qual("fmt", "Printf").Call(

jen.Lit("Name: %s, Age: %d\n"),

jen.Id("p").Dot("Name"),

jen.Id("p").Dot("Age"),

),

)

//5.输出保存到文件

f.Save("example.go")

}

执行完毕后会生成如下example.go文件

package main

import "fmt"

type Person struct {

Age int

ID int64 `gorm:"primary_key"`

Name string `json:"name"`

}

func main() {

p := Person{Name: "Alice", Age: 20}

p.PrintInfo()

}

func (p *Person) PrintInfo() {

fmt.Printf("Name: %s, Age: %d\n", p.Name, p.Age)

}

二. gorm.io/gorm 新版本 gorm + jen 生成表对应结构体

介绍 gorm Migrator

在使用新版本的gorm时底层有一个Migrator迁移器,内部提供了一些用来创建,修改,删除,以及获取数据库的元数据信息的方法

CurrentDatabase:用于获取当前数据库的名称

TableType:用于获取一个表的类型,是普通表还是视图,返回一个TableType类型的值

ColumnTypes: 用于获取表字段信息的

GetIndexes: 用户获取索引信息的

AddColumn:用于给一个表添加一个列,根据你定义的结构体的字段来生成对应的类型和约束

AutoMigrate:用于自动迁移你的schema,保持你的schema是最新的。它会创建表,缺少的外键,约束,列和索引,并且会更改现有列的类型(如果其大小、精度、是否为空可更改)

FullDataTypeOf:用于获取给定字段的完整数据类型,包括长度、精度、是否为空等

GetTypeAliases:用于获取给定数据库类型名称的别名,例如int和integer

CreateTable:用于创建一个或多个表,根据你定义的结构体来生成对应的字段和类型,以及主键、外键、索引等约束

DropTable:用于删除一个或多个表,如果表不存在,不会报错

HasTable:用于检查一个表是否存在,可以传入表名或结构体

RenameTable:用于重命名一个表,可以传入旧表名和新表名,或者旧结构体和新结构体

GetTables:用于获取数据库中所有表的名称,返回一个字符串切片

package main

import (

"fmt"

"github.com/dave/jennifer/jen"

_ "github.com/go-sql-driver/mysql"

"gorm.io/driver/mysql"

"gorm.io/gorm" //TODO 注意与"github.com/jinzhu/gorm"的不同,"github.com/jinzhu/gorm"是gorm的老版本

"gorm.io/gorm/logger"

"strconv"

"strings"

)

var db *gorm.DB

var err error

func init() {

// 连接数据库,开启日志模式

dsn := "账号:密码@tcp(ip:3306)/lm_pms?charset=utf8mb4&parseTime=True&loc=Local"

db, err = gorm.Open(

mysql.Open(dsn),

&gorm.Config{Logger: logger.Default.LogMode(logger.Info)},

)

if err != nil {

panic("failed to connect database")

}

}

func main() {

//首先通过Raw()可以执行原生SQL

//当前执行"SHOW CREATE TABLE hotel_list"获取指定表的建表语句

rows, err := db.Raw("SHOW CREATE TABLE hotel_list").Rows()

if err != nil {

fmt.Println(err)

}

for rows.Next() {

var table string

var sql string

rows.Scan(&table, &sql)

fmt.Println(table, sql)

}

//1.通过Migrator()方法可以获取到一个Migrator迁移器

//这个迁移器内部提供了一些用来创建,修改,删除,以及获取数据库的元数据信息的方法

//我们可以点进去看一下

migrator := db.Migrator()

//3.调用ColumnTypes()获取表的列类型信息

columns, err := migrator.ColumnTypes("hotel_list")

if err != nil {

fmt.Println(err)

}

for _, column := range columns {

// 获取列的名称,类型,长度,是否为空等信息

name := column.Name()

valtype := column.DatabaseTypeName()

length, _ := column.Length()

nullable, _ := column.Nullable()

valDefault, _ := column.DefaultValue()

comment, _ := column.Comment()

fmt.Printf("name: %v, valtype: %v,length %v, nullable %v, valDefault %v, comment %v", name, valtype, length, nullable, valDefault, comment)

}

//4.调用

Index, err := migrator.GetIndexes("users")

tab, err := migrator.TableType("users")

fmt.Print(Index, tab)

}

gorm Migrator 读取数据库表结构 + jen 生成对应结构体

package main

import (

"github.com/dave/jennifer/jen"

_ "github.com/go-sql-driver/mysql"

"gorm.io/driver/mysql"

"gorm.io/gorm" //TODO 注意与"github.com/jinzhu/gorm"的不同,"github.com/jinzhu/gorm"是gorm的老版本

"gorm.io/gorm/logger"

"strings"

)

var db *gorm.DB

func init() {

// 连接数据库,开启日志模式

dsn := "账号:密码@tcp(ip:3306)/lm_pms?charset=utf8mb4&parseTime=True&loc=Local"

db, err := gorm.Open(

mysql.Open(dsn),

&gorm.Config{Logger: logger.Default.LogMode(logger.Info)},

)

if err != nil {

panic("failed to connect database")

}

db = db

}

func main() {

//1.创建一个jen文件对象,用于生成代码

f := jen.NewFile("main")

//2.需要生成结构体的表名

dbTableName := "pms_billpay_log"

//3.获取迁移器,通过迁移器获取表信息,字段信息

migrator := db.Migrator()

tab, err := migrator.TableType(dbTableName)

if nil != err {

panic(err)

}

columns, err := migrator.ColumnTypes(dbTableName)

if err != nil {

panic(err)

}

//4.获取表名,将表名转换为驼峰命名并且首字母大写

name := underscoreToCamelCase(tab.Name())

//5.生成结构体

f.Type().Id(name).StructFunc(func(g *jen.Group) {

for _, c := range columns {

//5.1获取属性名

statement := g.Id(underscoreToCamelCase(c.Name()))

//5.2获取属性类型,将mysql数据类型转换为golang中使用的

//TODO 注意此处只是简单的判断了几个类型,要根据实际需求进行转换

cType := c.DatabaseTypeName()

switch cType {

case "int", "tinyint", "smallint":

statement.Int()

case "mediumint", "bigint":

statement.Qual("github.com/shopspring/decimal", "Decimal")

case "float":

statement.Float32()

case "double":

statement.Float64()

case "decimal":

statement.Qual("github.com/shopspring/decimal", "Decimal")

case "char", "varchar", "text", "tinytext", "mediumtext", "longtext", "blob":

statement.String()

case "date", "time", "datetime", "timestamp":

statement.Qual("time", "Time")

case "bit", "bool":

statement.Bool()

case "json":

statement.Interface()

default:

statement.Interface()

}

//5.3获取属性后的tag,设置tag

tagM := getTagMap(c)

statement.Tag(tagM)

}

})

//6.生成保存结构体的文件

f.Save("example.go")

}

// 组装属性tag

// TODO 在组装tag时,并不是越多越好,例如默认值, 是否允许为空,数据类型,数据长度等,如果代码中使用不到,就不要组装了,因为在后续迭代过程中,由于遗漏,可能会出现数据库表设置与当前代码tag中的不一致

func getTagMap(column gorm.ColumnType) map[string]string {

tagStr := ""

if f, _ := column.PrimaryKey(); f {

tagStr += "primary_key;"

}

if f, _ := column.Unique(); f {

tagStr += "Unique;"

}

/*if f, _ := column.AutoIncrement(); f {

tagStr += "autoIncrement;"

}*/

if f, _ := column.Nullable(); !f {

tagStr += "not null;"

}

if t, ok := column.ColumnType(); ok {

tagStr += "type:" + t + ";"

}

/*if le, ok := column.Length(); ok {

tagStr += "(" + strconv.FormatInt(le, 10) + ");"

} else {

tagStr += ";"

}*/

tagStr += "column:" + column.Name() + ";"

if comment, ok := column.Comment(); ok && len(comment) > 0 {

tagStr += "comment:'" + comment + "';"

}

if defaultVal, ok := column.DefaultValue(); ok && len(defaultVal) > 0 {

tagStr += "default:" + defaultVal + ";"

}

m := make(map[string]string)

m["gorm"] = tagStr

name := column.Name()

m["json"] = lowerFirst(lowerFirst(underscoreToCamelCase(name)))

return m

}

// 将带下划线的数据库命名转换为驼峰命名,首字母大写

func underscoreToCamelCase(name string) string {

result := ""

if !strings.Contains(name, "_") {

first := strings.Title(name[:1])

return first + name[1:]

}

parts := strings.Split(name, "_")

// 遍历字符串切片,对每个部分进行转换

for _, part := range parts {

// 使用Title函数,将每个部分的首字母转换为大写

part = strings.Title(part)

// 使用Replace函数,将每个部分的其他字母转换为小写

part = strings.Replace(part, part[1:], strings.ToLower(part[1:]), 1)

// 将转换后的部分拼接到结果字符串中

result += part

}

// 返回结果字符串

return result

}

// 字符串首字母小写

func lowerFirst(str string) string {

// 判断字符串是否为空

if str == "" {

// 如果为空,直接返回空字符串

return ""

}

// 使用strings包的ToLower方法,将字符串的第一个字符转换为小写

first := strings.ToLower(str[:1])

// 将转换后的第一个字符和剩余的字符串拼接起来,返回结果

return first + str[1:]

}

三. github.com/jinzhu/gorm 老版本 gorm + jen 生成表结构

老版本的gorm中没有Migrator , 所以编写专门读取指定表信息的sql,例如读取指定表的字段名,字段类型,字段索引,最大长度,默认值,是否运行为null,注释等信息,然后使用jen生成对应该表的结构体

package main

import (

"github.com/dave/jennifer/jen"

_ "github.com/go-sql-driver/mysql"

"github.com/jinzhu/gorm" //TODO 注意与"gorm.io/gorm"的不同,"github.com/jinzhu/gorm"是gorm的老版本

"log"

"strconv"

"strings"

)

// 1.定义用于存储表字段信息的结构体

type Column struct {

ColumnName string `gorm:"column:COLUMN_NAME"` //字段名称

ColumnKey string `gorm:"column:COLUMN_KEY"` //索引类型

DataType string `gorm:"column:DATA_TYPE"` //数据类型

CharacterMaximumLength int `gorm:"column:CHARACTER_MAXIMUM_LENGTH"` //设置的最大长度限制

IsNullable string `gorm:"column:IS_NULLABLE"` //是否可以为空

ColumnDefault string `gorm:"column:COLUMN_DEFAULT"` //默认值

ColumnComment string `gorm:"column:COLUMN_COMMENT"` //注释

}

// 2.数据库地址

const dsn = "账号:密码@tcp(ip:3306)/lm_pms?charset=utf8mb4&parseTime=True&loc=Local"

// 3.需要生成的表所在库名

const databaseName = "lm_pms"

// 4.需要生成的表名

const tableName = "pms_billpay_log"

// 5.表名的驼峰命名并且首字母大写

var TabNameCamelCaseUp = underscoreToCamelCase(tableName)

// 6.表名的驼峰命名,并且首字母小写

var TabNameCamelCaselower = lowerFirst(TabNameCamelCaseUp)

func main() {

//1.获取数据库连接

db, err := gorm.Open("mysql", dsn)

if err != nil {

log.Fatal(err)

}

defer db.Close()

//2.执行sql查询指定表的所有字段信息,包括(字段名,数据类型,索引,设置的最大长度,是否允许为null,默认值,注释)

var columns []Column

sql := "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT FROM information_schema.columns WHERE table_schema = '" + databaseName + "' AND table_name = '" + tableName + "';"

err = db.Debug().Raw(sql).Scan(&columns).Error

if err != nil {

panic(err)

}

//3.使用jen开始创建并生成对应表的代码,指定代码所在包

f := jen.NewFile("main")

//4.封装结构体

f.Type().Id(TabNameCamelCaseUp).StructFunc(func(g *jen.Group) {

for _, c := range columns {

//4.1将数据库字段名转换为结构体需要的属性名

statement := g.Id(underscoreToCamelCase(c.ColumnName))

//4.2获取属性类型,将mysql数据类型转换为golang中使用的

//TODO 注意此处只是简单的判断了几个类型,要根据实际需求进行转换

switch c.DataType {

case "int", "tinyint", "smallint":

statement.Int()

case "mediumint", "bigint":

statement.Qual("github.com/shopspring/decimal", "Decimal")

case "float":

statement.Float32()

case "double":

statement.Float64()

case "decimal":

statement.Qual("github.com/shopspring/decimal", "Decimal")

case "char", "varchar", "text", "tinytext", "mediumtext", "longtext", "blob":

statement.String()

case "date", "time", "datetime", "timestamp":

statement.Qual("time", "Time")

case "bit", "bool":

statement.Bool()

case "json":

statement.Interface()

default:

statement.Interface()

}

//4.3获取属性后的tag,设置tag

tagM := getTagMap(c)

statement.Tag(tagM)

}

})

f.Comment("TODO 代表数据库连接的全局变量,项目启动时先初始化数据库连接,在执行操作数据库方法时就可以直接使用全局变量的连接,不要来回传递了," +

"此处是为了防止生成的代码报错添加的,项目中如果已经存在数据库连接变量,将生成代码中的这个变量删除,直接使用已经存在的就可以")

f.Var().Id("db").Op("*").Qual("github.com/jinzhu/gorm", "DB")

//6.生成TableName方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("TableName").Params().String().Block(

jen.Return(jen.Lit(tableName)),

)

//7.生成Add方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("Add").Params().Error().Block(

jen.Return(jen.Id("db").Dot("Create").Call(jen.Id("c")).Dot("Error")),

)

//8.生成Update方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("Update").Params().Error().Block(

jen.Return(jen.Id("db").Dot("Model").Call(jen.Id("c")).Dot("Update").Call(jen.Id("c")).Dot("Error")),

)

//9.生成UpdateSave方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("UpdateSave").Params().Error().Block(

jen.Return(jen.Id("db").Dot("Model").Call(jen.Id("c")).Dot("Save").Call(jen.Id("c")).Dot("Error")),

)

//10.生成SearchFirst方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("SearchFirst").Params().Error().Block(

jen.Return(jen.Id("db").Dot("Model").Call(jen.Id("c")).Dot("Where").Call(jen.Id("c")).Dot("First").Call(jen.Id("c")).Dot("Error")),

)

//11.生成Search方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("Search").Params().Params(jen.Index().Id(TabNameCamelCaseUp), jen.Error()).Block(

jen.Var().Id(TabNameCamelCaselower+"List").Index().Id(TabNameCamelCaseUp),

jen.Err().Op(":=").Id("db").Dot("Model").Call(jen.Id("c")).Dot("Where").Call(jen.Id("c")).Dot("Find").Call(jen.Op("&").Id(TabNameCamelCaselower+"List")).Dot("Error"),

jen.Return(jen.Id(TabNameCamelCaselower+"List"), jen.Err()),

)

//12.生成SelectById方法

f.Func().Id("SelectById").Params(jen.Id("id").Int()).Params(jen.Op("*").Id(TabNameCamelCaseUp), jen.Error()).Block(

jen.Var().Id(TabNameCamelCaselower).Id(TabNameCamelCaseUp),

jen.Err().Op(":=").Id("db").Dot("First").Call(jen.Op("&").Id(TabNameCamelCaselower), jen.Id("id")).Dot("Error"),

jen.If(jen.Err().Op("!=").Nil()).Block(

jen.Return(jen.Nil(), jen.Err()),

),

jen.Return(jen.Op("&").Id(TabNameCamelCaselower), jen.Nil()),

)

//13.生成SelectByPage方法

f.Func().Params(jen.Id("c").Op("*").Id(TabNameCamelCaseUp)).Id("SelectByPage").Params(jen.Id("pageNo").Int(), jen.Id("pageSize").Int()).Params(jen.Index().Id(TabNameCamelCaseUp), jen.Int(), jen.Error()).Block(

jen.Var().Id(TabNameCamelCaselower+"List").Index().Id(TabNameCamelCaseUp),

jen.Var().Id("count").Int(),

jen.Id("db").Op("=").Id("db").Dot("Model").Call(jen.Op("&").Id(TabNameCamelCaseUp+"{}")).Dot("Where").Call(jen.Id("c")),

jen.Id("limit").Op(":=").Id("pageSize"),

jen.Id("offset").Op(":=").Id("pageSize").Op("*").Call(jen.Id("pageNo").Op("-").Lit(1)),

jen.Comment("注意Limit 方法和 Offset 方法必须在 Find 方法之前调用,否则会出现错误。"),

jen.Err().Op(":=").Id("db").Dot("Count").Call(jen.Op("&").Id("count")).Dot("Limit").Call(jen.Id("limit")).Dot("Offset").Call(jen.Id("offset")).Dot("Find").Call(jen.Op("&").Id(TabNameCamelCaselower+"List")).Dot("Error"),

jen.If(jen.Err().Op("!=").Nil()).Block(

jen.Return(jen.Nil(), jen.Lit(0), jen.Err()),

),

jen.Return(jen.Id(TabNameCamelCaselower+"List"), jen.Id("count"), jen.Nil()),

)

//14.保存文件

err = f.Save(tableName + ".go")

if err != nil {

log.Fatal(err)

}

}

// 组装属性tag

// TODO 在组装tag时,并不是越多越好,例如默认值, 是否允许为空,数据类型,数据长度等,如果代码中使用不到,就不要组装了,因为在后续迭代过程中,由于遗漏,可能会出现数据库表设置与当前代码tag中的不一致

func getTagMap(column Column) map[string]string {

tagStr := ""

//拼接索引

//TODO 当前只判断了主键索引

if column.ColumnKey == "PRI" {

tagStr += "primary_key;"

}

if column.IsNullable == "NO" {

tagStr += "not null;"

}

tagStr += column.DataType

if column.CharacterMaximumLength > 0 {

tagStr += "(" + strconv.Itoa(column.CharacterMaximumLength) + ")"

}

tagStr += ";"

tagStr += "column:" + column.ColumnName + ";"

if len(column.ColumnComment) > 0 {

tagStr += "comment:'" + column.ColumnComment + "';"

}

if len(column.ColumnDefault) > 0 {

tagStr += "default:" + column.ColumnDefault + ";"

}

m := make(map[string]string)

m["gorm"] = tagStr

m["json"] = lowerFirst(lowerFirst(underscoreToCamelCase(column.ColumnName)))

return m

}

// 将带下划线的数据库命名转换为驼峰命名,首字母大写

func underscoreToCamelCase(name string) string {

result := ""

if !strings.Contains(name, "_") {

first := strings.Title(name[:1])

return first + name[1:]

}

parts := strings.Split(name, "_")

// 遍历字符串切片,对每个部分进行转换

for _, part := range parts {

// 使用Title函数,将每个部分的首字母转换为大写

part = strings.Title(part)

// 使用Replace函数,将每个部分的其他字母转换为小写

part = strings.Replace(part, part[1:], strings.ToLower(part[1:]), 1)

// 将转换后的部分拼接到结果字符串中

result += part

}

// 返回结果字符串

return result

}

// 字符串首字母小写

func lowerFirst(str string) string {

// 判断字符串是否为空

if str == "" {

// 如果为空,直接返回空字符串

return ""

}

// 使用strings包的ToLower方法,将字符串的第一个字符转换为小写

first := strings.ToLower(str[:1])

// 将转换后的第一个字符和剩余的字符串拼接起来,返回结果

return first + str[1:]

}

柚子快报邀请码778899分享:golang gin gorm

http://yzkb.51969.com/

精彩内容

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。