mangokit:golang web项目管理工具,使用proto定义http路由和错误

文章目录

    • 前言
    • 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:"data"`Codee    int    `json:"code"`Messagee 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开发

github:https://github.com/mangohow/mangokit

 

1、mangokit介绍

mangokit是一个web项目的管理工具,它的功能如下:

  1. 根据预设的项目结构创建出一个web项目,使用已有的代码框架,减少工作量
  2. 使用proto来定义http路由以及错误码,使用相关工具生成代码,完成自动结构体反序列化以及返回值响应
  3. 使用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 ("context""net/http""github.com/mangohow/mangokit/serialize""github.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 ("fmt""github.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/wire""mangokit_test/internal/conf""mangokit_test/internal/dao""mangokit_test/internal/server""mangokit_test/internal/service""github.com/mangohow/mangokit/transport/httpwrapper""github.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/conf""mangokit_test/internal/dao""mangokit_test/internal/server""mangokit_test/internal/service""github.com/mangohow/mangokit/transport/httpwrapper""github.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 wire

 

2、mangokit实现

mangokit主要包含三个组件:

  • protoc-gen-go-gin
  • protoc-gen-go-error
  • mangokit

protoc-gen-go-gin用于根据proto文件中定义的service来生成gin框架的路由代码

protoc-gen-go-error用于根据proto文件中定义的enum来生成相应的响应错误码

mangokit中则设置了多种指令用于管理项目,比如:

  1. 使用create命令来生成一个初始项目结构
  2. 使用add命令来添加proto文件、makefile或Dockerfile
  3. 使用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 ("flag""fmt""google.golang.org/protobuf/compiler/protogen""google.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命令行工具开发,包含以下功能:

  1. 创建基础项目:根据预设的项目目录结构和代码生成
  2. 添加文件:包括api proto、error proto、makefile和Dockerfile
  3. 生成代码:包括go代码生成、wire生成和openapi生成

在这里插入图片描述

 

3、使用示例

3.1 创建新项目

在这里插入图片描述

首先使用mangokit来创建一个项目,项目目录为mangokit-test,go 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
  • api:api目录用来放置proto文件以及根据proto文件生成的go代码,通常将.proto文件放在proto文件夹下,而生成的代码放在它的上一级目录,这样看起来更清晰一些
  • cmd:cmd目录存放了wire注入代码和main文件
  • configs:configs目录用来放置程序的配置文件
  • internal:internal用来存放本项目依赖的代码,不会暴露给其它的项目,其中包括middleware(中间件)model(数据库结构体模型)、dao(数据库访问对象)、conf(配置信息代码)server(服务初始化代码)service(service的具体实现代码)
  • pkg:用来存放一些共用代码
  • test:存放测试代码
  • third_party:其中包含一些使用到的proto的扩展文件

在创建项目时默认会从github拉取一个预制的项目结构,如果遇到网络问题导致无法拉取,则可以使用-r命令来指定其它的仓库,比如使用gitee:

mangokit create -r https://gitee.com/mangohow/mangokit-template mangokit-test mangokit_test

3.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/222296.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Week-P2】CNN彩色图片分类-CIFAR10数据集

文章目录 一、环境配置二、准备数据三、搭建网络结构四、开始训练五、查看训练结果六、总结3.1 ⭐ torch.nn.Conv2d()详解3.2 ⭐ torch.nn.Linear()详解3.3 ⭐torch.nn.MaxPool2d()详解3.4 ⭐ 关于卷积层、池化层的计算4.2.1 optimizer.zero_grad()说明4.2.2 loss.backward()说…

重生奇迹MU剑士所有装备

1、普通装备:剑、盾、头盔、披风、手套、鞋子等。 2、精良装备:精良剑、精良盾、精良头盔、精良披风、精良手套、精良鞋子等。 3、稀有装备:稀有剑、稀有盾、稀有头盔、稀有披风、稀有手套、稀有鞋子等。 4、传说装备:传说剑、传…

数据安全扫描仪荣膺网络安全优秀创新成果大赛优胜奖 - 凸显多重优势

近日,由中国网络安全产业联盟(CCIA)主办、CCI数据安全工作委员会中国电子技术标准化研究院等单位承办的“2023年网络安全优秀创新成果大赛”获奖名单公布。天空卫士数据安全扫描仪(DSS)产品获得创新成果大赛优胜奖。 本…

图像处理—小波变换

小波变换 一维小波变换 因为存在 L 2 ( R ) V j 0 ⊕ W j 0 ⊕ W j 0 1 ⊕ ⋯ L^{2}(\boldsymbol{R})V_{j_{0}}\oplus W_{j_{0}}\oplus W_{j_{0}1}\oplus\cdots L2(R)Vj0​​⊕Wj0​​⊕Wj0​1​⊕⋯,所以存在 f ( x ) f(x) f(x)可以在子空间 V j 0 V_{j_0} Vj0…

Nginx快速入门:nginx各类转发、代理配置详解|location、proxy_pass参数详解(五)

0. 引言 咱们上节讲解了nginx的负载均衡配置,但是还有很多其他的转发情况,包括不同路径转发至不同的业务服务,通配符识别路径转发等。 今天一起来学习nginx的转发配置 1. location模块的匹配模式 首先我们要了解nginx进行转发代理的核心在…

c++学习笔记-提高篇-STL标准模板库3(stack容器、queue容器以及list容器)

目录 Stack容器 一、Stack容器介绍 二、stack常用接口 三、栈的示例 queue(队列)容器 一、queue容器介绍 二、queue常用接口 三、queue示例 list容器 一、list容器介绍 二、list常用接口及示例 (一)list构造函数 &am…

使用 Taro 开发鸿蒙原生应用 —— 探秘适配鸿蒙 ArkTS 的工作原理

背景 在上一篇文章中,我们已经了解到华为即将发布的鸿蒙操作系统纯血版本——鸿蒙 Next,以及各个互联网厂商开展鸿蒙应用开发的消息。其中,Taro作为一个重要的前端开发框架,也积极适配鸿蒙的新一代语言框架 —— ArkTS。 本文将…

html5 实现网页截屏 页面生成图片

效果 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"referrer" content"no-referrer" /><meta name"viewport"content"widthdevice-width,intial-sc…

Python整数常用的方法汇总与Python3 File(文件) 方法

Python整数常用的方法汇总 python&#xff13;基础之整数常用的方法整理 希望对大家学习或者使用python3能具有一定的参考价值。 __abs__ #返回一个数的绝对值 __add__ #两数相加 __and__ #两数按位与操作 __bool__ …

11种方法判断​软件的安全可靠性​

软件的安全可靠性是衡量软件好坏的一个重要标准&#xff0c;安全性指与防止对程序及数据的非授权的故意或意外访问的能力有关的软件属性&#xff0c;可靠性指与在规定的一段时间和条件下&#xff0c;软件 软件的安全可靠性是衡量软件好坏的一个重要标准&#xff0c;安全性指与防…

智能优化算法应用:基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜜獾算法4.实验参数设定5.算法结果6.参考文献7.MA…

centos(linux)安装jenkins

官网&#xff1a;https://pkg.jenkins.io/redhat/ jdk版本要和jenkins对上&#xff01; 安装官网进行操作&#xff1a; sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.reposudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io-…

华为Harmony——ArkTs语言

文章目录 一、简单示例二、声明式UI描述创建组件无参有参数 配置属性配置事件配置子组件 三、自定义组件基本用法基本结构成员函数/变量 一、简单示例 我们以一个具体的示例来说明ArkTS的基本组成。如下图所示&#xff0c;当开发者点击按钮时&#xff0c;文本内容从“Hello Wo…

关于“Python”的核心知识点整理大全35

目录 13.3.4 重构 create_fleet() game_functions.py 13.3.5 添加行 game_functions.py alien_invasion.py 13.4 让外星人群移动 13.4.1 向右移动外星人 settings.py alien.py alien_invasion.py game_functions.py 13.4.2 创建表示外星人移动方向的设置 13.4.3 检…

转义字符使用详解【C语言】

目录 转义字符的概念 转义字符表 转义字符详解 和 实际使用示例 一、\a 二、\b 三、\f 四、\n 五、\r 六、\t 七、\v 八、\\ 九、\ 十、\" 十一、\? 十二、\0 十三、\ddd 十四、\xhh 总结—— 转义字符的概念 所有的 ASCII码都可以用“\加数字” 来表示…

【C语言】动态内存管理基础知识——动态通讯录,如何实现通讯录容量的动态化

引言 动态内存管理的函数有&#xff1a;malloc,calloc,ralloc,free,本文讲解动态内存函数和使用&#xff0c;如何进行动态内存管理,实现通讯录联系人容量的动态化&#xff0c;对常见动态内存错误进行总结。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》…

C语言之输入输出和字符(2)

目录 缓冲和重定向 ▇缓冲 ▇重定向 字符 转义字符 \和\"……字符和字符" 字符串字面量的写法 字符常量的写法 八进制转义字符和十六进制转义字符 字符编码 在看本节之前&#xff0c;请先看下上一章&#xff0c;做到更好地衔接。https://blog.csdn.net/W061…

基于vue-cli快速发布vue npm 包

一、编写组件 1. 初始化项目并运行 vue create vue-digital-countnpm run serve2. 组件封装 新建package文件夹 ​ 因为我们可能会封装多个组件&#xff0c;所以在src下面新建一个package文件夹用来存放所有需要上传的组件。 ​ 当然&#xff0c;如果只有一个组件&#xff…

【科普知识】什么是步进驱动器、驱动模式及驱动的方法?

我们已经知道步进电机就是一种将电脉冲信号转化为角位移的执行机构。那么如何精准地控制电机的速度和位置呢&#xff1f;这就需要一种驱动器的装置。 1.什么是步进驱动器&#xff1f; 电机控制原理图 步进驱动器是一种能使步进电机运转的功率放大器&#xff0c;能把控制器发来…

Linux(二)常用命令

文章目录 一、文件管理命令1.1 chmod1.2 chown1.3 cat1.4 cp1.5 find1.6 head1.7 tail1.8 less1.9 more1.10 mv1.11 rm1.12 touch1.13 vim1.14 >和>>1.15 scp1.16 ln1.17 怎么用命令查看日志 二、文档管理命令2.1 grep2.2 wc2.3 echo 三、磁盘管理命令3.1 cd3.2 df3.3…