序言
本文主要介绍了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的默认约定
- Gorm使用名为ID的字段作为主键;
- 使用结构体的蛇形负数作为表明;
- 字段名的蛇形作为列名;
- 使用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)
性能优化
- 关闭事务;
- 使用PrepareStmt 缓存预编译语句可以提高调用速度,可提升大约35%左右;
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 提供了 GET
、POST
、PUT
、DELETE
、ANY
等方法用于注册路由。
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概念和作用,三个框架基本的使用,最后通过实战案例项目串联了三个框架在实际场景中的用法。