网站内部资源推广的基本方法,html5餐饮美食订餐微官网wap手机网站模板整站下载,关于大创做网站的项目计划书,影城网站建设文章目录 前言1、mangokit介绍1.1 根据proto文件生成http路由1.2 根据proto文件生成响应码1.3 使用wire来管理依赖注入 2、mangokit实现2.1 protobuf插件开发2.2 mangokit工具 3、使用示例3.1 创建新项目3.2 添加新的proto文件3.3 代码生成 前言
在使用gin框架开发web应用时需要我们自己手动完成请求到结构体的反序列化以及发送响应如下
func Handler(ctx *gin.Context) {user : new(User)if err : ctx.ShouldBind(user); err ! nil {...}...resp : serivce()...ctx.Json(http.StatusOk, resp)
}显然这些工作都是多余的和业务无关的每个handler都需要我们自己处理非常的麻烦
为了解决这个问题我们可以使用反射的方式来字段完成请求数据到结构体的映射对于响应则定义一个统一的结构体并且让handler返回这个结构体如下
type Response struct {R RespValueStatus int
}type RespValue struct {Data interface{} json:dataCodee int json:codeMessagee string json:message
}func NewResponse(status, code int, message string, data interface{}) *Response {...
}func Handler(ctx *gin.Context, user *User) *Response {...resp service()...return NewResponse(http.StatusOk, 0, success, resp)
}在注册路由时则需要使用反射来对我们的handler进行适配使用反射机制创建请求参数然后将数据反序列化为对应的结构体然后调用我们定义的handler并且获取到返回值调用ctx.Json来发送
这种方式方便了我们的开发但是使用反射会对程序带来一定的性能损失但是在这里只是简单的使用性能损失很少并且使用反射容易出现错误
最近在使用了bilibili的kratos框架后给了我一些灵感我们完全可以使用proto来定义http的路由然后生成反序列化的结构代码并且可以使用proto来定义返回错误码等。
因此借鉴了kratos的设计我实现了一个小工具用来加速我的web开发
githubhttps://github.com/mangohow/mangokit 1、mangokit介绍
mangokit是一个web项目的管理工具它的功能如下
根据预设的项目结构创建出一个web项目使用已有的代码框架减少工作量使用proto来定义http路由以及错误码使用相关工具生成代码完成自动结构体反序列化以及返回值响应使用wire来管理依赖注入减少依赖管理的烦恼
1.1 根据proto文件生成http路由
proto定义文件如下
hello.proto
syntax proto3;package hello.v1;import google/api/annotations.proto;option go_package api/helloworld/v1;v1;// 定义service
service Hello {rpc SayHello (HelloRequest) returns (HelloReply) {option (google.api.http) {get: /hello/:name};}
}message HelloRequest {string name 1;
}message HelloResponse {string message 2;
}然后使用mangokit命令根据proto生成gin框架对应的路由处理器
mangokit generate proto api生成的文件如下
hello.pb.go
hello_http_gin.pb.go
其中hello.pb.go是protoc --go-out生成的而hello_http_gin.pb.go是我们自己写的proto插件protoc-gen-go-gin生成的
hello_http_gin.pb.go的代码如下
// Code generated by protoc-gen-go-gin. DO NOT EDIT.
// versions:
// - protoc-gen-go-gin v1.0.0
// - protoc v3.20.1
// source: helloworld/v1/proto/hello.protopackage v1import (contextnet/httpgithub.com/mangohow/mangokit/serializegithub.com/mangohow/mangokit/transport/httpwrapper
)type HelloHTTPService interface {SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}func RegisterHelloHTTPService(server *httpwrapper.Server, svc HelloHTTPService) {server.GET(/hello/:name, _Hello_SayHello_HTTP_Handler(svc))
}func _Hello_SayHello_HTTP_Handler(svc HelloHTTPService) httpwrapper.HandlerFunc {return func(ctx *httpwrapper.Context) error {in : new(HelloRequest)if err : ctx.BindRequest(in); err ! nil {return err}value : context.WithValue(context.Background(), gin-ctx, ctx)reply, err : svc.SayHello(value, in)if err ! nil {return err}ctx.JSON(http.StatusOK, serialize.Response{Data: reply})return nil}
}
在上面生成的go代码中包含一个接口的定义其中包含了我们定义的handler方法
并且提供了RegisterHelloHTTPService函数来注册路由注册的路由为_Hello_SayHello_HTTP_Handler函数在这个函数中有反序列化的代码以及响应代码
因此我们只需要实现HelloHTTPService中的方法并且调用RegisterHelloHTTPService来注册路由即可大大的减少了我们的工作量。
这有点类似于grpc的方式。
1.2 根据proto文件生成响应码
有时候只使用http的状态码是不够的比如200表示请求成功但是虽然请求成功了还可能出现其它问题。
比如一个登录的接口用户登录时可能出现以下的情况1、用户不存在 2、密码错误 3、用户被封禁了
因此我们需要定义相关的一些响应码来处理这些情况
proto定义文件如下
errors.proto
syntax proto3;package errors.v1;
import errors/errors.proto;option go_package api/errors/v1;v1;enum ErrorReason {// 设置缺省错误码option (errors.default_code) 500;Success 0 [(errors.code) 200];// 为某个枚举单独设置错误码UserNotFound 1 [(errors.code) 200];UserPasswordIncorrect 2 [(errors.code) 200];UserBanned 3 [(errors.code) 200];
}
在上面的proto文件中我们使用enum来定义响应码其中包括int类型的响应码以及返回的http状态码(errors.code)
然后使用mangokit来生成go代码
mangokit generate proto api生成的文件如下
errors.pb.go
errors_errors.pb.go
其中errors.pb.go是protoc --go_out生成的而errors_errors.pb.go同样也是自己编写的proto插件protoc-gen-go-error生成的
errors_errors.pb.go中的代码如下
// Code generated by protoc-gen-go-error. DO NOT EDIT.
// versions:
// - protoc-gen-go-error v1.0.0
// - protoc v3.20.1
// source: errors/v1/proto/errors.protopackage v1import (fmtgithub.com/mangohow/mangokit/errors
)func ErrorSuccess(format string, args ...interface{}) errors.Error {return errors.New(int32(ErrorReason_Success), 200, ErrorReason_Success.String(), fmt.Sprintf(format, args...))
}// 为某个枚举单独设置错误码
func ErrorUserNotFound(format string, args ...interface{}) errors.Error {return errors.New(int32(ErrorReason_UserNotFound), 200, ErrorReason_UserNotFound.String(), fmt.Sprintf(format, args...))
}func ErrorUserPasswordIncorrect(format string, args ...interface{}) errors.Error {return errors.New(int32(ErrorReason_UserPasswordIncorrect), 200, ErrorReason_UserPasswordIncorrect.String(), fmt.Sprintf(format, args...))
}func ErrorUserBanned(format string, args ...interface{}) errors.Error {return errors.New(int32(ErrorReason_UserBanned), 200, ErrorReason_UserBanned.String(), fmt.Sprintf(format, args...))
}
然后我们就可以调用这些函数来生成具体的响应码减少我们的代码工作量
1.3 使用wire来管理依赖注入
wire是谷歌开源的一款依赖注入工具相比于其它的反射式的依赖注入方式wire采用代码生成的方式来完成依赖注入代码运行效率更高
代码如下
//go:generate wire
//go:build wireinject
// build wireinjectpackage mainimport (github.com/google/wiremangokit_test/internal/confmangokit_test/internal/daomangokit_test/internal/servermangokit_test/internal/servicegithub.com/mangohow/mangokit/transport/httpwrappergithub.com/sirupsen/logrus
)func newApp(serverConf *conf.Server, dataConf *conf.Data, logger *logrus.Logger) (*httpwrapper.Server, func(), error) {panic(wire.Build(dao.ProviderSet, service.ProviderSet, server.NewHttpServer))
}
根据上面的代码wire即可自动生成依赖创建的代码
// Code generated by Wire. DO NOT EDIT.//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// build !wireinjectpackage mainimport (mangokit_test/internal/confmangokit_test/internal/daomangokit_test/internal/servermangokit_test/internal/servicegithub.com/mangohow/mangokit/transport/httpwrappergithub.com/sirupsen/logrus
)// Injectors from wire.go:func newApp(serverConf *conf.Server, dataConf *conf.Data, logger *logrus.Logger) (*httpwrapper.Server, func(), error) {db, cleanup, err : dao.NewFakeMysqlClient(dataConf)if err ! nil {return nil, nil, err}greeterDao : dao.NewGreeterDao(db)greeterService : service.NewGreeterService(greeterDao, logger)httpwrapperServer : server.NewHttpServer(serverConf, logger, greeterService)return httpwrapperServer, func() {cleanup()}, nil
}同样的mangokit中也添加了相应的指令来生成wire依赖注入代码
mangokit generate wire2、mangokit实现
mangokit主要包含三个组件
protoc-gen-go-ginprotoc-gen-go-errormangokit
protoc-gen-go-gin用于根据proto文件中定义的service来生成gin框架的路由代码
protoc-gen-go-error用于根据proto文件中定义的enum来生成相应的响应错误码
mangokit中则设置了多种指令用于管理项目比如
使用create命令来生成一个初始项目结构使用add命令来添加proto文件、makefile或Dockerfile使用generate命令来根据proto文件生成go代码、生成openapi以及生成wire依赖注入
2.1 protobuf插件开发
在使用protoc时可以指定其它的插件用于生成代码比如
--go_out则会调用protoc-gen-go插件来生成go的代码--go-grpc_out则会调用protoc-gen-go-grpc插件来生成grpc的代码
同样的我们可以使用go来实现一个类似的插件从而根据proto文件来生成gin框架的代码以及响应码代码
工作原理
在使用 protoc --go-gin_out时protoc会解析proto文件然后生成抽象语法树并且它会使用protobuf将语法树序列化为二进制序列然后使用标准输入将二进制序列传入我们的插件中然后再使用protobuf进行反序列化然后我们在自己的程序中就可以根据提供的信息来生成go代码比如proto中定义的message、service、enum等
开发proto插件我们可以使用google.golang.org/protobuf/compiler/protogen库 我们可以参考kratos的代码来实现自己的代码https://github.com/go-kratos/kratos/tree/main/cmd/protoc-gen-go-errors 首先看main函数
protogen.Options.Run来运行我们的程序
在传入的匿名函数中我们会接收到protogen.Plugin参数该参数中有proto文件中定义的各种结构的详细信息
然后我们可以遍历每个proto文件来生成相应的代码在generateFile中生成代码
package mainimport (flagfmtgoogle.golang.org/protobuf/compiler/protogengoogle.golang.org/protobuf/types/pluginpb
)var (showVersion flag.Bool(version, false, print the version and exit)
)func main() {flag.Parse()if *showVersion {fmt.Printf(protoc-gen-go-gin %v\n, version)return}protogen.Options{ParamFunc: flag.CommandLine.Set,}.Run(func(plugin *protogen.Plugin) error {plugin.SupportedFeatures uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)for _, f : range plugin.Files {if !f.Generate {continue}generateFile(plugin, f)}return nil})
}
在protogen.File中保存了一个proto文件中定义的各种结构解析后的信息 详细代码参考https://github.com/mangohow/mangokit
代码编写好之后编译为二进制程序在使用protoc时指定插件名称我们的插件一定要以protoc-gen开头在指定插件名称时指定protoc-gen后面的部分拼接上_out即可比如protoc-gen-go-error在使用时为protoc --go-error_out. hello.proto
2.2 mangokit工具
mangokit使用cobra命令行工具开发包含以下功能
创建基础项目根据预设的项目目录结构和代码生成添加文件包括api proto、error proto、makefile和Dockerfile生成代码包括go代码生成、wire生成和openapi生成 3、使用示例
3.1 创建新项目 首先使用mangokit来创建一个项目项目目录为mangokit-testgo mod名称为mangokit_test
mangokit create mangokit-test mangokit_test然后执行cd mangokit-test go mod tidy来下载依赖
项目目录结构如下
$ tree
.
|-- api
| |-- errors
| | -- v1
| | |-- errors.pb.go
| | |-- errors_errors.pb.go
| | -- proto
| | -- errors.proto
| -- helloworld
| -- v1
| |-- greeter.pb.go
| |-- greeter_http_gin.pb.go
| -- proto
| -- greeter.proto
|-- cmd
| -- server
| |-- main.go
| |-- wire.go
| -- wire_gen.go
|-- configs
| -- application.yaml
|-- internal
| |-- conf
| | |-- conf.pb.go
| | -- conf.proto
| |-- dao
| | |-- dao.go
| | |-- data.go
| | -- userdao.go
| |-- middleware
| |-- model
| | -- user.go
| |-- server
| | -- http.go
| -- service
| |-- helloservice.go
| -- service.go
|-- pkg
|-- test
|-- third_party
|-- go.mod
|-- go.sum
|-- makefile
|-- Dockerfile
|-- openapi.yaml32 directories, 52 files
apiapi目录用来放置proto文件以及根据proto文件生成的go代码通常将.proto文件放在proto文件夹下而生成的代码放在它的上一级目录这样看起来更清晰一些cmdcmd目录存放了wire注入代码和main文件configsconfigs目录用来放置程序的配置文件internalinternal用来存放本项目依赖的代码不会暴露给其它的项目其中包括middleware中间件、model数据库结构体模型、dao数据库访问对象、conf配置信息代码、server服务初始化代码、serviceservice的具体实现代码pkg用来存放一些共用代码test存放测试代码third_party其中包含一些使用到的proto的扩展文件
在创建项目时默认会从github拉取一个预制的项目结构如果遇到网络问题导致无法拉取则可以使用-r命令来指定其它的仓库比如使用gitee
mangokit create -r https://gitee.com/mangohow/mangokit-template mangokit-test mangokit_test3.2 添加新的proto文件 可以使用下面的命令来添加新的proto文件
# 添加http api
mangokit add api api/helloworld/v1/proto hello.proto然后就会在api/helloworld/v1/proto目录下生成一个hello.proto文件
syntax proto3;package hello.v1;import google/api/annotations.proto;option go_package api/helloworld/v1;v1;service Hello {}使用下面的命令来添加error proto
mangokit add error api/errors/v1/proto errorReason.proto同样的在api/errors/v1/proto目录下生成了errorReason.proto文件
syntax proto3;package errorReason.v1;import errors/errors.proto;option go_package api/errors/v1;v1;enum ErrorReason {option (errors.default_code) 500;Placeholder 0 [(errors.code) 0];}除了添加proto文件还可以添加预制的makefile和Dockerfile
3.3 代码生成 根据proto生成代码
# 根据api目录下的proto文件生成go代码
mangokit generate proto api根据wire依赖注入生成代码
mangokit generate wire生成openapi文档
mangokit generate openapi生成上面所有的三个项目
mangokit generate all