Go框架三件套讲解(Web/RPC/ORM) | 青训营

序言

本文主要介绍了Go框架三件套 GORM、Kitex、Hertz 相关的知识。
GORM: 是一个已经迭代了十多年的功能强大的ORM框架。
Kitex: 是字节跳动内部的 Golang 微服务 RPC 框架,具有高性能强可扩展 的特点,在字节内部已广泛使用。如果对微服务性能有要求,又希望定制扩展融入自己的治理体系,Kitex 会是一个不错的选择。
Hertz: 是HTTP框架,参考其他开源框架的优势,具有高易用性、高性能、高扩展性特点。

三件套使用

Gorm的基本使用

定义结构体Student

定义结构体Student,属性有(Id,Name, Password, Age, CreatedAt),可以理解为Java项目中的实体对象,用于存放数据库中操作的数据。

go 复制代码
type Student struct {
    Id        int64     `gorm:"primaryKey"`  // 指定id为主键主键 默认"ID"为主键
    Name      string    `gorm:"column:name"` //指定字段名为 name
    Password  string    `gorm:"column:password"`
    Age       int64     `gorm:"column:age"`
    CreatedAt time.Time `gorm:"column:create_at"` // 蛇形命名
}

GORM 使用结构体名的蛇形命名作为表名。对于结构体 Student 根据约定,数据库中会创建表为 students表, 我们可以手动设置表名为student,可通过定义 Tabler 接口来更改默认表名,示例如下:

go 复制代码
type Tabler interface {
    TableName() string
}

// TableName 会将 Student 的表名重写为 `student`
func (Student) TableName() string {
    return "student"
}

Gorm的默认约定

  1. Gorm使用名为ID的字段作为主键;
  2. 使用结构体的蛇形负数作为表明;
  3. 字段名的蛇形作为列名;
  4. 使用CreatedAt、UpdatedAt字段作为创建、更新;

连接MySql数据库 并新增数据

GORM支持MySQL、SQL SErver、PostgreSQL、SQLite数据库,此处以连接Mysql为例。

连接数据库并无法操作数据库,需要使用db.AutoMigrate(&Student{})迁移将结构体与数据库建立联系,通过结构体会创建数据库。

go 复制代码
func main() {

    //连接数据库
    dsn := "root:ml105237@tcp(127.0.0.1:3306)/exercise?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Printf("数据库连接失败!")
    }

    // 迁移
    db.AutoMigrate(&Student{})

    //新增数据
    student := Student{1, "xiaoming", "123456", 13, time.Now()}
    res := db.Create(&student)
    fmt.Println("执行的有些条数:", res.RowsAffected)
}

输出结果:

执行的有些条数: 1

数据库中创建了数据库表student,并新增了数据

claus.OnConflict处理数据冲突

css 复制代码
student := Student{Id: 2, Name: "小舞"}
//db.Create(student)
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&student)

批量新增数据

go 复制代码
//数据三条数据
students := []Student{
    {2, "张三", "333333", 33, time.Now()},
    {3, "李四", "444444", 44, time.Now()},
    {4, "王五", "555555", 55, time.Now()},
}
res := db.Create(&students)
fmt.Println("执行的有些条数:", res.RowsAffected)

输出结果:

执行的有些条数: 3

查看数据库

修改

根据用户id修改用户姓名,将id=1的用户名由"xiaoming"修改为"唐三"

go 复制代码
// 修改数据 
res := db.Model(&Student{Id: 1}).Update("name", "唐三")
fmt.Println(res)

查询

提供了如下五种查询方式。

scss 复制代码
// 查询 方式一(获取第一条记录(主键升序))
student1 := Student{}
db.First(&student1)
fmt.Println("方式一 查询 student1为:", student1)

// 查询 方式二 (获取一条记录,没有指定排序字段)
student2 := Student{}
db.Take(&student2)
fmt.Println("方式二 查询的student2为:", student2)

// 查询 方式三 (获取最后一条记录(主键降序)
student3 := Student{}
db.Last(&student3)
fmt.Println("方式三 查询的student3为:", student3)

// 查询 方式四 (根据主键查询)
student4 := Student{}
db.First(&student4, 3)
fmt.Println("方式四 查询的student4为:", student4)

// 查询 方式五 全部查询
students := []Student{}
db.Find(&students)
fmt.Println("方式五查询的所有学生数据如下:")
for i := 0; i < len(students); i++ {
    fmt.Println(students[i])
}

输出结果:

