这是我参与「第五届青训营 」笔记创作活动的第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.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");
}
生成代码: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
可以看到
- 在 “biz/handler/hello/example/hello_service.go” 下新增了新的方法
- 在 “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 实战案例
笔记项目地址