这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
本次笔记将记录go的框架三件套:GORM、Kitex和Hertz
一些概念
-
DSN:数据源名称,即Data Source Name,指当我们的数据库建立好之后,系统需要知道数据的来源,所以就需要设定系统的 DSN(数据来源名称)。
-
ORM:对象关系映射,即Object-Relationl Mapping,是在关系型数据库和对象之间作一个映射。
-
RPC:远程过程调用,即Remote Procedure Call。它是利用网络从远程计算机上请求服务,可以理解为把程序的一部分放在其他远程计算机上执行。
-
IDL:接口描述语言,即Interface description language,是跨平台开发的基础。对于多个不同的平台、主机一起开发某个项目时,IDL用于集成。
-
GORM:Go中一款性能极好的ORM库,数据表对应结构体,数据行对应结构体实例,数据库字段对应结构体字段。个人感觉与MyBatis差不多。
-
Kitex:字节内部的Golang微服务RPC框架,具有高性能、强可扩展性的特点。
-
Hertz:字节内部的HTTP框架,具有高易用性、高性能、高扩展性的特点。用于注册路由。
GORM
GORM官网指南:
https://gorm.io/zh_CN/docs/index.html
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
作用
相比较于java,GORM实现着与JDBC、MyBatis等驱动的功能,能够在golang的环境内对数据库进行操作,能实现增删改查,同时也支持Hook、事务等功能。
操作
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
}
func (p User) TableName() string{
return "user"
}
//创建一个结构体User,结构体User对应表名为user
连接数据库
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{})
}
增加
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数
批量增加
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
查询
// 获取第一条记录(主键升序)
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 or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
若想避免ErrRecordNotFound错误,你可以使用Find,比如
db.Limit(1).Find(&user),Find方法可以接受struct和slice的数据。
带有条件的查询,如下图
删除
删除一条记录时,删除对象需要指定主键,否则会触发批量 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";
根据主键删除
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
更新
更新单个列
// 条件更新
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;
更新多列
// 根据 `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;
//**注意** 当使用 struct 进行更新时,GORM 只会更新非零值的字段。
//你可以使用 `map` 更新字段,或者使用 `Select` 指定要更新的字段
以上为最基本的GORM用法,更多使用方法请访问官网。
Kitex
安装
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
go install github.com/cloudwego/thriftgo@latest
定义IDL
namespace go api
struct Request {
1: string message
}
struct Response {
1: string message
}
service Echo {
Response echo(1: Request req)
}
如果我们要进行PRC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值时什么样的,这个时候,就需要通过IDL来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。
Kitex生成代码
kitex -module example -service example echo.thrift
生成后的目录:
- build.sh :构建脚本
- kitex_gen:IDL 内容相关的生成代码,主要是基础的Server/Client 代码。
- main.go 程序入口
- handler.go用户在该文件里实现IDLservice 定义的方法
使用
创建Client
服务默认监听8888端口
echo.NewClient 用于创建 client,其第一个参数为调用的 服务名,第二个参数为options,用于传入参数, 此处的 client.WithHostPorts 用于指定服务端的地址。
import "github.com/cloudwego/kitex/client"
...
c, err := echo.NewClient("example", client.WithHostPorts("0.0.0.0:8888"))
if err != nil {
log.Fatal(err)
}
服务注册与发现
Kitex 的服务注册与发现已经对接了主流了服务注册与发现中心,如 ETCD,Nacos 等。
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)
}
}
Hertz
样例
//Hertz 提供了 GET、POST、PUT、DELETE、ANY 等方法用于注册路由。
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
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 提供了参数路由和通配路由,路由的优先级为:静态路由 > 命名路由 > 通配路由
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
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 提供了 Bind、Validate、 BindAndValidate 函数用于进行参数绑定和校验。
func main() {
r := server.New()
r.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
// 参数绑定需要配合特定的go tag使用
type Test struct {
A string `query:"a" vd:"$!='Hertz'"`
}
// BindAndValidate
var req Test
err := ctx.BindAndValidate(&req)
...
// Bind
req = Test{}
err = ctx.Bind(&req)
...
// Validate,需要使用 "vd" tag
err = ctx.Validate(&req)
...
})
...
}
中间件
分为两类:服务端中间件和客户端中间件
//此为服务端的中间件的实现
func MyMiddleware() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) {
// pre-handle
// ...
c.Next(ctx)
}
}
Hertz提供了代码生成工具Hz,通过定义IDL文件即可生成对应的基础服务代码。
安装hz:
go install github.com/cloudwego/hertz/cmd/hz@latest
- 用thrift IDL创建新项目,下面是thrift IDL文件:
// idl/hello.thrift
namespace go hello.example
struct HelloReq {
1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
}
struct HelloResp {
1: string RespBody;
}
service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}
好难啊