• 为hade增加model自动生成功能


    大家好,我是轩脉刃。

    我们写业务的时候和db接触是少不了的,那么要生成model也是少不了的,如何自动生成model,想着要给hade框架增加个这样的命令。

    看了下网上的几个开源项目,最终聚焦在两个项目中:

    https://github.com/go-gorm/gen

    https://github.com/xxjwxc/gormt

    gormt的gui是非常强大的,看文档都支持终端gui和windows的gui。但是gormt是一个工具,无法在另外一个项目中引入。

    但是gen项目是gorm官方推出的,有jinzhu作者的参与。

    所以我尝试选择gen项目来。

    gen

    gen其实不只是工具,它更像一个全新的orm封装。gen项目生成出来的文件有其实有两个部分,一个是model,就是db的表和对应的model,以xxx.gen.go 命名。而另一个部分是每个model对应一套gen函数,这套gen函数基本上是对orm的二次封装了。

    image-20220211091540150

    当然这套函数是基于gorm来封装的,不过你可以完全脱离gorm来使用这套函数。

    生成的方法示例如下:

    g := gen.NewGenerator(gen.Config{
    			OutPath:      "/Users/yejianfeng/Documents/workspace/gohade/hade/app/dal",
    			ModelPkgPath: "/Users/yejianfeng/Documents/workspace/gohade/hade/app/dal/model",
    			WithUnitTest: true,
    
    			FieldNullable:     false,
    			FieldCoverable:    true,
    			FieldWithIndexTag: false,
    			FieldWithTypeTag:  false,
    
    			Mode: gen.WithDefaultQuery,
    		})
    		gormService := container.MustMake(contract.ORMKey).(contract.ORMService)
    		db, err := gormService.GetDB(orm.WithConfigPath("database.default"))
    		if err != nil {
    			return err
    		}
    
    		g.UseDB(db)
    		g.WithDataTypeMap(dataMap)
    		//g.WithJSONTagNameStrategy(func(c string) string { return "-" })
    
    		//g.ApplyBasic(model.Customer{})
    		//g.ApplyBasic(g.GenerateAllTable()...)
    		//g.GenerateModel("users")
    		//g.GenerateModel("answers")
    		//g.GenerateAllTable()
    		g.ApplyBasic(g.GenerateAllTable()...)
    		g.Execute()
    

    使用起来像是这样:

    u := query.Use(db).User
    
    // Get first matched record
    user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).First()
    // SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;
    
    // Get all matched records
    users, err := u.WithContext(ctx).Where(u.Name.Neq("modi")).Find()
    // SELECT * FROM users WHERE name <> 'modi';
    
    // IN
    users, err := u.WithContext(ctx).Where(u.Name.In("modi", "zhangqiang")).Find()
    // SELECT * FROM users WHERE name IN ('modi','zhangqiang');
    
    // LIKE
    users, err := u.WithContext(ctx).Where(u.Name.Like("%modi%")).Find()
    // SELECT * FROM users WHERE name LIKE '%modi%';
    

    最终生成的文件如下:

    image-20220211092157272

    gen有一些高级的功能:

    • 自定义模型函数,且提供了通过sql语句的注释自实现函数的方法
    • 提供了单元测试框架,你可以自己定义TestCase,来实现对某个自实现函数的单测
    • 智能字段查询,在select语句的时候,可以根据输出对象自动生成select的field

    其实gen更像是另一个orm框架了,和facebook的ent类似,为每个model生成一套orm方法。gen是字节跳动的无恒实验室开发的产品,据说字节内部正在将gorm切换到gen。gen的主打是安全,意思是,如果你的orm是完全使用gen来生成的,通过注释sql而不是自己裸写sql来生成,它更能保证安全性。(当然,因为所有的实现代码都是gen来自动生成的)。

    我目前的认知还是觉得这套东西太重了一些,整个熟悉下来无异于需要了解另外一个orm框架的语法了。在使用gorm和gen上并没有什么太大的区别。

    // 插入一条数据
    	email := "foo@gmail.com"
    	name := "foo"
    	user := &model.User{
    		ID:        0,
    		Username:  name,
    		Password:  "",
    		Email:     email,
    		CreatedAt: time.Time{},
    	}
    	dal.SetDefault(db)
    	err := dal.User.WithContext(c).Create(user)
    	if err != nil {
    		c.AbortWithError(50001, err)
    		return
    	}
    
    // 插入一条数据
    	email := "foo@gmail.com"
    	name := "foo"
    	age := uint8(25)
    	birthday := time.Date(2001, 1, 1, 1, 1, 1, 1, time.Local)
    	user := &User{
    		Name:         name,
    		Email:        &email,
    		Age:          age,
    		Birthday:     &birthday,
    		MemberNumber: sql.NullString{},
    		ActivatedAt:  sql.NullTime{},
    		CreatedAt:    time.Now(),
    		UpdatedAt:    time.Now(),
    	}
    	err = db.Create(user).Error
    	logger.Info(c, "insert user", map[string]interface{}{
    		"id":  user.ID,
    		"err": err,
    	})
    

    而且如果要写出官网给出的这么复杂的语句:

    p := query.Use(db).Pizza
    
    pizzas, err := p.WithContext(ctx).Where(
        p.WithContext(ctx).Where(p.Pizza.Eq("pepperoni")).
            Where(p.WithContext(ctx).Where(p.Size.Eq("small")).Or(p.Size.Eq("medium"))),
    ).Or(
        p.WithContext(ctx).Where(p.Pizza.Eq("hawaiian")).Where(p.Size.Eq("xlarge")),
    ).Find()
    
    // SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
    
    

    我相信对新手来说真是一个不大容易的事情。

    所以目前我还只倾向于使用gen的生成model的部分。

    自动生成model命令设计

    首先设计一下这个命令的产品形态。

    ./hade model gen --output=document/app/model/ --database=database.default
    

    在命令行中的参数:

    output: 必选,表示输出的路径
    database: 可选,默认使用database.default
    

    如果没有设置db,或者output没有设置,直接返回错误。

    第一步是一个交互命令行工具,首先展示要生成的表列表选择:

    请选择要生成模型的表格:
    [] *
    [] users
    [] answers
    [] questions
    

    第二步确认要生成的目录和文件,以及覆盖提示:

    继续下列操作会在目录(xxxx)生成下列文件:
    user.gen.go(覆盖)
    answer.gen.go(新文件)
    
    请确认是否继续?(Y/N)
    

    第三步选择后是一个生成模型的选项:

    请选择模型规则:
    [] FieldNullable, 对于数据库的可null字段设置指针
    [] FieldCoverable, 根据数据库的Default设置字段的默认值
    [] FieldWithIndexTag, 根据数据库的索引关系设置索引标签
    [] FieldWithTypeTag, 生成类型字段
    

    最后一步就是生成模型文件了。

    自动生成model命令实现

    了解了gen和命令的设计,实现就很简单了。

    大概就分几步吧:

    • 获取数据库中的所有表
    • 让用户多选要生成model的表格
      • 和现有的目录中的文件进行比对
    • 让用户多选要生成的model的选项,比如是否可null,是否有default设置等
    • 使用gen生成模型文件

    具体代码在 https://github.com/gohade/hade/blob/feature/model-gen/framework/command/model/model.go

    其中代码实现方便稍微有几个地方要注意下:

    如何查询一个数据库中的所有表

    使用gorm很方便就实现了

    dbTables, err := db.Migrator().GetTables()
    

    当用户选择了要生成的表格,要和硬盘中已有的文件进行比对,如何操作

    这里其实涉及到两个集合的交集和差集

    我发现collection库之前已经实现了差集,但是没有实现交集。

    这里我补充实现了colleciton的交集,Intersect,并且将collection库升级到1.4.1

    // Intersect 比较两个数组,获取两个数组交集,仅对基础元素生效
    Intersect (arr ICollection) ICollection
    

    gen 库如何只生成model不生成gen文件?

    g.UseDB(db)
    
    for _, table := range genTables {
    	g.GenerateModel(table)
    }
    g.Execute()
    

    model命令验证

    验证一下要model/gen命令

    第一步,使用 ./hade model gen --output=app/model

    image-20220215091522181

    选择其中的两个表,answers和questions,提示目录文件

    image-20220215091541908

    下一步确认y继续

    image-20220215091643242

    最后生成模型成功

    image-20220215091659104

    查看文件,确实生成了model

    image-20220215091735434

    功能完结。

  • 相关阅读:
    【Qt图形视图框架】QGraphicsScene分析
    【servelt原理_13_状态管理】
    POI导出Excel设置背景颜色不生效
    如何寻找有价值的行业报告
    sublime 文件编辑器使用快捷键
    时序数据库 | InfluxDB - 行协议
    在企业级开发过程中我发现有位同事用select * from where 条件 for update
    操作系统两大创始人反目,这个排名第九的 Linux 发行版 OS 何去何从?
    秒杀的时候怎么使用Redis?
    淘宝客APP源码/社交电商自营商城源码/前端基于Uniapp开发
  • 原文地址:https://www.cnblogs.com/yjf512/p/15895168.html