go-gorm / gen

Gen: Friendly & Safer GORM powered by Code Generation
https://gorm.io/gen/
MIT License
2.29k stars 297 forks source link

分享一下我二开的gen版本,使用起来容易了很多很多 #777

Closed hinego closed 1 year ago

hinego commented 1 year ago

前景提要

gorm-gen这个项目我比较喜欢,但是使用起来的时候很麻烦,例如实现以下功能时:

诚然,这些功能都提供了function去实现,但是使用起来体验太差了,因为这些功能都是额外设定的规则,需要去学习熟悉!

我的解决方案?

我习惯于通过golang写gorm的model结构体,然后生成数据库结构!再通过gorm把生成的结构生成成model,但是如果按原方案就需要额外写挺多代码来实现上面写的这些功能,我并不想,所以我复用了我自己写的model结构体

例如:

现在的使用体验?

我现在根本不需要写任何额外的代码,只需要手写好原有的数据库结构体,就能自动生成最终的成品,不需要额外写任何代码

项目地址:https://github.com/hinego/gen.git

PS:传入结构体时不能传指针,然后关联关系时多对多目前有问题(等我用到时会去修复)

下面会提供目前我使用的自动生成的代码

hinego commented 1 year ago

这是我自动生成的代码,我只需要写好model,执行这个文件就能自动生成好

package main

import (
    "context"
    "errors"
    "github.com/gogf/gf/v2/encoding/gjson"
    "github.com/gogf/gf/v2/encoding/gyaml"
    "github.com/gogf/gf/v2/os/gcmd"
    "github.com/gogf/gf/v2/os/gctx"
    "github.com/gogf/gf/v2/os/gfile"
    "github.com/gogf/gf/v2/os/gproc"
    "github.com/gogf/gf/v2/text/gstr"
    "github.com/hinego/gen"
    "github.com/hinego/types"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "gorm.io/gorm/schema"
    "log"
    "os"
    "strings"
    "time"
)

var (
    g1        *gen.Generator
    genConfig *gen.CmdParams
    gdb       *gorm.DB
    newLogger = logger.New(log.New(os.Stdout, "\r\n", log.Ltime|log.Lshortfile), logger.Config{
        SlowThreshold:             time.Second,   // 慢 SQL 阈值
        LogLevel:                  logger.Silent, // 日志级别
        IgnoreRecordNotFoundError: true,          // 忽略ErrRecordNotFound(记录未找到)错误
        Colorful:                  true,          // 禁用彩色打印
    })
    cfg = &Config{
        Reset:    false,
        Database: "host=192.168.32.130 user=postgres password=postgres dbname=status port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        Type:     "postgres",
        Path:     "./app/dao",
    }
)

type Config struct {
    Reset    bool   `yaml:"reset"`
    Database string `yaml:"database"`
    Type     string `yaml:"type"`
    Path     string `yaml:"path"`
}

func load() {
    if !gfile.Exists("database.yml") {
        if encode, err := gyaml.Encode(cfg); err != nil {
            log.Fatalf("encode fail: %v", err)
        } else {
            if err = gfile.PutBytes("database.yml", encode); err != nil {
                log.Fatalf("write fail: %v", err)
            }
        }
        log.Fatalln("create database.yml success")
    } else {
        if decode := gfile.GetBytes("database.yml"); decode == nil {
            log.Fatalf("read fail")
        } else {
            if err := gyaml.DecodeTo(decode, cfg); err != nil {
                log.Fatalf("decode fail: %v", err)
            }
        }
    }
}

