4

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

 2 years ago
source link: https://studygolang.com/articles/35400
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

idersec · 大约9小时之前 · 60 次点击 · 预计阅读时间 7 分钟 · 大约8小时之前 开始浏览    

数据库操作是大多数程序员必不可少的工作, 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

go get gorm.io/gen

更详细的配置示例可以参照:最佳实践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 [email protected] 
    //         {{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


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

280

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK