欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go框架三件套Gorm?Kitex?Hertz基本用法與常見API講解

 更新時間:2023年02月05日 14:03:14   作者:啵啵腸  
這篇文章主要為大家介紹了Go框架三件套Gorm?Kitex?Hertz的基本用法與常見API講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>

三件套介紹

Gorm、Kitex、Hertz的基本用法與常見的API講解

Gorm

gorm是Golang語言中一個已經(jīng)迭代數(shù)十年且功能強大、性能極好的ORM框架

ORM:Object Relational Mapping(對象關(guān)系映射),其主要作用是在編程中,把面向?qū)ο?/strong>的概念跟數(shù)據(jù)庫中表的概念對應起來,

簡單來說,在golang中,自定義的一個結(jié)構(gòu)體對應著一張表,結(jié)構(gòu)體的實例則對應著表中的一條記錄。

Kitex

Kitex是字節(jié)內(nèi)部Golang微服務RPC框架 具有高性能、強可擴展的主要特點 支持多協(xié)議并且擁有豐富的開源擴展

Hertz

Hertz是字節(jié)內(nèi)部的Http框架 參考了其他開源框架的優(yōu)勢 結(jié)合字節(jié)跳動內(nèi)部的需求 具有高可用、高性能、高擴展性的特點

三件套使用

Gorm

該部分筆記主要參考:gorm.io/zh_CN/docs

聲明模型

模型定義

模型是標準的 struct,由 Go 的基本數(shù)據(jù)類型、實現(xiàn)了 ScannerValuer 接口的自定義類型及其指針或別名組成

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

約定

GORM 傾向于約定優(yōu)于配置

默認情況下,GORM 使用 ID 作為主鍵,使用結(jié)構(gòu)體名的 蛇形復數(shù) 作為表名,字段名的 蛇形 作為列名,并使用 CreatedAt、UpdatedAt 字段追蹤創(chuàng)建、更新時間

gorm.Model

GORM 定義一個 gorm.Model 結(jié)構(gòu)體,其包括字段 IDCreatedAt、UpdatedAt、DeletedAt

// gorm.Model 的定義
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

還可以將它嵌入到結(jié)構(gòu)體中,以包含這幾個字段,例如:

type User struct {
  gorm.Model
  Name string
}
// 等效于
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

連接到數(shù)據(jù)庫

GORM 官方支持的數(shù)據(jù)庫類型有: MySQL, PostgreSQL, SQlite, SQL Server

MySQL

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
func main() {
  // 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

注意: 想要正確的處理 time.Time ,您需要帶上 parseTime 參數(shù), 要支持完整的 UTF-8 編碼,您需要將 charset=utf8 更改為 charset=utf8mb4

使用現(xiàn)有數(shù)據(jù)庫連接

GORM 允許通過一個現(xiàn)有的數(shù)據(jù)庫連接來初始化 *gorm.DB

import (
  "database/sql"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
sqlDB, err := sql.Open("mysql", "mydb_dsn")
gormDB, err := gorm.Open(mysql.New(mysql.Config{
  Conn: sqlDB,
}), &gorm.Config{})

CRUD接口

創(chuàng)建記錄
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&amp;user) // 通過數(shù)據(jù)的指針來創(chuàng)建
user.ID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 返回插入數(shù)據(jù)的主鍵
result.Error &nbsp; &nbsp; &nbsp; &nbsp;// 返回 error
result.RowsAffected // 返回插入記錄的條數(shù)

批量插入

將切片數(shù)據(jù)傳遞給 Create 方法,GORM 將生成一個單一的 SQL 語句來插入所有數(shù)據(jù),并回填主鍵的值,鉤子方法也會被調(diào)用。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&amp;users)
for _, user := range users {
 &nbsp;user.ID // 1,2,3
}

通過Map數(shù)據(jù)類型創(chuàng)建記錄

GORM 支持根據(jù) map[string]interface{}[]map[string]interface{}{} 創(chuàng)建記錄

DB.Model(&amp;User{}).Create(map[string]interface{}{
 &nbsp;"Name": "jinzhu", "Age": 18,
})
// 根據(jù) `[]map[string]interface{}{}` 批量插入
DB.Model(&amp;User{}).Create([]map[string]interface{}{
  {"Name": "jinzhu_1", "Age": 18},
  {"Name": "jinzhu_2", "Age": 20},
})

