Go框架三件套 | 青训营笔记

56 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

Gorm

Gorm是一个已经迭代了10年+的功能强大的ORM框架

连接数据库

	db, err := gorm.Open("mysql", "root:123456@(localhost)/test?charset=utf8mb4&parseTime=True&loc=Local")
	if err != nil {
		panic(err)
	}
	defer db.Close()

GORM Model定义

GORM内置了一个gorm.Model结构体。gorm.Model是一个包含了ID,CreatedAt,Updated,DeletedAt四个字段的Golang结构体

// gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

使用结构体声明模型时,可以添加标记(tags)

type User struct {
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // 设置字段大小为255
  MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
  Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
  Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
  IgnoreMe     int     `gorm:"-"` // 忽略本字段
}

在这里插入图片描述 在这里插入图片描述

一些约定

GORM 默认会使用名为ID的字段作为表的主键。 如果要指定其他字段为主键则可使用标记primary_key

// 使用`AnimalID`作为主键
type Animal struct {
  AnimalID int64 `gorm:"primary_key"`
  Name     string
  Age      int64
}

表名默认就是结构体名称的复数,SysUser对应的表明为sys_users
自定义表名

// 将 User 的表名设置为 `users`
func (User) TableName() string {
  return "users"
}

// 使用User结构体创建名为`admin`的表
//同时有两种方法可以传递表名Table和Model
db.Table("admin").CreateTable(&User{})
var user User
db.Model(&user).Update("name", "zzn")

禁用表名为负数

// 禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)

列名由下划线分割
也可以将属性映射到自定义列名

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}

时间戳跟踪
如果模型有 CreatedAt字段,该字段的值将会是初次创建记录的时间。 如果模型有UpdatedAt字段,该字段的值将会是每次更新记录的时间。 如果模型有DeletedAt字段,调用Delete删除该记录时,将会设置DeletedAt字段为当前时间,而不是直接将记录从数据库中删除。

	var u1 = SysUser{ Name: "zzn", Age: 18, Birthday: time.Now()}
	//自动生成Id主键,插入后可通过user.Id获取 
	//result.Error         返回 error
	//result.RowsAffected   返回插入记录的条数
	result := db.Create(&u1)

只插入部分指定字段

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

忽略部分字段

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

批量插入
传入切片

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

关联创建

type User struct {
  gorm.Model
  Name       string
  CreditCard CreditCard
}
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

也可以通过 Select、 Omit 跳过关联保存

db.Omit("CreditCard").Create(&user)
// 跳过所有关联
db.Omit(clause.Associations).Create(&user)

默认值
标签 default 为字段定义默认值 插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段 对于声明了默认值的字段,像 0、''、false 等零值是不会保存到数据库。

单条数据
GORM 提供了First、Take、Last方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回ErrRecordNotFound 错误
想避免ErrRecordNotFound错误,你可以使用Find

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;


result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

根据主键查询

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是string,如uuid

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

查所有对象

//users为切片
result := db.Find(&users)
// SELECT * FROM users;

条件查询

db.Where("name = ?", "zzn").First(&user)
// SELECT * FROM users WHERE name = 'zzn' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "zzn").Find(&users)
// SELECT * FROM users WHERE name <> 'zzn';

通过struct,map,切片进行查询

// Struct
db.Where(&User{Name: "zzn", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "zzn" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "zzn", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "zzn" AND age = 20;

// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

提示当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值 i为0,'',false或者其他零值时,将不会被用于构建查询条件

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";

内联条件
将查询条件写到First和Find函数中,取代Where

// Get by primary key if it were a non-integer type
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;

选择特定字段

 db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

排序

db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

保存所有字段

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

更新单个列
当使用 Model 方法,并且值中有主键值时,主键将会被用于构建条件

// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列
Updates 方法支持 struct 和 map[string]interface{} 参数
根据 struct 更新属性,只会更新非零值的字段

// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

更新选定字段
如果您想要在更新时选定、忽略某些字段,您可以使用 Select、Omit

// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

硬删除

// email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

软删除
如果模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力

db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

如果不想引入 gorm.Model,您也可以这样启用软删除特性

type User struct {
  Deleted gorm.DeletedAt
}

事务

在这里插入图片描述 在这里插入图片描述

Hook

GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法。 如果任何Hook返回错误,GORM将停止后续的操作并回滚事务。

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()

    if u.Role == "admin" {
        return errors.New("invalid role")
    }
    return
}

Kitex

Kitex是字节内部的Golang微服务RPC框架,具有高性能,强可拓展的主要特点,支持多协议并且拥有丰富的额开源拓展

使用IDL定义服务和接口

如果我们要进行RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的。这时候,就需要通过IDL来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。
接口描述语言(Interface definition language,IDL) 是一种语言的通用术语,它允许用一种语言编写的程序或对象与用未知语言编写的另一个程序进行通信。 我们可以使用 IDL 来支持 RPC 的信息传输定义。Kitex 默认支持 thriftproto3 两种 IDL

Hertz

Hertz是字节内部的HTTP框架,参考了其他开源框架的优势,结合字节跳动内部的需求,具有高易用性,高性能,高扩展性特点

基本使用

服务监听8080端口并注册一个GET方法的路由函数

func main() {    
    h := server.Default(server.WithHostPoerts("127.0.0.1:8080"))
    h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { 
        ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
    })     
    h.Spin() 
}

路由组

h := server.Default(server.WithHostPorts("127.0.0.1:8080")) 
v1 := h.Group("/v1") 
v1.GET("/get", func(ctx context.Context, c *app.RequestContext) { 
    c.String(consts.StatusOK, "get") 
})

将请求参数绑定到结构体中
Hertz提供了Bind、Validate、BindAndValidate 函数用于进行参数绑定和校验 image.png 中间件 Hertz 服务端中间件是 HTTP 请求-响应周期中的一个函数,提供了一种方便的机制来检查和过滤进入应用程序的 HTTP 请求 中间件可以在请求更深入地传递到业务逻辑之前或之后执行

func MyMiddleware() app.HandlerFunc {  
    return func(ctx context.Context, c *app.RequestContext) {    
        // pre-handle    
        c.Next(ctx) // 中间件调用链下一个   
        // post-handle    
    } 
}  
func main() {    
    h := server.Default(server.WithHostPort("127.0.0.1:8080"))    
    h.Use(MyMiddleware())    
    h.Get("/middleware",func(ctx context.Context, c *app.RequestContext) {
        c.String(consts.StatusOK, "Hello hertz!")   
    })    
    h.Spin() 
}

可使用 Abort()AbortWithMsg(msg string, statusCode int)AbortWithStatus(code int) 终止后续调用

Hertz Client

Hertz提供了HTTP Client用于帮助用户发送HTTP请求

参考文献

www.cloudwego.io/zh/docs/kit…
gorm.io/zh_CN/docs/…
blog.csdn.net/wangsofa/ar…