再探Kitex、Hertz生成目录及easy_note目录分析 | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第6天
目录
前提准备
先clone easy_note的idl文件cloudwego/biz-demo/easy_note/idl
Kitex生成目录
kitex代码生成
- --module:指定module名
- --service:指定生成服务名生成服务端侧代码
kitex代码生成后目录
kitex.yaml
该文件声明kitex的生成信息:包括命令行参数定义的服务名和kitex版本
main.go
package main
import (
demonote "demo/kitex_gen/demonote/noteservice"
"log"
)
func main() {
svr := demonote.NewServer(new(NoteServiceImpl))
err := svr.Run()
if err != nil {
log.Println(err.Error())
}
}
main.go中只做了两件事:
-
新建rpc服务(
svr := demonote.NewServer(new(NoteServiceImpl))
) -
启动rpc服务(
err := svr.Run()
)
scripts文件夹和build.sh
服务启动脚本,可以先忽略~
kitex_gen目录
- kitex_gen目录下的noteservice文件夹包含的是rpc调用的驱动信息 包括:服务端的启动信息,根据idl生成的服务接口,反射函数的定义,接口使用的包装
- 而k-consts.go/k-note.go/note.go就是rpc底层的传输实现,包含根据thrift实现的rpc传输信息的包装、传输和验证。
handler.go
业务实现最核心的部分,该文件包含了在idl文件中定义的服务方法的具体实现。 在使用kitex生成脚手架代码后,要将业务代码现在此文件内。
ackage main
import (
"context"
demonote "demo/kitex_gen/demonote"
)
// NoteServiceImpl implements the last service interface defined in the IDL.
type NoteServiceImpl struct{}
// CreateNote implements the NoteServiceImpl interface.
func (s *NoteServiceImpl) CreateNote(ctx context.Context, req *demonote.CreateNoteRequest) (resp *demonote.CreateNoteResponse, err error) {
// TODO: Your code here...
return
}
...
Hertz生成目录
Hertz代码生成
- --idl: 指定idl文件的位置
- --module: 指定生成模块名
Hertz代码生成后目录
.hz
该文件标记hz版本
main.go
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
h := server.Default()
register(h)
h.Spin()
}
main完成三件事:
server.Default()
: 以默认配置构造服务端实例register(h)
: 注册路由h.Spin()
: 开启自旋,监听等待
router_gen.go
注册路由的主函数,由hertz生成
// register registers all routers.
func register(r *server.Hertz) {
router.GeneratedRegister(r)
customizedRegister(r)
}
router.GenerateRegister(r)
转入biz/router文件夹下的路由注册处理customizedRegister(r)
转入router.go文件处理
router.go
主注册路由函数router_gen.go中,由自定义注册路由函数customizedRegister
转入,在此文件的自定义注册路由。
biz文件夹
handler文件夹
处理http请求的主逻辑代码,其中服务名文件夹(此处为demoapi)下的逻辑为根据idl文件生成的模板代码。
// CreateUser .
// @router /v2/user/register [POST]
func CreateUser(ctx context.Context, c *app.RequestContext) {
var err error
var req demoapi.CreateUserRequest
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(demoapi.CreateUserResponse)
c.JSON(consts.StatusOK, resp)
}
// CheckUser .
// @router /v2/user/login [POST]
func CheckUser(ctx context.Context, c *app.RequestContext) {
var err error
var req demoapi.CheckUserRequest
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(demoapi.CheckUserResponse)
c.JSON(consts.StatusOK, resp)
}
...
module文件夹
http响应模型的定义文件夹,包含http处理过程中的底层细节
router文件夹
-
register.go
package router import ( demoapi "demo/biz/router/demoapi" "github.com/cloudwego/hertz/pkg/app/server" ) // GeneratedRegister registers routers generated by IDL. func GeneratedRegister(r *server.Hertz) { //INSERT_POINT: DO NOT DELETE THIS LINE! demoapi.Register(r) }
接受主路由注册函数的调用,转向idl定义路由注册的文件夹
-
demoapi文件夹 idl定义的路由注册函数所在文件夹,包含定义路由注册和中间件处理
-
路由注册 api.go
func Register(r *server.Hertz) { root := r.Group("/", rootMw()...) { _v2 := root.Group("/v2", _v2Mw()...) _v2.POST("/note", append(_noteMw(), demoapi.CreateNote)...) _note := _v2.Group("/note", _noteMw()...) _note.PUT("/:note_id", append(_updatenoteMw(), demoapi.UpdateNote)...) _note.DELETE("/:note_id", append(_deletenoteMw(), demoapi.DeleteNote)...) _note.GET("/query", append(_querynoteMw(), demoapi.QueryNote)...) { _user := _v2.Group("/user", _userMw()...) _user.POST("/login", append(_checkuserMw(), demoapi.CheckUser)...) _user.POST("/register", append(_createuserMw(), demoapi.CreateUser)...) } } }
此为hertz根据idl服务需求自动生成的路由注册函数,其中
append()
,包含了对应服务的中间件(_xxMw()) -
中间件处理 middleware.go
func rootMw() []app.HandlerFunc { // your code... return nil } func _v2Mw() []app.HandlerFunc { // your code... return nil } func _noteMw() []app.HandlerFunc { // your code... return nil } func _updatenoteMw() []app.HandlerFunc { // your code... return nil } ...
api.go中的自动生成路由注册使用到的中间件定义在此文件中,如需使用对应服务中间件,改写此文件中中间件函数对应内容即可。
-
easy_note项目目录
easy_note项目地址
biz-demo/easy_note at main · cloudwego/biz-demo (github.com)
项目架构图
-
项目启动时,demonote和demouser服务端启动,将自己的服务信息注册到注册中心etcd上。
-
demoapi启动时,注册各请求的路由,初始化要使用的中间件,并向etcd解析待使用服务的远程服务调用地址。
-
demoapi、demouser和demonote三个服务通过rpc通信。demoapi由hertz部署,接受http请求,在完成鉴权等一系列中间件处理后,根据请求调用对应方法。这些方法底层通过rpc通信,远程调用demonote和demouser提供的服务。

基本项目目录
docker-compose.yaml
本项目使用到了etcd注册中心,mysql数据库,opentelemetry链路追踪等众多服务,因此使用docker部署上述服务。
在项目文件夹下,使用
docker-compose up
部署并启动服务
idl包
放置接口描述语言文件,无需多谈
kitex_gen文件夹
放置demouser和demoapi的kitex生成文件,无需多言~
pkg包

pkg包下放置非服务信息:
- configs:otel和sql的配置信息
- consts:放置常量信息
- errno:自定义的错误码
- mv:服务端和客户端的处理中间件,一般为信息记录
cmd包
主体逻辑在这个包下!!!
user文件夹(note同)
- dal(data access layer)定义了DAO,主要是使用Gorm配合的数据库处理
- output:user服务的启动脚本所在文件夹
- pack:完成数据模型到响应模型的转换
- script:服务构建脚本
- service:具体服务逻辑的实现
- main.go:服务启动时向etcd注册自己的服务信息,并载入各类中间件
- handler.go:接受具体rpc请求响应返回处理结果
api文件夹
重复文件见上方代码生成后目录
-
mw文件夹 本项目使用了全局中间件jwt鉴权,为此定义了mw文件夹
-
rpc文件夹 api和note及user是通过rpc通信,所以定义了rpc文件夹
其中包含了三个文件
-
init.go用来初始化rpc服务,向etcd获取服务信息
--init.go-- func Init() { initUser() initNote() } --user.go-- func initNote() { r, err := etcd.NewEtcdResolver([]string{consts.ETCDAddress}) if err != nil { panic(err) } provider.NewOpenTelemetryProvider( provider.WithServiceName(consts.ApiServiceName), provider.WithExportEndpoint(consts.ExportEndpoint), provider.WithInsecure(), ) c, err := noteservice.NewClient( consts.NoteServiceName, client.WithResolver(r), client.WithMuxConnection(1), client.WithMiddleware(mw.CommonMiddleware), client.WithInstanceMW(mw.ClientMiddleware), client.WithSuite(tracing.NewClientSuite()), client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: consts.ApiServiceName}), ) if err != nil { panic(err) } noteClient = c }
-
note.go(user.go同) 包含向etcd查询服务信息(见上),以及包装远程调用为本地函数
// CreateNote create note info func CreateNote(ctx context.Context, req *demonote.CreateNoteRequest) error { resp, err := noteClient.CreateNote(ctx, req) if err != nil { return err } if resp.BaseResp.StatusCode != 0 { return errno.NewErrNo(resp.BaseResp.StatusCode, resp.BaseResp.StatusMessage) } return nil } // QueryNotes query list of note info func QueryNotes(ctx context.Context, req *demonote.QueryNoteRequest) ([]*demonote.Note, int64, error) { resp, err := noteClient.QueryNote(ctx, req) if err != nil { return nil, 0, err } if resp.BaseResp.StatusCode != 0 { return nil, 0, errno.NewErrNo(resp.BaseResp.StatusCode, resp.BaseResp.StatusMessage) } return resp.Notes, resp.Total, nil }
-
启动项目
依次开启docker、note和user的服务及api的服务
-
测试api
引用参考
使用 Hertz 和 Gorm 快速搭建 Web 服务 - 掘金 (juejin.cn)
biz-demo/easy_note at main · cloudwego/biz-demo (github.com)