再探Kitex、Hertz生成目录及easy_note目录分析 | 青训营笔记

299 阅读5分钟

再探Kitex、Hertz生成目录及easy_note目录分析 | 青训营笔记

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

目录

前提准备


先clone easy_note的idl文件cloudwego/biz-demo/easy_note/idl

image-20230124103735444

Kitex生成目录


kitex代码生成

image-20230124104617168

  • --module:指定module名
  • --service:指定生成服务名生成服务端侧代码

kitex代码生成后目录

image-20230124105018935

kitex.yaml

image-20230124105904145

该文件声明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中只做了两件事:

  1. 新建rpc服务(svr := demonote.NewServer(new(NoteServiceImpl))

  2. 启动rpc服务(err := svr.Run()

scripts文件夹和build.sh

服务启动脚本,可以先忽略~

kitex_gen目录

image-20230124111219119

  • 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代码生成

image-20230124145546992

  • --idl: 指定idl文件的位置
  • --module: 指定生成模块名

Hertz代码生成后目录

image-20230124145946799

.hz

image-20230124150141908

该文件标记hz版本

main.go
package main

import (
	"github.com/cloudwego/hertz/pkg/app/server"
)

func main() {
	h := server.Default()

	register(h)
	h.Spin()
}

main完成三件事:

  1. server.Default(): 以默认配置构造服务端实例
  2. register(h): 注册路由
  3. 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
image-20230124151248843

主注册路由函数router_gen.go中,由自定义注册路由函数customizedRegister转入,在此文件的自定义注册路由。

biz文件夹
image-20230124151857607
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文件夹

image-20230124153821247

  • 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提供的服务。

image-20230124155803320

基本项目目录

image-20230124155314856

docker-compose.yaml

本项目使用到了etcd注册中心,mysql数据库,opentelemetry链路追踪等众多服务,因此使用docker部署上述服务。

在项目文件夹下,使用docker-compose up部署并启动服务

idl包

放置接口描述语言文件,无需多谈

kitex_gen文件夹

放置demouser和demoapi的kitex生成文件,无需多言~

pkg包
image-20230124160719877

pkg包下放置非服务信息:

  • configs:otel和sql的配置信息
  • consts:放置常量信息
  • errno:自定义的错误码
  • mv:服务端和客户端的处理中间件,一般为信息记录
cmd包

主体逻辑在这个包下!!!

user文件夹(note同)

image-20230124161123080

  • dal(data access layer)定义了DAO,主要是使用Gorm配合的数据库处理
  • output:user服务的启动脚本所在文件夹
  • pack:完成数据模型到响应模型的转换
  • script:服务构建脚本
  • service:具体服务逻辑的实现 image-20230124161717849
  • main.go:服务启动时向etcd注册自己的服务信息,并载入各类中间件
  • handler.go:接受具体rpc请求响应返回处理结果
api文件夹
image-20230124162229576

重复文件见上方代码生成后目录

  • mw文件夹 本项目使用了全局中间件jwt鉴权,为此定义了mw文件夹 image-20230124162640686

  • rpc文件夹 api和note及user是通过rpc通信,所以定义了rpc文件夹

    其中包含了三个文件 image-20230124162759580

    • 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的服务

image-20230124163341454

image-20230124173354180

image-20230124173410297

  • 测试api

    image-20230124173458285

    image-20230124173544066

    image-20230124173709577

    image-20230124173657756

引用参考


使用 Hertz 和 Gorm 快速搭建 Web 服务 - 掘金 (juejin.cn)

biz-demo/easy_note at main · cloudwego/biz-demo (github.com)