Go框架三件套之Hertz | 青训营笔记

351 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第1天

Hertz

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

安装命令行工具hz

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

~ » hz -version                             
hz version v0.5.0

hz生成代码测试

创建 hertz_demo 文件夹,进入该目录中

初始化:go mod init hertz_demo

生成代码: hz new -mod hertz_demo

整理依赖:go mod tidy

编译项目:go build

运行项目并测试:./hertz_demo

测试:curl 'http://127.0.0.1:8888/ping'

如果返回{"message":"pong"},说明接口调通。

基于thrift IDL 创建项目

将上一个测试生成的目录代码都删除,使用 thrift 重新生成项目

创建 idl 目录,并进入

// idl/hello.thrift
namespace go hello.examplestruct HelloReq {
    1: string Name (api.query="name"); // 添加 api 注解为方便进行参数绑定
}
​
struct HelloResp {
    1: string RespBody;
}
​
​
service HelloService {
    HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}

生成代码:hz new -mod hertz_demo -idl idl/hello.thrift

整理依赖:go mod tidy

修改handler,添加自己的逻辑:

// Code generated by hertz generator.package example
​
import (
   "context"
​
   "github.com/cloudwego/hertz/pkg/app"
   "github.com/cloudwego/hertz/pkg/protocol/consts"
   example "hertz_demo/biz/model/hello/example"
)
​
// HelloMethod .
// @router ./hello [GET]
func HelloMethod(ctx context.Context, c *app.RequestContext) {
   var err error
   var req example.HelloReq
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(consts.StatusBadRequest, err.Error())
      return
   }
​
   resp := new(example.HelloResp)
   //可以修改整个函数的逻辑,而不仅仅局限于当前模版
   resp.RespBody = "hello, " + req.Name
   c.JSON(consts.StatusOK, resp)
}

编译:go build

运行:./hertz_demo

测试:curl --location --request GET 'http://127.0.0.1:8888/hello?name=hertz'

如果返回{"RespBody":"hello,hertz"},说明接口调通。

update:更新一个已有的项目

hello.thrift 有更新

// idl/hello.thrift
namespace go hello.example
​
struct HelloReq {
    1: string Name (api.query="name");
}
​
struct HelloResp {
    1: string RespBody;
}
​
struct OtherReq {
    1: string Other (api.body="other");
}
​
struct OtherResp {
    1: string Resp;
}
​
​
service HelloService {
    HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
    OtherResp OtherMethod(1: OtherReq request) (api.post="/other");
}
​
service NewService {
    HelloResp NewMethod(1: HelloReq request) (api.get="/new");
}

更新修改后的 thrift idl:hz update -idl idl/hello.thrift

可以看到

  1. 在 “biz/handler/hello/example/hello_service.go” 下新增了新的方法
  2. 在 “biz/handler/hello/example” 下新增了文件 “new_service.go” 以及对应的 “NewMethod” 方法。

开发OtherMehod接口

// HelloMethod .
// @router /hello [GET]
func HelloMethod(ctx context.Context, c *app.RequestContext) {
   var err error
   var req example.HelloReq
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }
​
   resp := new(example.HelloResp)
​
   // 你可以修改整个函数的逻辑,而不仅仅局限于当前模板
   resp.RespBody = "hello," + req.Name // 添加的逻辑
​
   c.JSON(200, resp)
}
​
// OtherMethod .
// @router /other [POST]
func OtherMethod(ctx context.Context, c *app.RequestContext) {
   var err error
   // example.OtherReq 对应的model文件也会重新生成
   var req example.OtherReq
   err = c.BindAndValidate(&req)
   if err != nil {
      c.String(400, err.Error())
      return
   }
​
   resp := new(example.OtherResp)
​
   // 增加的逻辑
   resp.Resp = "Other method: " + req.Other
​
   c.JSON(200, resp)
}

编译:go build

运行项目:./hertz_demo

测试:

curl --location --request POST 'http://127.0.0.1:8888/other' \
--header 'Content-Type: application/json' \
--data-raw '{
    "Other": "other method"
}'

