GORM-GEN初识与入门,和SQL注入说拜拜

idersec · 2022-01-10 15:57:03 · 5776 次点击 · 预计阅读时间 7 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2022-01-10 15:57:03 的文章,其中的信息可能已经有所发展或是发生改变。

背景

数据库操作是大多数程序员必不可少的工作, GORM 作为一个拥有 25k star 的项目已经是 Go 语言操作关系型数据库的首选。

  • 由于 GORM 中提供了很多 interface{} 形式的参数,这让程序员很容易误用,导致线上项目存在 SQL 注入的风险。

  • 在操作数据库时候,因为没有对应的结构体可以绑定,最后只能默默的拼接出一条SQL去执行。

  • 复杂的数据库表查询场景时,开发者需逐条手写数据表中的列与对应结构体的成员变量,逐条核对字段类型。遇到字段类型新增和变更,更改地方一大堆。

你和你的团队是否也为此事苦恼过?

由字节跳动无恒实验室与GORM作者(https://github.com/jinzhu )联合研发的开源工具GEN你值得一试!

什么是GEN

GEN是一个基于GORM的安全ORM框架,其主要通过代码生成方式实现GORM代码封装。旨在安全上避免业务代码出现SQL注入,同时给研发带来最佳用户体验。

GEN来告诉你,什么叫最佳用户体验:

⚡️自动同步库表,省去繁琐复制

🔗 代码一键生成,专注业务逻辑

🐞 字段类型安全,执行SQL也安全

😉 查询优雅返回,完美兼容GORM

GEN提供了自动同步数据表结构体到GORM模型,使用非常简单,即使数据库字段信息改变,可以一键同步,数据库查询相关代码可以一键生成,CRUD只需要调用对应的方法,开发体验飞起。GEN采用了类型安全限制,所有参数都做了安全限制,完全不用担心存在注入;最重要的是自定义SQL只需要通过模板注释到interface的方法上,自动帮助你生成安全的代码,是的,自定义SQL也不会出现SQL注入问题,而且工具完美兼容GORM。简直哇塞哇塞哇哇塞!

GORM和GEN查询对比案例

//GORM 需要先定义类型
var user model.User
err:=db.Where("id=?",5).Take(&user).Error

//GEN 可以直接查询,返回对应类型
user,err:= u.Where(u.ID.Eq(5)).Take()

如何使用GEN

1. 下载

go get gorm.io/gen

2. 生成

更详细的配置示例可以参照:最佳实践DEMO(https://github.com/go-gorm/gen/tree/master/examples)

执行以下方法后即可在指定目录生成对应代码:

package main

import "gorm.io/gen"

// generate code
func main() {
    // specify the output directory (default: "./query")
    // ### if you want to query without context constrain, set mode gen.WithoutContext ###
    g := gen.NewGenerator(gen.Config{
        OutPath: "../dal/query",
        /* Mode: gen.WithoutContext|gen.WithDefaultQuery*/
        //if you want the nullable field generation property to be pointer type, set FieldNullable true
        /* FieldNullable: true,*/
        //if you want to generate index tags from database, set FieldWithIndexTag true
        /* FieldWithIndexTag: true,*/
        //if you want to generate type tags from database, set FieldWithTypeTag true
        /* FieldWithTypeTag: true,*/
        //if you need unit tests for query code, set WithUnitTest true
        /* WithUnitTest: true, */
    })

    // reuse the database connection in Project or create a connection here
    // if you want to use GenerateModel/GenerateModelAs, UseDB is necessray or it will panic
    // db, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
    g.UseDB(db)

    // apply basic crud api on structs or table models which is specified by table name with function
    // GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
    g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))

    // apply diy interfaces on structs or table models
    g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))

    // execute the action of code generation
    g.Execute()
}

3. 基础查询

执行生成代码后,GEN会帮助生成基础的查询方法,并且绑定到结构体上,可以直接调用函数查询获取查询结果,不需要提前定义变量,参数和结构体字段类型绑定,防止研发过程中误用。

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`name`,`age`) VALUES ("modi", 18)

user, err := u.WithContext(ctx).Where(u.Name.Eq("iDer"),u.Age.Gte(18)).First()
// SELECT * FROM users WHERE name = "iDer" and age>=18;

