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      RespValue
	Status 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.proto

package v1

import (
	"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.proto

package v1

import (
	"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 wireinject

package main

import (
	"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 !wireinject

package main

import (
	"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 main

import (
	"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.yaml

32 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

精通服务器远程管理:全面指南

引言 在当今数字化世界中,IT专业人员和管理员能够远程管理服务器的能力是无价之宝。远程服务器管理不仅提高了效率,而且在无法物理访问服务器的情况下确保了持续的运营。本指南将深入探讨远程管理的不同类型、远程桌面的使用方法,以及如何安全…

Diary26-Vue综合案例1-书籍购物车

Vue综合案例1-书籍购物车 案例要求: 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpor…

阿里推荐 LongAdder ,不推荐 AtomicLong !

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、CAS 1.1 CAS 全称 1.2 通俗理解CAS 1.3 CAS的问题 1.4 解决 ABA 问题 二、LongAdder 2.1 什么是 LongAdder 2.2 为什么推…

flutter自定义地图Marker完美展示图片

世人都说雪景美 寒风冻脚无人疼 只道是一身正气 结论 参考Flutter集成高德地图并添加自定义Maker先实现自定义Marker。如果自定义Marker中用到了图片&#xff0c;那么会碰到图片没有被绘制到Marker的问题&#xff0c;此时需要通过precacheImage来预加载图片&#xff0c;从而解…

Jenkins 构建环境指南

目录 Delete workspace before build starts&#xff08;常用&#xff09; Use secret text(s) or file(s) &#xff08;常用&#xff09; Add timestamps to the Console Output &#xff08;常用&#xff09; Inspect build log for published build scans Terminate a …

【分享】如何给Excel加密?码住这三种方法!

想要给Excel文件进行加密&#xff0c;方法有很多&#xff0c;今天分享三种Excel加密方法给大家。 打开密码 设置了打开密码的excel文件&#xff0c;打开文件就会提示输入密码才能打开excel文件&#xff0c;只有输入了正确的密码才能打开并且编辑文件&#xff0c;如果密码错误…

2023年度佳作:AIGC、AGI、GhatGPT 与人工智能大模型的创新与前景展望

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 写在前面参与规则 ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论…

Matlab-修改默认启动路径

Matlab-修改默认启动路径 第一:找到MATLAB的安装路径 第二步&#xff1a;进入到…\toolbox\local下&#xff0c;找到matlabrc.m 第三部&#xff1a;编辑matlabrc.m&#xff0c;在文本最后一行加入启动文件路径

ubuntu qt 源码编译

官方源码下载地址 : 源码地址 选择要下载的版本 dmg结尾的是MacOS系统里使用的Qt库&#xff0c;qt-everywhere-opensource-src-4.7.0是Qt源码包&#xff0c;有zip和tar.gz两个压缩格式的&#xff0c;两个内容是一样的&#xff0c;只是zip一般在Windows下比较流行&#xff0c;…

uniapp运行到手机模拟器

第一步&#xff0c;下载MUMU模拟器 下载地址&#xff1a;MuMu模拟器官网_安卓12模拟器_网易手游模拟器 (163.com) 第二步&#xff0c;运行mumu模拟器 第三步&#xff0c;运行mumu多开器 第三步&#xff0c;查看abs 端口 第四步&#xff0c;打开HBuilder,如下图&#xff0c;将…

Flink cdc3.0同步实例(动态变更表结构、分库分表同步)

文章目录 前言准备flink环境docker构建mysql、doris环境数据准备 通过 FlinkCDC cli 提交任务整库同步同步变更路由变更路由表结构不一致无法同步 结尾 前言 最近Flink CDC 3.0发布&#xff0c; 不仅提供基础的数据同步能力。schema 变更自动同步、整库同步、分库分表等增强功…

软考学习五部曲

视频学知识 学习知识环节看视频看书都可以&#xff0c;书很厚一本。如果要看完的话要很多时间&#xff0c;所以我觉得还是看视频更快一点&#xff0c;而且视频还可以倍速。我看的那个视频我觉得非常不错&#xff0c;但是我看的视频b站已经下架了看不到了。其他的视频没仔细去看…

数据库原理及应用·数据库系统结构

2.1 数据模型的概念 2.1.1 什么是数据模型 数据模型&#xff08;Data Model&#xff09; 是对现实世界数据特征的模拟和抽象&#xff0c;用来描述数据是如何组织、存储和操作的。 数据模型应满足如下三个条件&#xff1a; 能比较真实地模拟现实世界 容易为人所理解 便于在计…

MyBatis的ORM映射

目录 什么是ORM 一&#xff0c;列的别名 二&#xff0c;结果映射 三&#xff0c;总结 什么是ORM ORM&#xff1a;对象关系映射&#xff08;Object Relational Mapping&#xff0c;简称ORM&#xff09;模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简…

图片抠图怎么抠?教你这几个方法抠图方法

图片抠图怎么抠&#xff1f;在数字化时代&#xff0c;图片抠图已经成为日常工作中不可或缺的一项任务。通过对图片的抠图处理&#xff0c;我们可以将图片中的某个元素提取出来&#xff0c;或者将图片背景更换&#xff0c;达到更好的视觉效果。那么图片抠图怎么抠&#xff1f;下…

基于ssm校园交友网站设计与实现(源码齐全可用)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题&#xff0c;今天给大家介绍…

YOLOv5改进 | 卷积篇 | 通过RFAConv重塑空间注意力(深度学习的前沿突破)

一、本文介绍 本文给大家带来的改进机制是RFAConv&#xff0c;全称为Receptive-Field Attention Convolution&#xff0c;是一种全新的空间注意力机制。与传统的空间注意力方法相比&#xff0c;RFAConv能够更有效地处理图像中的细节和复杂模式(适用于所有的检测对象都有一定的…

Kafka--Kafka日志索引详解以及生产常见问题分析与总结

一、Kafka的Log日志梳理 ​ 这一部分数据主要包含当前Broker节点的消息数据(在Kafka中称为Log日志)。这是一部分无状态的数据&#xff0c;也就是说每个Kafka的Broker节点都是以相同的逻辑运行。这种无状态的服务设计让Kafka集群能够比较容易的进行水平扩展。比如你需要用一个新…

猫目标检测数据集VOC+YOLO格式11000张

猫是一种非常受欢迎的宠物&#xff0c;它们有着柔软的毛发、敏捷的身体和灵活的尾巴。猫是一种非常独立的动物&#xff0c;也是一种非常聪明和好奇的动物。 猫是一种肉食性动物&#xff0c;主要以小型哺乳动物、鸟类和昆虫为食。它们通常在夜间活动&#xff0c;利用敏锐的听觉…

CodeBlocks配置WinLibs

一、准备工作 1、去Code::Blocks - Browse /Binaries/Nightlies at SourceForge.net下载CodeBlocks最新的nightly build版本&#xff0c;并下载wxWidget dll和Mingw64 dll库文件。 我下载的CB 13411 &#xff0c;Mingw64dlls13.1.0.7z&#xff0c;wxmsw32u_gcc_cb_wx324_2D_g…