查詢

檢索單個對象

GORM 提供了 First、TakeLast 方法,以便從數(shù)據(jù)庫中檢索單個對象。當查詢數(shù)據(jù)庫時它添加了 LIMIT 1 條件,且沒有找到記錄時,它會返回 ErrRecordNotFound 錯誤

// 獲取第一條記錄(主鍵升序)
db.First(&amp;user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 獲取一條記錄,沒有指定排序字段
db.Take(&amp;user)
// SELECT * FROM users LIMIT 1;
// 獲取最后一條記錄(主鍵降序)
db.Last(&amp;user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&amp;user)
result.RowsAffected // 返回找到的記錄數(shù)
result.Error &nbsp; &nbsp; &nbsp; &nbsp;// returns error
// 檢查 ErrRecordNotFound 錯誤
errors.Is(result.Error, gorm.ErrRecordNotFound)

First、Last 方法會根據(jù)主鍵查找到第一個、最后一個記錄, 它僅在通過 struct 或提供 model 值進行查詢時才起作用。如果 model 類型沒有定義主鍵,則按第一個字段排序

var user User
// 可以
DB.First(&amp;user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 可以
result := map[string]interface{}{}
DB.Model(&amp;User{}).First(&amp;result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 不行
result := map[string]interface{}{}
DB.Table("users").First(&amp;result)
// 但可以配合 Take 使用
result := map[string]interface{}{}
DB.Table("users").Take(&amp;result)
// 根據(jù)第一個字段排序
type Language struct {
 &nbsp;Code string
 &nbsp;Name string
}
DB.First(&amp;Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

檢索對象

// 獲取全部記錄
result := db.Find(&amp;users)
// SELECT * FROM users;
result.RowsAffected // 返回找到的記錄數(shù),相當于 `len(users)`
result.Error &nbsp; &nbsp; &nbsp; &nbsp;// returns error

條件查詢

String條件

// 獲取第一條匹配的記錄
db.Where("name = ?", "jinzhu").First(&amp;user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// 獲取全部匹配的記錄
db.Where("name &lt;&gt; ?", "jinzhu").Find(&amp;users)
// SELECT * FROM users WHERE name &lt;&gt; 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&amp;users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&amp;users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age &gt;= ?", "jinzhu", "22").Find(&amp;users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age &gt;= 22;
// Time
db.Where("updated_at &gt; ?", lastWeek).Find(&amp;users)
// SELECT * FROM users WHERE updated_at &gt; '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&amp;users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

Struct & Map 條件

// Struct
db.Where(&amp;User{Name: "jinzhu", Age: 20}).First(&amp;user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&amp;users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// 主鍵切片條件
db.Where([]int64{20, 21, 22}).Find(&amp;users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

注意 當使用結(jié)構(gòu)作為條件查詢時,GORM 只會查詢非零值字段。這意味著如果您的字段值為 0、''、false 或其他 零值,該字段不會被用于構(gòu)建查詢條件

Not 條件

構(gòu)建NOT條件,用法與 Where 類似

db.Not("name = ?", "jinzhu").First(&amp;user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&amp;users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&amp;user)
// SELECT * FROM users WHERE name &lt;&gt; "jinzhu" AND age &lt;&gt; 18 ORDER BY id LIMIT 1;
// 不在主鍵切片中的記錄
db.Not([]int64{1,2,3}).First(&amp;user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

Or條件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&amp;users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&amp;users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&amp;users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

選擇特定字段

選擇想從數(shù)據(jù)庫中檢索的字段,默認情況下會選擇全部字段

db.Select("name", "age").Find(&amp;users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&amp;users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

Order查詢

指定從數(shù)據(jù)庫檢索記錄時的排序方式

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

Limit & Offset查詢

Limit 指定獲取記錄的最大數(shù)量 Offset 指定在開始返回記錄之前要跳過的記錄數(shù)量

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// 通過 -1 消除 Limit 條件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// 通過 -1 消除 Offset 條件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

Group & Having查詢

type result struct {
  Date  time.Time
  Total int
}
db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name`
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
  ...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
  ...
}
type Result struct {
  Date  time.Time
  Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)

Distinct查詢

從模型中選擇不相同的值

db.Distinct("name", "age").Order("name, age desc").Find(&results)

高級查詢

智能選擇字段

GORM 允許通過 Select 方法選擇特定的字段,如果您在應用程序中經(jīng)常使用此功能,你也可以定義一個較小的結(jié)構(gòu)體,以實現(xiàn)調(diào)用 API 時自動選擇特定的字段

type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  // 假設后面還有幾百個字段...
}
type APIUser struct {
  ID   uint
  Name string
}
// 查詢時會自動選擇 `id`, `name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10

子查詢

子查詢可以嵌套在查詢中,GORM 允許在使用 *gorm.DB 對象作為參數(shù)時生成子查詢

db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

From子查詢

GORM 允許您在 Table 方法中通過 FROM 子句使用子查詢

db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
subQuery1 := db.Model(&User{}).Select("name")
subQuery2 := db.Model(&Pet{}).Select("name")
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p

Group條件

使用 Group 條件可以更輕松的編寫復雜 SQL

db.Where(
    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
    db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{}).Statement
// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

更新

保存所有字段

Save 會保存所有的字段,即使字段是零值

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;

更新單個列

當使用 Update 更新單列時,需要有一些條件,否則將會引起錯誤 ErrMissingWhereClause 。當使用 Model 方法,并且值中有主鍵值時,主鍵將會被用于構(gòu)建條件

// 條件更新
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;
// 根據(jù)條件和 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 方法支持 structmap[string]interface{} 參數(shù)。當使用 struct 更新時,默認情況下,GORM 只會更新非零值的字段

// 根據(jù) `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;
// 根據(jù) `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})

批量更新

如果尚未通過 Model 指定記錄的主鍵,則 GORM 會執(zhí)行批量更新

// 根據(jù) struct 更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
// 根據(jù) map 更新
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

獲取更新的記錄數(shù)

// 通過 `RowsAffected` 得到更新的記錄數(shù)
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
result.RowsAffected // 更新的記錄數(shù)
result.Error        // 更新的錯誤

刪除

刪除一條記錄

刪除一條記錄時,刪除對象需要指定主鍵,否則會觸發(fā) 批量 Delete

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 帶額外條件的刪除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根據(jù)主鍵刪除

GORM 允許通過主鍵(可以是復合主鍵)和內(nèi)聯(lián)條件來刪除對象,它可以使用數(shù)字(如以下例子。也可以使用字符串——譯者注)

db.Delete(&User{}, 10)
// DELETE FROM users 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 會執(zhí)行批量刪除,它將刪除所有匹配的記錄

db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

返回刪除行的數(shù)據(jù)

// 返回所有列
var users []User
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
// 返回指定的列
DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

軟刪除

如果模型包含了一個 gorm.deletedat 字段(gorm.Model 已經(jīng)包含了該字段),它將自動獲得軟刪除的能力!

擁有軟刪除能力的模型調(diào)用 Delete 時,記錄不會從數(shù)據(jù)庫中被真正刪除。但 GORM 會將 DeletedAt 置為當前時間, 并且你不能再通過普通的查詢方法找到該記錄。

// user 的 ID 是 `111`
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;

查詢被軟刪除的記錄

可以使用 Unscoped 找到被軟刪除的記錄

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

事務

禁用默認事務

為了確保數(shù)據(jù)一致性,GORM 會在事務里執(zhí)行寫入操作(創(chuàng)建、更新、刪除)。如果沒有這方面的要求,可以在初始化時禁用它,這將獲得大約 30%+ 性能提升。

// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})
// 持續(xù)會話模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

事務開啟

要在事務中執(zhí)行一系列操作,一般流程如下

db.Transaction(func(tx *gorm.DB) error {
  // 在事務中執(zhí)行一些 db 操作(從這里開始,您應該使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何錯誤都會回滾事務
    return err
  }
  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
  }
  // 返回 nil 提交事務
  return nil
})

嵌套事務

GORM 支持嵌套事務,您可以回滾較大事務內(nèi)執(zhí)行的一部分操作

db.Transaction(func(tx *gorm.DB) error {
  tx.Create(&user1)
  tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
  })
  tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
  })
  return nil
})

手動事務

Gorm 支持直接調(diào)用事務控制方法(commit、rollback)

// 開始事務
tx := db.Begin()
// 在事務中執(zhí)行一些 db 操作(從這里開始,您應該使用 'tx' 而不是 'db')
tx.Create(...)
// ...
// 遇到錯誤時回滾事務
tx.Rollback()
// 否則,提交事務
tx.Commit()

Gorm性能提高

使用PrepareStmt緩存預編譯語句可以提高后續(xù)調(diào)用的速度,提高大約35%左右。

db , err := gorm.Open(mysql.Open("username:password@tcp(localhost:9910)/database?charset=utf8"),&gorm.Config{
                          PrepareStmt: true}

Kitex

安裝

Kitex目前對Windows的支持不完善,建議使用虛擬機或WSL2

安裝代碼生成工具

go install github.com/cloudwego/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest

使用

kitex 是 Kitex 框架提供的用于生成代碼的一個命令行工具。目前,kitex 支持 thrift 和 protobuf 的 IDL,并支持生成一個服務端項目的骨架。

編寫IDL

IDL是什么:IDL 全稱是 Interface Definition Language,接口定義語言

為什么使用IDL:要進行 RPC,就需要知道對方的接口是什么,需要傳什么參數(shù),同時也需要知道返回值是什么樣的,就好比兩個人之間交流,需要保證在說的是同一個語言、同一件事。 這時候,就需要通過 IDL 來約定雙方的協(xié)議,就像在寫代碼的時候需要調(diào)用某個函數(shù),我們需要知道函數(shù)簽名一樣。

首先我們需要編寫一個 IDL,這里以 thrift IDL 為例。

首先創(chuàng)建一個名為 echo.thrift 的 thrift IDL 文件。

然后在里面定義我們的服務

namespace go api
struct Request {
  1: string message
}
struct Response {
  1: string message
}
service Echo {
    Response echo(1: Request req)
}

生成echo服務代碼

有了 IDL 以后我們便可以通過 kitex 工具生成項目代碼了,執(zhí)行如下命令:

$ kitex -module example -service example echo.thrift

上述命令中,-module 表示生成的該項目的 go module 名,-service 表明我們要生成一個服務端項目,后面緊跟的 example 為該服務的名字。最后一個參數(shù)則為該服務的 IDL 文件。

生成后的項目結(jié)構(gòu)如下:

.
|-- build.sh
|-- echo.thrift
|-- handler.go
|-- kitex_gen
|   `-- api
|       |-- echo
|       |   |-- client.go
|       |   |-- echo.go
|       |   |-- invoker.go
|       |   `-- server.go
|       |-- echo.go
|       `-- k-echo.go
|-- main.go
`-- script
    |-- bootstrap.sh
    `-- settings.py

編寫echo服務邏輯

需要編寫的服務端邏輯都在 handler.go 這個文件中

package main
import (
  "context"
  "example/kitex_gen/api"
)
// EchoImpl implements the last service interface defined in the IDL.
type EchoImpl struct{}
// Echo implements the EchoImpl interface.
func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
  // TODO: Your code here...
  return
}

這里的 Echo 函數(shù)就對應了我們之前在 IDL 中定義的 echo 方法。

現(xiàn)在讓我們修改一下服務端邏輯,讓 Echo 服務起到作用。

func (s *EchoImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
  return &api.Response{Message: req.Message}, nil
}

編譯運行

kitex 工具已經(jīng)幫我們生成好了編譯和運行所需的腳本:

編譯:

$ sh build.sh

執(zhí)行上述命令后,會生成一個 output 目錄,里面含有我們的編譯產(chǎn)物。

運行:

$ sh output/bootstrap.sh

執(zhí)行上述命令后,Echo 服務就開始運行了。

編寫客戶端

有了服務端后,接下來就編寫一個客戶端用于調(diào)用剛剛運行起來的服務端。

首先,同樣的,先創(chuàng)建一個目錄用于存放我們的客戶端代碼:

$ mkdir client

進入目錄:

$ cd client

創(chuàng)建一個 main.go 文件,然后就開始編寫客戶端代碼了。

首先讓我們創(chuàng)建一個調(diào)用所需的 client

import "example/kitex_gen/api/echo"
import "github.com/cloudwego/kitex/client"
...
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
  log.Fatal(err)
}

上述代碼中,echo.NewClient 用于創(chuàng)建 client,其第一個參數(shù)為調(diào)用的 服務名(用于微服務中的服務發(fā)現(xiàn)),第二個參數(shù)為 options,用于傳入?yún)?shù), 此處的 client.WithHostPorts 用于指定服務端的地址。

發(fā)起調(diào)用

import "example/kitex_gen/api"
...
req := &api.Request{Message: "my request"}
resp, err := c.Echo(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
if err != nil {
  log.Fatal(err)
}
log.Println(resp)

上述代碼中,我們首先創(chuàng)建了一個請求 req , 然后通過 c.Echo 發(fā)起了調(diào)用。

其第一個參數(shù)為 context.Context,通過通常用其傳遞信息或者控制本次調(diào)用的一些行為,你可以在后續(xù)章節(jié)中找到如何使用它。

其第二個參數(shù)為本次調(diào)用的請求。

其第三個參數(shù)為本次調(diào)用的 options ,Kitex 提供了一種 callopt 機制,顧名思義——調(diào)用參數(shù) ,有別于創(chuàng)建 client 時傳入的參數(shù),這里傳入的參數(shù)僅對此次生效。 此處的 callopt.WithRPCTimeout 用于指定此次調(diào)用的超時(通常不需要指定,此處僅作演示之用)。

在編寫完一個簡單的客戶端后,我們終于可以發(fā)起調(diào)用了。

可以通過下述命令來完成這一步驟:

$ go run main.go

如果不出意外,可以看到類似如下輸出:

2023/01/26 07:23:35 Response({Message:my request})

至此成功編寫了一個 Kitex 的服務端和客戶端,并完成了一次調(diào)用!

Hertz

安裝命令行工具hz

首先,我們需要安裝使用demo所需要的命令行工具 hz:

  • 確保 GOPATH 環(huán)境變量已經(jīng)被正確地定義(例如 export GOPATH=~/go)并且將$GOPATH/bin添加到 PATH 環(huán)境變量之中(例如 export PATH=$GOPATH/bin:$PATH);請勿將 GOPATH 設置為當前用戶沒有讀寫權(quán)限的目錄
  • 安裝 hz:go install github.com/cloudwego/hertz/cmd/hz@latest

確定代碼放置位置

  • 若將代碼放置于$GOPATH/src下,需在$GOPATH/src下創(chuàng)建額外目錄,進入該目錄后再獲取代碼:
  $ mkdir -p $(go env GOPATH)/src/github.com/cloudwego
  $ cd $(go env GOPATH)/src/github.com/cloudwego
  • 若將代碼放置于 GOPATH 之外,可直接獲取

編寫示例代碼

  • 在當前目錄下創(chuàng)建 hertz_demo 文件夾,進入該目錄中
  • 創(chuàng)建 main.go 文件
  • main.go 文件中添加以下代碼
package main
import (
    "context"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
    "github.com/cloudwego/hertz/pkg/common/utils"
    "github.com/cloudwego/hertz/pkg/protocol/consts"
)
func main() {
    h := server.Default()
    h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
            ctx.JSON(consts.StatusOK, utils.H{"message": "pong"})
    })
    h.Spin()
}

生成go.mod 文件

$ go mod init hertz_demo

整理 & 拉取依賴

$ go mod tidy

運行示例代碼

完成以上操作后,我們可以直接編譯并啟動 Server

$ go build -o hertz_demo && ./hertz_demo

如果成功啟動,將看到以下信息

2023/01/26 07:23:35 Response({Message:my request})

接下來,我們可以對接口進行測試

$ curl http://127.0.0.1:8888/ping

如果不出意外,可以看到類似如下輸出

$ {"message":"pong"}

到現(xiàn)在,我們已經(jīng)成功啟動了 Hertz Server,并完成了一次調(diào)用!

Hertz路由優(yōu)先級

Hertz提供了參數(shù)路由和通配路由,路由的優(yōu)先級為:靜態(tài)路由>命名路由>通配路由

Hertz中間件

Hertz中間件的種類是多種多樣的,簡單分為兩大類:

  • 服務端中間件
  • 客戶端中間件

服務端中間件

中間件可以在請求更深入地傳遞到業(yè)務邏輯之前或之后執(zhí)行:

  • 中間件可以在請求到達業(yè)務邏輯之前執(zhí)行,比如執(zhí)行身份認證和權(quán)限認證,當中間件只有初始化(pre-handle)相關(guān)邏輯,且沒有和 real handler 在一個函數(shù)調(diào)用棧中的需求時,中間件中可以省略掉最后的.Next,如圖中的中間件 B。
  • 中間件也可以在執(zhí)行過業(yè)務邏輯之后執(zhí)行,比如記錄響應時間和從異常中恢復。如果在業(yè)務 handler 處理之后有其它處理邏輯( post-handle ),或?qū)瘮?shù)調(diào)用鏈(棧)有強需求,則必須顯式調(diào)用.Next,如圖中的中間件 C。

實現(xiàn)一個中間件

// 方式一
func MyMiddleware() app.HandlerFunc {
  return func(ctx context.Context, c *app.RequestContext) {
    // pre-handle
    // ...
    c.Next(ctx)
  }
}
// 方式二
func MyMiddleware() app.HandlerFunc {
  return func(ctx context.Context, c *app.RequestContext) {
    c.Next(ctx) // call the next middleware(handler)
    // post-handle
    // ...
  }
}

中間件會按定義的先后順序依次執(zhí)行,如果想快速終止中間件調(diào)用,可以使用以下方法,注意當前中間件仍將執(zhí)行。

  • Abort():終止后續(xù)調(diào)用
  • AbortWithMsg(msg string, statusCode int):終止后續(xù)調(diào)用,并設置 response中body,和狀態(tài)碼
  • AbortWithStatus(code int):終止后續(xù)調(diào)用,并設置狀態(tài)碼

Server級別中間件

Server級別中間件會對整個server的路由生效

h := server.Default()
h.Use(GlobalMiddleware())

路由組級別中間件

路由組級別中間件對當前路由組下的路徑生效

h := server.Default()
group := h.Group("/group")
group.Use(GroupMiddleware())

或者

package main
import (
    "context"
    "fmt"
    "github.com/cloudwego/hertz/pkg/app"
    "github.com/cloudwego/hertz/pkg/app/server"
)
func GroupMiddleware() []app.HandlerFunc {
    return []app.HandlerFunc{func(ctx context.Context, c *app.RequestContext) {
        fmt.Println("group middleware")
        c.Next(ctx)
    }}
}
func main() {
    h := server.Default(server.WithHostPorts("127.0.0.1:8888"))
    group := h.Group("/group", append(GroupMiddleware(),
        func(ctx context.Context, c *app.RequestContext) {
            fmt.Println("group middleware 2")
            c.Next(ctx)
        })...)
    // ...
    h.Spin()
}

使用默認中間件

Hertz 框架已經(jīng)預置了常用的 recover 中間件,使用 server.Default() 默認可以注冊該中間件

客戶端中間件

客戶端中間件可以在請求發(fā)出之前或獲取響應之后執(zhí)行:

  • 中間件可以在請求發(fā)出之前執(zhí)行,比如統(tǒng)一為請求添加簽名或其他字段。
  • 中間件也可以在收到響應之后執(zhí)行,比如統(tǒng)一修改響應結(jié)果適配業(yè)務邏輯。

實現(xiàn)一個中間件

客戶端中間件實現(xiàn)和服務端中間件不同。Client 側(cè)無法拿到中間件 index 實現(xiàn)遞增,因此 Client 中間件采用提前構(gòu)建嵌套函數(shù)的形式實現(xiàn),在實現(xiàn)一個中間件時,可以參考下面的代碼。

func MyMiddleware(next client.Endpoint) client.Endpoint {
  return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
    // pre-handle
    // ...
    err = next(ctx, req, resp)
    if err != nil {
      return
    }
    // post-handle
    // ...
  }
}

注冊一個中間件

package main
import (
    "context"
    "fmt"
    "github.com/cloudwego/hertz/pkg/app/client"
    "github.com/cloudwego/hertz/pkg/protocol"
)
func MyMiddleware(next client.Endpoint) client.Endpoint {
    return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) {
        // pre-handle
        // ...
        fmt.Println("before request")
        req.AppendBodyString("k1=v1&")
        err = next(ctx, req, resp)
        if err != nil {
            return
        }
        // post-handle
        // ...
        fmt.Println("after request")
        return nil
    }
}
func main() {
    client, _ := client.NewClient()
    client.Use(MyMiddleware)
    statusCode, body, err := client.Post(context.Background(),
        []byte{},
        "http://httpbin.org/redirect-to?url=http%3A%2F%2Fhttpbin.org%2Fpost&status_code=302",
        &protocol.Args{})
    fmt.Printf("%d, %s, %s", statusCode, body, err)
}

總結(jié)

  • 了解Gorm/Kitex/Hertz是什么
  • 熟悉Gorm/Kitex/Hertz的基礎使用
  • 通過實戰(zhàn)案例分析將三個框架的使用串聯(lián)起來

以上就是Go框架三件套Gorm Kitex Hertz基本用法與常見API講解的詳細內(nèi)容,更多關(guān)于Go框架Gorm Kitex Hertz的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中struct的匿名屬性特征實例分析

    Go語言中struct的匿名屬性特征實例分析

    這篇文章主要介紹了Go語言中struct的匿名屬性特征,實例分析了struct的匿名屬性特征,對于深入學習Go語言程序設計具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang-切片slice的創(chuàng)建方式

    golang-切片slice的創(chuàng)建方式

    這篇文章主要介紹了golang-切片slice的創(chuàng)建方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • golang判斷chan channel是否關(guān)閉的方法

    golang判斷chan channel是否關(guān)閉的方法

    這篇文章主要介紹了golang判斷chan channel是否關(guān)閉的方法,結(jié)合實例形式對比分析了Go語言判斷chan沒有關(guān)閉的后果及關(guān)閉的方法,需要的朋友可以參考下
    2016-07-07
  • CentOS 32 bit安裝golang 1.7的步驟詳解

    CentOS 32 bit安裝golang 1.7的步驟詳解

    Go是Google開發(fā)的一種編譯型,并發(fā)型,并具有垃圾回收功能的編程語言。在發(fā)布了6個rc版本之后,Go 1.7終于正式發(fā)布了。本文主要介紹了在CentOS 32 bit安裝golang 1.7的步驟,文中給出了詳細的步驟,相信對大家的學習和理解具有一定的參考借鑒價值,下面來一起看看吧。
    2016-12-12
  • Golang中的自定義類型之間的轉(zhuǎn)換的實現(xiàn)(type conversion)

    Golang中的自定義類型之間的轉(zhuǎn)換的實現(xiàn)(type conversion)

    這篇文章主要介紹了Golang中的自定義類型之間的轉(zhuǎn)換的實現(xiàn)(type conversion),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • Go設計模式之原型模式圖文詳解

    Go設計模式之原型模式圖文詳解

    原型模式是一種創(chuàng)建型設計模式, 使你能夠復制已有對象, 而又無需使代碼依賴它們所屬的類,本文將通過圖片和文字讓大家可以詳細的了解Go的原型模式,感興趣的通過跟著小編一起來看看吧
    2023-07-07
  • 詳解Golang使用MongoDB通用操作

    詳解Golang使用MongoDB通用操作

    這篇文章主要介紹了詳解Golang使用MongoDB通用操作,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • GO接收GET/POST參數(shù)及發(fā)送GET/POST請求的實例詳解

    GO接收GET/POST參數(shù)及發(fā)送GET/POST請求的實例詳解

    這篇文章主要介紹了GO接收GET/POST參數(shù)及發(fā)送GET/POST請求,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Go基礎教程系列之Go接口使用詳解

    Go基礎教程系列之Go接口使用詳解

    這篇文章主要介紹了Go基礎教程系列之Go接口使用詳解,需要的朋友可以參考下
    2022-04-04
  • Go語言并發(fā)編程 互斥鎖詳情

    Go語言并發(fā)編程 互斥鎖詳情

    在并發(fā)編程中,多個Goroutine訪問同一塊內(nèi)存資源時可能會出現(xiàn)競態(tài)條件,我們需要在臨界區(qū)中使用適當?shù)耐讲僮鱽硪员苊飧倯B(tài)條件。Go 語言中提供了很多同步工具,本文將介紹互斥鎖Mutex和讀寫鎖RWMutex的使用方法。
    2021-10-10

最新評論