func prepare(ctx context.Context, parser *gcmd.Parser) {
    _ = os.Remove("/etc/gen.db")
    log.SetFlags(log.Ltime | log.Lshortfile)
    genConfig = gen.ArgParse()
    if genConfig == nil {
        log.Fatalf("parse genConfig fail")
    }
    schema.RegisterSerializer("auto", types.AutoSerializer{})
    genConfig.DSN = cfg.Database
    genConfig.DB = cfg.Type
    genConfig.OutPath = cfg.Path
    if db, err := gen.Connect(gen.DBType(genConfig.DB), genConfig.DSN); err != nil {
        log.Fatalf("connect db server fail: %v", err)
    } else {
        g1 = gen.NewGenerator(gen.Config{
            Mode:              gen.WithDefaultQuery | gen.WithoutContext,
            OutPath:           genConfig.OutPath,
            OutFile:           genConfig.OutFile,
            ModelPkgPath:      genConfig.ModelPkgName,
            WithUnitTest:      genConfig.WithUnitTest,
            FieldNullable:     false,
            FieldWithIndexTag: true,
            FieldWithTypeTag:  true,
            FieldSignable:     genConfig.FieldSignable,
        })
        db.Logger = newLogger
        db.Logger.Info(ctx, "working directory: %s", gfile.Pwd())
        if cfg.Reset {
            if tables, err := db.Migrator().GetTables(); err != nil {
                return
            } else {
                log.Println(gjson.MustEncodeString(tables))
                for _, table := range tables {
                    if err := db.Migrator().DropTable(table); err != nil {
                        log.Println("删除失败", err)
                    }
                }
            }
            if tables, err := db.Migrator().GetTables(); err != nil {
                return
            } else {
                log.Println("清空后", len(tables))
            }
        }
        g1.UseDB(db)
        gdb = db
    }
}

var gn = &gcmd.Command{
    Name:  "main",
    Usage: "main",
    Brief: "[开发专用]生成dao",
    Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
        if !gfile.Exists("go.mod") {
            return errors.New("此为开发使用命令")
        }
        load()
        gfile.Remove("app/dao")
        gfile.Remove("app/model")
        prepare(ctx, parser)
        g1.LinkModel(Md...)
        if !genConfig.OnlyModel {
            g1.ApplyBasic(g1.GenerateAllTable()...)
        }
        g1.Execute()
        file, err := gfile.ScanDirFile("app/model", "*", true)
        if err != nil {
            log.Println(err)
            return nil
        }
        for _, v := range file {
            data := gfile.GetContents(v)
            data = gstr.Replace(data, `"github.com/gogf/gf/v2`, `"github.com/gogf/gf`)
            data = gstr.Replace(data, `"github.com/gogf/gf`, `"github.com/gogf/gf/v2`)
            gfile.PutContents(v, data)
        }
        print(gproc.MustShellExec(ctx, "git add ./app/dao"))
        print(gproc.MustShellExec(ctx, "git add ./app/model"))
        db, err := gdb.DB()
        if err == nil {
            db.Close()
            os.Remove("/etc/gen.db")
        }
        return nil
    },
}

func print(result string) {
    res := strings.Split(result, "\n")
    for _, v := range res {
        if !strings.Contains(v, "CRLF") && v != "" && !strings.Contains(v, "working directory") {
            log.Println(v)
        }
    }
}
func main() {
    gn.Run(gctx.New())
}
hinego commented 1 year ago
        g1.LinkModel(Md...)
这个Md 其实就是类似于
var Md = []any{table.Token{}}
这样的,保存了当前全部需要生成结构体

这个Md也是自动生成的(我前置的一个脚本)
会自动读取table文件夹下全部除types.go结尾的go文件
提取出其中全部结构体名称,自动写到md里面去

这就实现了我只需要管table里面的结构体定义,不需要写任何其他代码
tr1v3r commented 1 year ago

这个主要是针对已有结构体转查询结构体做的优化吧,这个我们确实没有花很多精力去做,因为很多人都是先写数据库再同步生成结构体的。你添加的功能确实很实用,欢迎把你的修改整理成PR提交

hinego commented 1 year ago

这个主要是针对已有结构体转查询结构体做的优化吧,这个我们确实没有花很多精力去做,因为很多人都是先写数据库再同步生成结构体的。你添加的功能确实很实用,欢迎把你的修改整理成PR提交

有空会尝试的,你们如果有空的话,也可以参考我写的,改一下加进去

csh0101 commented 12 months ago

你这么做为什么不直接用ent.io..