如果返回{"Resp":"Other method: other method"},说明接口调通。

生成代码的结构

hz 生成的代码结构都类似,下面以"基于 thrift IDL 创建项目"小节生成的代码结构为例,说明 hz 生成的代码的含义。

.
├── biz                                // business 层,存放业务逻辑相关流程
│   ├── handler                        // 存放 handler 文件
│   │   ├── hello                      // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│   │   │   └── example
│   │   │       ├── hello_service.go   // handler 文件,用户在该文件里实现 IDL service 定义的方法,update 时会查找 当前文件已有的 handler 在尾部追加新的 handler
│   │   │       └── new_service.go     // 同上,idl 中定义的每一个 service 对应一个文件
│   │   └── ping.go                    // 默认携带的 ping handler,用于生成代码快速调试,无其他特殊含义
│   ├── model                          // IDL 内容相关的生成代码
│   │   └── hello                      // hello/example 对应 thrift idl 中定义的 namespace;而对于 protobuf idl,则是对应 go_package
│   │     └── example
│   │         └── hello.go             // thriftgo 的产物,包含 hello.thrift 定义的内容的 go 代码,update 时会重新生成
│   └── router                         // idl 中定义的路由相关生成代码
│       ├── hello                      // hello/example 对应 thrift idl 中定义的namespace;而对于 protobuf idl,则是对应 go_package 的最后一级
│       │   └── example
│       │       ├── hello.go           // hz 为 hello.thrift 中定义的路由生成的路由注册代码;每次 update 相关 idl 会重新生成该文件
│       │       └── middleware.go      // 默认中间件函数,hz 为每一个生成的路由组都默认加了一个中间件;update 时会查找当前文件已有的 middleware 在尾部追加新的 middleware
│       └── register.go                // 调用注册每一个 idl 文件中的路由定义;当有新的 idl 加入,在更新的时候会自动插入其路由注册的调用;勿动
├── go.mod                             // go.mod 文件,如不在命令行指定,则默认使用相对于GOPATH的相对路径作为 module 名
├── idl                                // 用户定义的idl,位置可任意
│   └── hello.thrift
├── main.go                            // 程序入口
├── router.go                          // 用户自定义除 idl 外的路由方法
└── router_gen.go                      // hz 生成的路由注册代码,用于调用用户自定义的路由以及 hz 生成的路由

支持的 api 注解

Field 注解可用于参数绑定及校验

Method 注解可用于生成路由注册相关代码

支持的 api 注解:

Field 注解
注解说明
api.raw_body生成 “raw_body” tag
api.query生成 “query” tag
api.header生成 “header” tag
api.cookie生成 “cookie” tag
api.body生成 “json” tag
api.path生成 “path” tag
api.form生成 “form” tag
api.go_tag (protobuf) go.tag (thrift)透传 go_tag,会生成 go_tag 里定义的内容
api.vd生成 “vd” tag
Method 注解
注解说明
api.get定义 GET 方法及路由
api.post定义 POST 方法及路由
api.put定义 PUT 方法及路由
api.delete定义 DELETE 方法及路由
api.patch定义 PATCH 方法及路由
api.options定义 OPTIONS 方法及路由
api.head定义 HEAD 方法及路由
api.any定义 ANY 方法及路由

使用方法:

Field 注解:

Thrift:

struct Demo {
    1: string Demo (api.query="demo", api.path="demo");
    2: string GoTag (go.tag="goTag:"tag"");
    3: string Vd (api.vd="$!='your string'");
}

Protobuf:

message Demo {
  string Demo = 1[(api.query)="demo",(api.path)="demo"];
  string GoTag = 2[(api.go_tag)="goTag:"tag""];
  string Vd = 3[(api.vd)="$!='your string'"];
}
Method 注解:

Thrift:

service Demo {
    Resp Method(1: Req request) (api.get="/route");
}

Protobuf:

service Demo {
  rpc Method(Req) returns(Resp) {
    option (api.get) = "/route";
  }
}

3 实战案例

笔记项目地址

github.com/cloudwego/k…