我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go框架三件套Gorm、Kitex、Hertz怎么使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go框架三件套Gorm、Kitex、Hertz怎么使用

本篇内容介绍了“Go框架三件套Gorm、Kitex、Hertz怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

三件套介绍

Gorm、Kitex、Hertz的基本用法与常见的API讲解

Gorm

gorm是Golang语言中一个已经迭代数十年且功能强大、性能极好的ORM框架

ORM:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,

简单来说,在golang中,自定义的一个结构体对应着一张表,结构体的实例则对应着表中的一条记录。

Kitex

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

Hertz

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

三件套使用

Gorm

该部分笔记主要参考:gorm.io/zh_CN/docs

声明模型

模型定义

模型是标准的 struct,由 Go 的基本数据类型、实现了 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 倾向于约定优于配置

默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

gorm.Model

GORM 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt

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

还可以将它嵌入到结构体中,以包含这几个字段,例如:

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 官方支持的数据库类型有: 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 参数, 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4

使用现有数据库连接

GORM 允许通过一个现有的数据库连接来初始化 *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接口

创建记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}result := db.Create(&user) // 通过数据的指针来创建user.ID             // 返回插入数据的主键result.Error        // 返回 errorresult.RowsAffected // 返回插入记录的条数
批量插入

将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}DB.Create(&users)for _, user := range users {  user.ID // 1,2,3}
通过Map数据类型创建记录

GORM 支持根据 map[string]interface{}[]map[string]interface{}{} 创建记录

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

查询

检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

// 获取第一条记录(主键升序)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// 检查 ErrRecordNotFound 错误errors.Is(result.Error, gorm.ErrRecordNotFound)

FirstLast 方法会根据主键查找到第一个、最后一个记录, 它仅在通过 struct 或提供 model 值进行查询时才起作用。如果 model 类型没有定义主键,则按第一个字段排序

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)// 根据第一个字段排序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 // 返回找到的记录数,相当于 `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';// INdb.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');// LIKEdb.Where("name LIKE ?", "%jin%").Find(&users)// SELECT * FROM users WHERE name LIKE '%jin%';// ANDdb.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;// Timedb.Where("updated_at > ?", lastWeek).Find(&users)// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';// BETWEENdb.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 条件

// Structdb.Where(&User{Name: "jinzhu", Age: 20}).First(&user)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;// Mapdb.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);

注意 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0''false 或其他 零值,该字段不会被用于构建查询条件

Not 条件

构建NOT条件,用法与 Where 类似

db.Not("name = ?", "jinzhu").First(&user)// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;// Not Indb.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");// Structdb.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';// Structdb.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);// Mapdb.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);

选择特定字段

选择想从数据库中检索的字段,默认情况下会选择全部字段

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;

Order查询

指定从数据库检索记录时的排序方式

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

Limit & Offset查询

Limit 指定获取记录的最大数量 Offset 指定在开始返回记录之前要跳过的记录数量

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 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 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 对象作为参数时生成子查询

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` = 18subQuery1 := 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 = 100db.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 方法,并且值中有主键值时,主键将会被用于构建条件

// 条件更新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 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段

// 根据 `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;
更新选定字段

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

// 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 会执行批量更新

// 根据 struct 更新db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})// UPDATE users SET name='hello', age=18 WHERE role = 'admin';// 根据 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);

获取更新的记录数

// 通过 `RowsAffected` 得到更新的记录数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 // 更新的记录数result.Error        // 更新的错误

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量 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 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)

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 会执行批量删除,它将删除所有匹配的记录

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 []UserDB.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 已经包含了该字段),它将自动获得软删除的能力!

拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 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;

事务

禁用默认事务

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

// 全局禁用db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{  SkipDefaultTransaction: true,})// 持续会话模式tx := db.Session(&Session{SkipDefaultTransaction: true})tx.First(&user, 1)tx.Find(&users)tx.Model(&user).Update("Age", 18)
事务开启

要在事务中执行一系列操作,一般流程如下