css 复制代码
方式一 查询 student1为: {1 唐三 123456 13 2023-08-11 21:27:55.082 +0800 CST}
方式二 查询的student2为: {1 唐三 123456 13 2023-08-11 21:27:55.082 +0800 CST}
方式三 查询的student3为: {4 王五 555555 55 2023-08-11 21:46:33.18 +0800 CST}
方式四 查询的student4为: {3 李四 444444 44 2023-08-11 21:46:33.18 +0800 CST}
方式五查询的所有学生数据如下:
{1 唐三 123456 13 2023-08-11 21:27:55.082 +0800 CST}
{2 张三 333333 33 2023-08-11 21:46:33.18 +0800 CST}
{3 李四 444444 44 2023-08-11 21:46:33.18 +0800 CST}
{4 王五 555555 55 2023-08-11 21:46:33.18 +0800 CST}

删除

根据用户id删除数据,传入的参数&student是下限,当没有其他任何参数时,下限中的id为条件删除数据。

css 复制代码
var students = []Student{}
db.Find(&students)
fmt.Println("删除前数据如下:")
for i := 0; i < len(students); i++ {
    fmt.Println(students[i])
}

// 根据id删除
student1 := Student{Id: 3}
db.Delete(&student1, 3)
// 根据用户名删除
student2 := Student{Id: 4}
db.Where("name", "王五").Delete(&student2)

db.Find(&students)
fmt.Println("删除后数据如下:")
for i := 0; i < len(students); i++ {
    fmt.Println(students[i])
}

输出结果:

css 复制代码
删除前数据如下:
{1 唐三 123456 13 2023-08-11 21:27:55.082 +0800 CST}
{2 张三 333333 33 2023-08-11 21:46:33.18 +0800 CST}
{3 李四 444444 44 2023-08-11 21:46:33.18 +0800 CST}
{4 王五 555555 55 2023-08-11 21:46:33.18 +0800 CST}
删除后数据如下:
{1 唐三 123456 13 2023-08-11 21:27:55.082 +0800 CST}
{2 张三 333333 33 2023-08-11 21:46:33.18 +0800 CST}

事务

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。

go 复制代码
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
})

开启事务会影响一定的性能,如果没有这方面的要求,可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

css 复制代码
// 全局禁用 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    SkipDefaultTransaction: true, 
})  
// 持续会话模式 
tx := db.Session(&Session{SkipDefaultTransaction: true}) 
tx.First(&student, 1) 
tx.Find(&students) 
tx.Model(&student).Update("Age", 18)

性能优化

  1. 关闭事务;
  2. 使用PrepareStmt 缓存预编译语句可以提高调用速度,可提升大约35%左右;

学习链接 gorm.cn/zh_CN/docs/...

GORM生态

GORM 拥有丰富的扩展生态,部分举例如下: Gorm文档:gorm.cn

Kitex

Kitex目前对Windows支持不完善,需要使用虚拟机或WSL2。

安装

安装 kitex:go install github.com/cloudwego/kitex/tool/cmd/kitex@latest

安装 thriftgo:go install github.com/cloudwego/thriftgo@latest

查看 kitex 安装版本:kitex --version

查看 thriftgo 安装版本:thriftgo --version

定义IDL

使用IDL定义服务与接口,如果我们要进行RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值。此时,通过IDL来约定双方的协议。
hello.thrift文件

go 复制代码
namespace go api

struct Request {
    1: string message
}

struct Response {
    1: string message
}

service Hello {
    Response echo(1: Request req)
}

生成服务代码

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

上述命令中:

  • -module 表示生成的该项目的 go module 名;建议使用完整包名 ,例如 github.com/Yourname/exampleserver
  • -service 表明我们要生成一个服务端项目,后面紧跟的 example-server 为该服务的名字
  • 最后一个参数则为该服务的 IDL 文件

生成后的项目结构如下:

服务默认监听8888端口

go 复制代码
package main

import (
    "context"
    "github.com/cloudwego/kitex-examples/hello/kitex_gen/api"
)

// HelloImpl implements the last service interface defined in the IDL.
type HelloImpl struct{}

// Echo implements the HelloImpl interface.
func (s *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) {
    // TODO: Your code here...
    resp = &api.Response{Message: req.Message}
    return
}

创建Client 发起请求

css 复制代码
func main() {
    // 创建Client
    client, err := hello.NewClient("hello", client.WithHostPorts("0.0.0.0:8888"))
    if err != nil {
       log.Fatal(err)
    }
    // 发起请求
    for {
       req := &api.Request{Message: "my request"}
       resp, err := client.Echo(context.Background(), req)
       if err != nil {
          log.Fatal(err)
       }
       log.Println(resp)
       time.Sleep(time.Second)
    }
}

