您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Go框架三件套Gorm、Kitex、Hertz怎么使用”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
Gorm、Kitex、Hertz的基本用法與常見的API講解
gorm是Golang語(yǔ)言中一個(gè)已經(jīng)迭代數(shù)十年且功能強(qiáng)大、性能極好的ORM框架
ORM:Object Relational Mapping(對(duì)象關(guān)系映射),其主要作用是在編程中,把面向?qū)ο?/strong>的概念跟數(shù)據(jù)庫(kù)中表的概念對(duì)應(yīng)起來(lái),
簡(jiǎn)單來(lái)說(shuō),在golang中,自定義的一個(gè)結(jié)構(gòu)體對(duì)應(yīng)著一張表,結(jié)構(gòu)體的實(shí)例則對(duì)應(yīng)著表中的一條記錄。
Kitex是字節(jié)內(nèi)部Golang微服務(wù)RPC框架 具有高性能、強(qiáng)可擴(kuò)展的主要特點(diǎn) 支持多協(xié)議并且擁有豐富的開源擴(kuò)展
Hertz是字節(jié)內(nèi)部的Http框架 參考了其他開源框架的優(yōu)勢(shì) 結(jié)合字節(jié)跳動(dòng)內(nèi)部的需求 具有高可用、高性能、高擴(kuò)展性的特點(diǎn)
該部分筆記主要參考:gorm.io/zh_CN/docs
模型是標(biāo)準(zhǔn)的 struct,由 Go 的基本數(shù)據(jù)類型、實(shí)現(xiàn)了 Scanner 和 Valuer 接口的自定義類型及其指針或別名組成
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)于配置
默認(rèn)情況下,GORM 使用 ID
作為主鍵,使用結(jié)構(gòu)體名的 蛇形復(fù)數(shù)
作為表名,字段名的 蛇形
作為列名,并使用 CreatedAt
、UpdatedAt
字段追蹤創(chuàng)建、更新時(shí)間
GORM 定義一個(gè) gorm.Model
結(jié)構(gòu)體,其包括字段 ID
、CreatedAt
、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)體中,以包含這幾個(gè)字段,例如:
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 }
GORM 官方支持的數(shù)據(jù)庫(kù)類型有: MySQL, PostgreSQL, SQlite, SQL Server
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
GORM 允許通過(guò)一個(gè)現(xiàn)有的數(shù)據(jù)庫(kù)連接來(lái)初始化 *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{})
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} result := db.Create(&user) // 通過(guò)數(shù)據(jù)的指針來(lái)創(chuàng)建 user.ID // 返回插入數(shù)據(jù)的主鍵 result.Error // 返回 error result.RowsAffected // 返回插入記錄的條數(shù)
將切片數(shù)據(jù)傳遞給 Create
方法,GORM 將生成一個(gè)單一的 SQL 語(yǔ)句來(lái)插入所有數(shù)據(jù),并回填主鍵的值,鉤子方法也會(huì)被調(diào)用。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} DB.Create(&users) for _, user := range users { user.ID // 1,2,3 }
GORM 支持根據(jù) map[string]interface{}
和 []map[string]interface{}{}
創(chuàng)建記錄
DB.Model(&User{}).Create(map[string]interface{}{ "Name": "jinzhu", "Age": 18, }) // 根據(jù) `[]map[string]interface{}{}` 批量插入 DB.Model(&User{}).Create([]map[string]interface{}{ {"Name": "jinzhu_1", "Age": 18}, {"Name": "jinzhu_2", "Age": 20}, })
GORM 提供了 First
、Take
、Last
方法,以便從數(shù)據(jù)庫(kù)中檢索單個(gè)對(duì)象。當(dāng)查詢數(shù)據(jù)庫(kù)時(shí)它添加了 LIMIT 1
條件,且沒有找到記錄時(shí),它會(huì)返回 ErrRecordNotFound
錯(cuò)誤
// 獲取第一條記錄(主鍵升序) 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 // 返回找到的記錄數(shù) result.Error // returns error // 檢查 ErrRecordNotFound 錯(cuò)誤 errors.Is(result.Error, gorm.ErrRecordNotFound)
First
、Last
方法會(huì)根據(jù)主鍵查找到第一個(gè)、最后一個(gè)記錄, 它僅在通過(guò) struct 或提供 model 值進(jìn)行查詢時(shí)才起作用。如果 model 類型沒有定義主鍵,則按第一個(gè)字段排序
var user User // 可以 DB.First(&user) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 // 可以 result := map[string]interface{}{} DB.Model(&User{}).First(&result) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 // 不行 result := map[string]interface{}{} DB.Table("users").First(&result) // 但可以配合 Take 使用 result := map[string]interface{}{} DB.Table("users").Take(&result) // 根據(jù)第一個(gè)字段排序 type Language struct { Code string Name string } DB.First(&Language{}) // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
// 獲取全部記錄 result := db.Find(&users) // SELECT * FROM users; result.RowsAffected // 返回找到的記錄數(shù),相當(dāng)于 `len(users)` result.Error // returns error
String條件
// 獲取第一條匹配的記錄 db.Where("name = ?", "jinzhu").First(&user) // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; // 獲取全部匹配的記錄 db.Where("name <> ?", "jinzhu").Find(&users) // SELECT * FROM users WHERE name <> 'jinzhu'; // IN db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users) // SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2'); // LIKE db.Where("name LIKE ?", "%jin%").Find(&users) // SELECT * FROM users WHERE name LIKE '%jin%'; // AND db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22; // Time db.Where("updated_at > ?", lastWeek).Find(&users) // SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00'; // BETWEEN db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&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(&User{Name: "jinzhu", Age: 20}).First(&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(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20; // 主鍵切片條件 db.Where([]int64{20, 21, 22}).Find(&users) // SELECT * FROM users WHERE id IN (20, 21, 22);
注意 當(dāng)使用結(jié)構(gòu)作為條件查詢時(shí),GORM 只會(huì)查詢非零值字段。這意味著如果您的字段值為 0
、''
、false
或其他 零值,該字段不會(huì)被用于構(gòu)建查詢條件
Not 條件
構(gòu)建NOT條件,用法與 Where
類似
db.Not("name = ?", "jinzhu").First(&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(&users) // SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2"); // Struct db.Not(User{Name: "jinzhu", Age: 18}).First(&user) // SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1; // 不在主鍵切片中的記錄 db.Not([]int64{1,2,3}).First(&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(&users) // SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin'; // Struct db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&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(&users) // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
選擇想從數(shù)據(jù)庫(kù)中檢索的字段,默認(rèn)情況下會(huì)選擇全部字段
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;
指定從數(shù)據(jù)庫(kù)檢索記錄時(shí)的排序方式
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;
Limit
指定獲取記錄的最大數(shù)量 Offset
指定在開始返回記錄之前要跳過(guò)的記錄數(shù)量
db.Limit(3).Find(&users) // SELECT * FROM users LIMIT 3; // 通過(guò) -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; // 通過(guò) -1 消除 Offset 條件 db.Offset(10).Find(&users1).Offset(-1).Find(&users2) // SELECT * FROM users OFFSET 10; (users1) // SELECT * FROM users; (users2)
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)
從模型中選擇不相同的值
db.Distinct("name", "age").Order("name, age desc").Find(&results)
GORM 允許通過(guò) Select 方法選擇特定的字段,如果您在應(yīng)用程序中經(jīng)常使用此功能,你也可以定義一個(gè)較小的結(jié)構(gòu)體,以實(shí)現(xiàn)調(diào)用 API 時(shí)自動(dòng)選擇特定的字段
type User struct { ID uint Name string Age int Gender string // 假設(shè)后面還有幾百個(gè)字段... } type APIUser struct { ID uint Name string } // 查詢時(shí)會(huì)自動(dòng)選擇 `id`, `name` 字段 db.Model(&User{}).Limit(10).Find(&APIUser{}) // SELECT `id`, `name` FROM `users` LIMIT 10
子查詢可以嵌套在查詢中,GORM 允許在使用 *gorm.DB
對(duì)象作為參數(shù)時(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%")
GORM 允許您在 Table
方法中通過(guò) 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 條件可以更輕松的編寫復(fù)雜 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
會(huì)保存所有的字段,即使字段是零值
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;
當(dāng)使用 Update
更新單列時(shí),需要有一些條件,否則將會(huì)引起錯(cuò)誤 ErrMissingWhereClause
。當(dāng)使用 Model
方法,并且值中有主鍵值時(shí),主鍵將會(huì)被用于構(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 的值進(jìn)行更新 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{}
參數(shù)。當(dāng)使用 struct
更新時(shí),默認(rèn)情況下,GORM 只會(huì)更新非零值的字段
// 根據(jù) `struct` 更新屬性,只會(huì)更新非零值的字段 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;
如果想要在更新時(shí)選定、忽略某些字段,您可以使用 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})
如果尚未通過(guò) Model
指定記錄的主鍵,則 GORM 會(huì)執(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);
// 通過(guò) `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 // 更新的錯(cuò)誤
刪除一條記錄時(shí),刪除對(duì)象需要指定主鍵,否則會(huì)觸發(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";
GORM 允許通過(guò)主鍵(可以是復(fù)合主鍵)和內(nèi)聯(lián)條件來(lái)刪除對(duì)象,它可以使用數(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 會(huì)執(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%";
// 返回所有列 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}}
如果模型包含了一個(gè) gorm.deletedat
字段(gorm.Model
已經(jīng)包含了該字段),它將自動(dòng)獲得軟刪除的能力!
擁有軟刪除能力的模型調(diào)用 Delete
時(shí),記錄不會(huì)從數(shù)據(jù)庫(kù)中被真正刪除。但 GORM 會(huì)將 DeletedAt
置為當(dāng)前時(shí)間, 并且你不能再通過(guò)普通的查詢方法找到該記錄。
// 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; // 在查詢時(shí)會(huì)忽略被軟刪除的記錄 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 會(huì)在事務(wù)里執(zhí)行寫入操作(創(chuàng)建、更新、刪除)。如果沒有這方面的要求,可以在初始化時(shí)禁用它,這將獲得大約 30%+ 性能提升。
// 全局禁用 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ SkipDefaultTransaction: true, }) // 持續(xù)會(huì)話模式 tx := db.Session(&Session{SkipDefaultTransaction: true}) tx.First(&user, 1) tx.Find(&users) tx.Model(&user).Update("Age", 18)
要在事務(wù)中執(zhí)行一系列操作,一般流程如下
db.Transaction(func(tx *gorm.DB) error { // 在事務(wù)中執(zhí)行一些 db 操作(從這里開始,您應(yīng)該使用 'tx' 而不是 'db') if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil { // 返回任何錯(cuò)誤都會(huì)回滾事務(wù) return err } if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil { return err } // 返回 nil 提交事務(wù) return nil })
GORM 支持嵌套事務(wù),您可以回滾較大事務(wù)內(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)用事務(wù)控制方法(commit、rollback)
// 開始事務(wù) tx := db.Begin() // 在事務(wù)中執(zhí)行一些 db 操作(從這里開始,您應(yīng)該使用 'tx' 而不是 'db') tx.Create(...) // ... // 遇到錯(cuò)誤時(shí)回滾事務(wù) tx.Rollback() // 否則,提交事務(wù) tx.Commit()
使用PrepareStmt
緩存預(yù)編譯語(yǔ)句可以提高后續(xù)調(diào)用的速度,提高大約35%左右。
db , err := gorm.Open(mysql.Open("username:password@tcp(localhost:9910)/database?charset=utf8"),&gorm.Config{ PrepareStmt: true}
Kitex目前對(duì)Windows的支持不完善,建議使用虛擬機(jī)或WSL2
安裝代碼生成工具
go install github.com/cloudwego/tool/cmd/kitex@latest go install github.com/cloudwego/thriftgo@latest
kitex
是 Kitex 框架提供的用于生成代碼的一個(gè)命令行工具。目前,kitex 支持 thrift 和 protobuf 的 IDL,并支持生成一個(gè)服務(wù)端項(xiàng)目的骨架。
IDL是什么:IDL 全稱是 Interface Definition Language,接口定義語(yǔ)言
為什么使用IDL:要進(jìn)行 RPC,就需要知道對(duì)方的接口是什么,需要傳什么參數(shù),同時(shí)也需要知道返回值是什么樣的,就好比兩個(gè)人之間交流,需要保證在說(shuō)的是同一個(gè)語(yǔ)言、同一件事。 這時(shí)候,就需要通過(guò) IDL 來(lái)約定雙方的協(xié)議,就像在寫代碼的時(shí)候需要調(diào)用某個(gè)函數(shù),我們需要知道函數(shù)簽名一樣。
首先我們需要編寫一個(gè) IDL,這里以 thrift IDL 為例。
首先創(chuàng)建一個(gè)名為 echo.thrift
的 thrift IDL 文件。
然后在里面定義我們的服務(wù)
namespace go api struct Request { 1: string message } struct Response { 1: string message } service Echo { Response echo(1: Request req) }
有了 IDL 以后我們便可以通過(guò) kitex 工具生成項(xiàng)目代碼了,執(zhí)行如下命令:
$ kitex -module example -service example echo.thrift
上述命令中,-module
表示生成的該項(xiàng)目的 go module 名,-service
表明我們要生成一個(gè)服務(wù)端項(xiàng)目,后面緊跟的 example
為該服務(wù)的名字。最后一個(gè)參數(shù)則為該服務(wù)的 IDL 文件。
生成后的項(xiàng)目結(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
需要編寫的服務(wù)端邏輯都在 handler.go
這個(gè)文件中
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ù)就對(duì)應(yīng)了我們之前在 IDL 中定義的 echo
方法。
現(xiàn)在讓我們修改一下服務(wù)端邏輯,讓 Echo
服務(wù)起到作用。
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)幫我們生成好了編譯和運(yùn)行所需的腳本:
編譯:
$ sh build.sh
執(zhí)行上述命令后,會(huì)生成一個(gè) output
目錄,里面含有我們的編譯產(chǎn)物。
運(yùn)行:
$ sh output/bootstrap.sh
執(zhí)行上述命令后,Echo
服務(wù)就開始運(yùn)行了。
有了服務(wù)端后,接下來(lái)就編寫一個(gè)客戶端用于調(diào)用剛剛運(yùn)行起來(lái)的服務(wù)端。
首先,同樣的,先創(chuàng)建一個(gè)目錄用于存放我們的客戶端代碼:
$ mkdir client
進(jìn)入目錄:
$ cd client
創(chuàng)建一個(gè) main.go
文件,然后就開始編寫客戶端代碼了。
首先讓我們創(chuàng)建一個(gè)調(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
,其第一個(gè)參數(shù)為調(diào)用的 服務(wù)名(用于微服務(wù)中的服務(wù)發(fā)現(xiàn)),第二個(gè)參數(shù)為 options,用于傳入?yún)?shù), 此處的 client.WithHostPorts
用于指定服務(wù)端的地址。
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)建了一個(gè)請(qǐng)求 req
, 然后通過(guò) c.Echo
發(fā)起了調(diào)用。
其第一個(gè)參數(shù)為 context.Context
,通過(guò)通常用其傳遞信息或者控制本次調(diào)用的一些行為,你可以在后續(xù)章節(jié)中找到如何使用它。
其第二個(gè)參數(shù)為本次調(diào)用的請(qǐng)求。
其第三個(gè)參數(shù)為本次調(diào)用的 options
,Kitex 提供了一種 callopt
機(jī)制,顧名思義——調(diào)用參數(shù) ,有別于創(chuàng)建 client 時(shí)傳入的參數(shù),這里傳入的參數(shù)僅對(duì)此次生效。 此處的 callopt.WithRPCTimeout
用于指定此次調(diào)用的超時(shí)(通常不需要指定,此處僅作演示之用)。
在編寫完一個(gè)簡(jiǎn)單的客戶端后,我們終于可以發(fā)起調(diào)用了。
可以通過(guò)下述命令來(lái)完成這一步驟:
$ go run main.go
如果不出意外,可以看到類似如下輸出:
2023/01/26 07:23:35 Response({Message:my request})
至此成功編寫了一個(gè) Kitex 的服務(wù)端和客戶端,并完成了一次調(diào)用!
首先,我們需要安裝使用demo所需要的命令行工具 hz:
確保 GOPATH
環(huán)境變量已經(jīng)被正確地定義(例如 export GOPATH=~/go
)并且將$GOPATH/bin
添加到 PATH
環(huán)境變量之中(例如 export PATH=$GOPATH/bin:$PATH
);請(qǐng)勿將 GOPATH
設(shè)置為當(dāng)前用戶沒有讀寫權(quán)限的目錄
安裝 hz:go install github.com/cloudwego/hertz/cmd/hz@latest
若將代碼放置于$GOPATH/src
下,需在$GOPATH/src
下創(chuàng)建額外目錄,進(jìn)入該目錄后再獲取代碼:
$ mkdir -p $(go env GOPATH)/src/github.com/cloudwego $ cd $(go env GOPATH)/src/github.com/cloudwego
若將代碼放置于 GOPATH 之外,可直接獲取
在當(dāng)前目錄下創(chuàng)建 hertz_demo 文件夾,進(jìn)入該目錄中
創(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
完成以上操作后,我們可以直接編譯并啟動(dòng) Server
$ go build -o hertz_demo && ./hertz_demo
如果成功啟動(dòng),將看到以下信息
2023/01/26 07:23:35 Response({Message:my request})
接下來(lái),我們可以對(duì)接口進(jìn)行測(cè)試
$ curl http://127.0.0.1:8888/ping
如果不出意外,可以看到類似如下輸出
$ {"message":"pong"}
到現(xiàn)在,我們已經(jīng)成功啟動(dòng)了 Hertz Server,并完成了一次調(diào)用!
Hertz提供了參數(shù)路由和通配路由,路由的優(yōu)先級(jí)為:靜態(tài)路由>命名路由>通配路由
Hertz中間件的種類是多種多樣的,簡(jiǎn)單分為兩大類:
服務(wù)端中間件
客戶端中間件
中間件可以在請(qǐng)求更深入地傳遞到業(yè)務(wù)邏輯之前或之后執(zhí)行:
中間件可以在請(qǐng)求到達(dá)業(yè)務(wù)邏輯之前執(zhí)行,比如執(zhí)行身份認(rèn)證和權(quán)限認(rèn)證,當(dāng)中間件只有初始化(pre-handle)相關(guān)邏輯,且沒有和 real handler 在一個(gè)函數(shù)調(diào)用棧中的需求時(shí),中間件中可以省略掉最后的.Next
,如圖中的中間件 B。
中間件也可以在執(zhí)行過(guò)業(yè)務(wù)邏輯之后執(zhí)行,比如記錄響應(yīng)時(shí)間和從異常中恢復(fù)。如果在業(yè)務(wù) handler 處理之后有其它處理邏輯( post-handle ),或?qū)瘮?shù)調(diào)用鏈(棧)有強(qiáng)需求,則必須顯式調(diào)用.Next
,如圖中的中間件 C。
// 方式一 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 // ... } }
中間件會(huì)按定義的先后順序依次執(zhí)行,如果想快速終止中間件調(diào)用,可以使用以下方法,注意當(dāng)前中間件仍將執(zhí)行。
Abort()
:終止后續(xù)調(diào)用
AbortWithMsg(msg string, statusCode int)
:終止后續(xù)調(diào)用,并設(shè)置 response中body,和狀態(tài)碼
AbortWithStatus(code int)
:終止后續(xù)調(diào)用,并設(shè)置狀態(tài)碼
Server級(jí)別中間件會(huì)對(duì)整個(gè)server的路由生效
h := server.Default() h.Use(GlobalMiddleware())
路由組級(jí)別中間件對(duì)當(dāng)前路由組下的路徑生效
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)預(yù)置了常用的 recover 中間件,使用 server.Default()
默認(rèn)可以注冊(cè)該中間件
客戶端中間件可以在請(qǐng)求發(fā)出之前或獲取響應(yīng)之后執(zhí)行:
中間件可以在請(qǐng)求發(fā)出之前執(zhí)行,比如統(tǒng)一為請(qǐng)求添加簽名或其他字段。
中間件也可以在收到響應(yīng)之后執(zhí)行,比如統(tǒng)一修改響應(yīng)結(jié)果適配業(yè)務(wù)邏輯。
客戶端中間件實(shí)現(xiàn)和服務(wù)端中間件不同。Client 側(cè)無(wú)法拿到中間件 index 實(shí)現(xiàn)遞增,因此 Client 中間件采用提前構(gòu)建嵌套函數(shù)的形式實(shí)現(xiàn),在實(shí)現(xiàn)一個(gè)中間件時(shí),可以參考下面的代碼。
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) }
“Go框架三件套Gorm、Kitex、Hertz怎么使用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。