db.Transaction(func(tx *gorm.DB) error {  // 在事务中执行一些 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 支持嵌套事务,您可以回滚较大事务内执行的一部分操作

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 支持直接调用事务控制方法(commit、rollback)

// 开始事务tx := db.Begin()// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')tx.Create(...)// ...// 遇到错误时回滚事务tx.Rollback()// 否则,提交事务tx.Commit()

Gorm性能提高

使用PrepareStmt缓存预编译语句可以提高后续调用的速度,提高大约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@latestgo install github.com/cloudwego/thriftgo@latest

使用

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

编写IDL

IDL是什么:IDL 全称是 Interface Definition Language,接口定义语言

为什么使用IDL:要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。

首先我们需要编写一个 IDL,这里以 thrift IDL 为例。

首先创建一个名为 echo.thrift 的 thrift IDL 文件。

然后在里面定义我们的服务

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

生成echo服务代码

有了 IDL 以后我们便可以通过 kitex 工具生成项目代码了,执行如下命令:

$ kitex -module example -service example echo.thrift

上述命令中,-module 表示生成的该项目的 go module 名,-service 表明我们要生成一个服务端项目,后面紧跟的 example 为该服务的名字。最后一个参数则为该服务的 IDL 文件。

生成后的项目结构如下:

.|-- 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 mainimport (  "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 函数就对应了我们之前在 IDL 中定义的 echo 方法。

现在让我们修改一下服务端逻辑,让 Echo 服务起到作用。

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

编译运行

kitex 工具已经帮我们生成好了编译和运行所需的脚本:

编译:

$ sh build.sh

执行上述命令后,会生成一个 output 目录,里面含有我们的编译产物。

运行:

$ sh output/bootstrap.sh

执行上述命令后,Echo 服务就开始运行了。

编写客户端

有了服务端后,接下来就编写一个客户端用于调用刚刚运行起来的服务端。

首先,同样的,先创建一个目录用于存放我们的客户端代码:

$ mkdir client

进入目录:

$ cd client

创建一个 main.go 文件,然后就开始编写客户端代码了。

首先让我们创建一个调用所需的 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 用于创建 client,其第一个参数为调用的 服务名(用于微服务中的服务发现),第二个参数为 options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址。

发起调用

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)

上述代码中,我们首先创建了一个请求 req , 然后通过 c.Echo 发起了调用。

其第一个参数为 context.Context,通过通常用其传递信息或者控制本次调用的一些行为,你可以在后续章节中找到如何使用它。

其第二个参数为本次调用的请求。

其第三个参数为本次调用的 options ,Kitex 提供了一种 callopt 机制,顾名思义——调用参数 ,有别于创建 client 时传入的参数,这里传入的参数仅对此次生效。 此处的 callopt.WithRPCTimeout 用于指定此次调用的超时(通常不需要指定,此处仅作演示之用)。

在编写完一个简单的客户端后,我们终于可以发起调用了。

可以通过下述命令来完成这一步骤:

$ go run main.go

如果不出意外,可以看到类似如下输出:

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

至此成功编写了一个 Kitex 的服务端和客户端,并完成了一次调用!

Hertz

安装命令行工具hz

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

  • 确保 GOPATH 环境变量已经被正确地定义(例如 export GOPATH=~/go)并且将$GOPATH/bin添加到 PATH 环境变量之中(例如 export PATH=$GOPATH/bin:$PATH);请勿将 GOPATH 设置为当前用户没有读写权限的目录

  • 安装 hz:go install github.com/cloudwego/hertz/cmd/hz@latest

确定代码放置位置

  • 若将代码放置于$GOPATH/class="lazy" data-src下,需在$GOPATH/class="lazy" data-src下创建额外目录,进入该目录后再获取代码:

  $ mkdir -p $(go env GOPATH)/class="lazy" data-src/github.com/cloudwego  $ cd $(go env GOPATH)/class="lazy" data-src/github.com/cloudwego
  • 若将代码放置于 GOPATH 之外,可直接获取

编写示例代码

  • 在当前目录下创建 hertz_demo 文件夹,进入该目录中

  • 创建 main.go 文件

  • main.go 文件中添加以下代码

package mainimport (    "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"}

到现在,我们已经成功启动了 Hertz Server,并完成了一次调用!

Hertz路由优先级

Hertz提供了参数路由和通配路由,路由的优先级为:静态路由>命名路由>通配路由

Hertz中间件

Hertz中间件的种类是多种多样的,简单分为两大类:

  • 服务端中间件

  • 客户端中间件

服务端中间件

Go框架三件套Gorm、Kitex、Hertz怎么使用

中间件可以在请求更深入地传递到业务逻辑之前或之后执行:

  • 中间件可以在请求到达业务逻辑之前执行,比如执行身份认证和权限认证,当中间件只有初始化(pre-handle)相关逻辑,且没有和 real handler 在一个函数调用栈中的需求时,中间件中可以省略掉最后的.Next,如图中的中间件 B。

  • 中间件也可以在执行过业务逻辑之后执行,比如记录响应时间和从异常中恢复。如果在业务 handler 处理之后有其它处理逻辑( post-handle ),或对函数调用链(栈)有强需求,则必须显式调用.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    // ...  }}

中间件会按定义的先后顺序依次执行,如果想快速终止中间件调用,可以使用以下方法,注意当前中间件仍将执行

  • Abort():终止后续调用

  • AbortWithMsg(msg string, statusCode int):终止后续调用,并设置 response中body,和状态码

  • AbortWithStatus(code int):终止后续调用,并设置状态码

Server级别中间件

Server级别中间件会对整个server的路由生效

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

路由组级别中间件

路由组级别中间件对当前路由组下的路径生效

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

或者

package mainimport (    "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 框架已经预置了常用的 recover 中间件,使用 server.Default() 默认可以注册该中间件

客户端中间件

客户端中间件可以在请求发出之前或获取响应之后执行:

  • 中间件可以在请求发出之前执行,比如统一为请求添加签名或其他字段。

  • 中间件也可以在收到响应之后执行,比如统一修改响应结果适配业务逻辑。

实现一个中间件

客户端中间件实现和服务端中间件不同。Client 侧无法拿到中间件 index 实现递增,因此 Client 中间件采用提前构建嵌套函数的形式实现,在实现一个中间件时,可以参考下面的代码。

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 mainimport (    "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怎么使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go框架三件套Gorm、Kitex、Hertz怎么使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go框架三件套Gorm、Kitex、Hertz怎么使用

本篇内容介绍了“Go框架三件套Gorm、Kitex、Hertz怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!三件套介绍Gorm、Ki
2023-07-05

go代理框架怎么使用

go代理框架可以用于实现HTTP代理或SOCKS代理。一个常用的go代理框架是goproxy,下面是使用goproxy框架实现HTTP代理的示例代码:```gopackage mainimport ("github.com/elazarl/
2023-06-14

Go语言中怎么使用ORM框架构造查询条件

这篇文章主要介绍了Go语言中怎么使用ORM框架构造查询条件的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go语言中怎么使用ORM框架构造查询条件文章都会有所收获,下面我们一起来看看吧。构造查询条件worm是一款
2023-07-04

Gin框架怎么使用panic处理中间件

这篇文章主要介绍“Gin框架怎么使用panic处理中间件”,在日常操作中,相信很多人在Gin框架怎么使用panic处理中间件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Gin框架怎么使用panic处理中间件
2023-07-06

怎么在php中使用lavarel框架导出文件

怎么在php中使用lavarel框架导出文件?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。php有什么用php是一个嵌套的缩写名称,是英文超级文本预处理语言,它的语法混合了
2023-06-14

Golang怎么使用Gin框架实现HTTP上传文件

本文小编为大家详细介绍“Golang怎么使用Gin框架实现HTTP上传文件”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang怎么使用Gin框架实现HTTP上传文件”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知
2023-07-05

使用Vant框架list组件遇到的坑怎么解决

本篇内容介绍了“使用Vant框架list组件遇到的坑怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用Vant框架list组件的坑介
2023-06-30

Express框架两个内置中间件方法怎么使用

这篇文章主要介绍“Express框架两个内置中间件方法怎么使用”,在日常操作中,相信很多人在Express框架两个内置中间件方法怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Express框架两个内置
2023-07-05

Vue怎么使用distpicker插件实现省市级下拉框三级联动

本篇内容介绍了“Vue怎么使用distpicker插件实现省市级下拉框三级联动”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!安装distpi
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录