服务注册与发现

当前Kitex的服务注册与发现已经对接了主流服务注册与发现中心,如ETCD,Nacos.

go 复制代码
func main() {
	r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
	if err != nil {
		log.Fatal(err)
	}
	client := hello.MustNewClient("Hello", client.WithResolver(r))
	for {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
		resp, err := client.Echo(ctx, &api.Request{Message: "Hello"})
		cancel()
		if err != nil {
			log.Fatal(err)
		}
		log.Println(resp)
		time.Sleep(time.Second)
	}
}

github链接: github.com/kitex-contr...

Kitex 生态

Hertz基本使用

监听服务端口

使用Hertz实现,服务监听8080端口,并注册一个GET方法的路由函数.

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()
}

路由

Hertz 提供了 GETPOSTPUTDELETEANY 等方法用于注册路由。

javascript 复制代码
func main(){
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))

	h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})

	h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "get")
	})
	h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "post")
	})
	h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "put")
	})
	h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "delete")
	})
	h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "patch")
	})
	h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "head")
	})
	h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "options")
	})
	h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "any")
	})
	h.Handle("LOAD","/load", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "load")
	})
	h.Spin()
}

路由组

Hertz 提供了路由组 ( Group ) 的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。

示例代码:

javascript 复制代码
func main(){
	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")
	})
	v1.POST("/post", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "post")
	})
	v2 := h.Group("/v2")
	v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "put")
	})
	v2.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "delete")
	})
	h.Spin()
}

Hertz 支持丰富的路由类型用于实现复杂的功能,包括静态路由、参数路由 (命名参数、通配参数)。

路由的优先级:静态路由 > 命名参数路由 > 通配参数路由

go 复制代码
// 命名参数路由  
h.GET("/hertz/:version", func(ctx context.Context, c *app.RequestContext) {
		version := c.Param("version")
		c.String(consts.StatusOK, "Hello %s", version)
	})
// 通配参数路由
h.GET("/hertz/:version/*action", func(ctx context.Context, c *app.RequestContext) {
		version := c.Param("version")
		action := c.Param("action")
		message := version + " is " + action
		c.String(consts.StatusOK, message)
	})

中间件-Recovery

Recovery 中间件是 Hertz 框架预置的中间件,使用 server.Default() 可以默认注册该中间件,为 Hertz 框架提供 panic 恢复的功能。

如果你不使用server.Default(),你也可以通过以下方式注册 Recovery 中间件:

css 复制代码
h := server.New()
h.Use(recovery.Recovery())

Hert Client

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

代码生成工具

Hertz 提供了代码生成工具Hz,通过定义IDL文件即可生成对应的基础服务代码。

csharp 复制代码
namespace go hello.example

struct HelloReq {
    1: string Name (api, query="name")
}

struct HelloResp {
    1: string RespBody
}

service HelloService {
    HelloResp HelloMethod(1: HelloReq request) (api.get="/hello")
}

Hertz 生态

实战案例介绍

项目笔记是一个使用Hertz、Kitex、Gorm搭建出来的具备一定业务逻辑的后端API项目。

项目地址: github.com/cloudwego/k...

课程总结

本文主要介绍了Go框架三件套GORM、Kitex和Hertz概念和作用,三个框架基本的使用,最后通过实战案例项目串联了三个框架在实际场景中的用法。

相关推荐
滑滑滑2 天前
后端实践-优化一个已有的 Go 程序提高其性能 | 豆包MarsCode AI刷题
青训营笔记
柠檬柠檬2 天前
Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题
青训营笔记
用户967136399652 天前
计算最小步长丨豆包MarsCodeAI刷题
青训营笔记
用户52975799354723 天前
字节跳动青训营刷题笔记2| 豆包MarsCode AI刷题
青训营笔记
clearcold3 天前
浅谈对LangChain中Model I/O的见解 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵3 天前
【字节青训营】 Go 进阶语言:并发概述、Goroutine、Channel、协程池 | 豆包MarsCode AI刷题
青训营笔记
用户336901104444 天前
数字分组求和题解 | 豆包MarsCode AI刷题
青训营笔记
dnxb1234 天前
GO语言工程实践课后作业:实现思路、代码以及路径记录 | 豆包MarsCode AI刷题
青训营笔记
用户916357440954 天前
AI刷题-动态规划“DNA序列编辑距离” | 豆包MarsCode AI刷题
青训营笔记
热的棒打鲜橙4 天前
数字分组求偶数和 | 豆包MarsCode AI刷题
青训营笔记