_, err := u.WithContext(ctx).Where(u.ID.Eq(12)).Update(u.Name, "jinzhu")
// UPDATE users SET name="jinzhu", updated_at='2013-11-17 21:34:10' WHERE id=12;

e.WithContext(ctx).Where(u.ID.Eq(10)).Delete()
// DELETE from users where id = 10;

orders, err := o.WithContext(ctx).Where(u.Columns(o.Amount).Gt(o.Select(u.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

GEN满足了基本上所有的日常使用的查询方法,包括事务、关联关系等高级用法,更多案例请参考 https://github.com/go-gorm/gen#readme

4. 自定义SQL查询

自定SQL的安全性是所有ORM最难解决的问题,GEN使用模板注释的方法完美解决了这个问题,只需要将SQL注释到interface的方法上。SQL支持简单的where查询和完整SQL查询,条件用Where()语法包住。Raw SQL 用sql()包住,也可省略直接写。

占位符

gen.T 用于返回数据的结构体,会根据生成结构体或者数据库表结构自动生成 gen.M 表示map[string]interface{},用于返回数据 gen.RowsAffected 用于执行SQL进行更新或删除时候,用于返回影响行数 @@table 查询的表名,如果没有传参,会根据结构体或者表名自动生成 @``@`` 当表名或者字段名可控时候,用@@占位,name为可变参数名,需要函数传入。 @ 当数据可控时候,用@占位,name为可变参数名,需要函数传入

子句

目前支持 if 、for 、where 、set子句,子句需要用{{}}括起来,并且需要用{{end}} 结束子句。where和set子句会帮助做连接词补全和开头连接词删除。各个子句支持嵌套使用。

type Method interface {

    // Where("name=@name and age=@age")
    SimpleFindByNameAndAge(name string, age int) (gen.T, error)

    // select * from users where id=@id
    FindUserToMap(id int) (gen.M, error)

    // sql(insert into @@table (name,age) values (@name,@age) )
    InsertValue(age int, name string) error

    // select name from @@table where id=@id
    FindNameById(id int) string

    // select * from @@table
    //  {{where}}
    //      id>0
    //      {{if cond}}id=@id {{end}}
    //      {{if key!="" && value != ""}} or @@key=@value{{end}}
    //  {{end}}
    FindByIDOrCustom(cond bool, id int, key, value string) ([]gen.T, error)

    // update @@table
    //  {{set}}
    //      update_time=now()
    //      {{if name != ""}}
    //          name=@name
    //      {{end}}
    //  {{end}}
    //  {{where}}
    //      id=@id
    //  {{end}}
    UpdateName(name string, id int) (gen.RowsAffected,error)

    // select * from @@table
    //  {{where}}
    //      {{for _,user:=range users}}
    //          {{if user.Age >18}
    //              OR name=@user.Name 
    //         {{end}}
    //      {{end}}
    //  {{end}}
    FindByOrList(users []gen.T) ([]gen.T, error)
}

GEN会自动生成安全的实现代码,并且和结构体绑定。使用时候直接调用对应的函数即可。

user,err := u.SimpleFindByNameAndAge("zhangqiang",18)

resultMap,err:= u.FindUserToMap(2)

name := u.FindNameById(5)

users,err := u.FindByIDOrCustom(true, 10, "name", "modi")

rows,err := UpdateName("jinzhu", 12)

user,err:= FindByOrList(users)

5. 最佳实践目录推荐

demo
├── cmd
│   └── generate
│       └── generate.go # 包含main函数,执行其即可完成生成代码步骤
├── dal
│   ├── dal.go # 实现具体的数据库连接等操作
│   └── model
│   │   ├── method.go # 指定所有自定义查询方法
│   │   └── model.go  # 描述与数据库表对应的数据结构(体)
│   └── query  # 生成的代码存放目录, 在执行代码生成操作后自动创建
│       └── gen.go # 生成的通用查询代码
│       └── tablename.gen.go # 生成的单个表字段和相关的查询代码
├── biz
│   └── query.go # 实现业务逻辑,调用生成的代码查询数据库
├── config
│   └── config.go # 存储相关的数据库DSN
├── generate.sh # 调用generate中main函数生成代码的脚本(推荐使用)
├── go.mod
├── go.sum
└── main.go

GEN项目地址

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


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5776 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传