【protoc自定义插件】「go语言」实现rpc的服务映射成http的服务,protoc生成gin的插件,(详解实现原理及过程)


前言

目的:proto映射成gin,把rpc的服务映射成http的服务

使用 proto 文件的实践技巧:

  1. 将 proto 文件作为项目的 API 定义:将所有的 RPC 接口和消息结构定义在 proto 文件中,并将其作为项目的 API 文档。这样可以使得代码更加清晰易懂,并且可以方便地自动生成文档。

  2. 使用 proto3 语法:proto3 是 proto 文件的最新版本,它的语法更加简洁,易于理解和维护。与 proto2 相比,proto3 删除了一些不必要的特性,减少了重复代码,并且更加安全。

  3. 采用一致的命名规范:在 proto 文件中定义的消息类型、字段名称和 RPC 接口名称应该采用一致的命名规范,以便于代码的阅读和维护。

  4. 使用 well-known types:proto 文件提供了许多 well-known types,这些类型已经在很多开源库和框架中得到了广泛的使用,例如 timestamp、duration、empty 等。在需要使用这些类型的场景下,应该优先选择它们,以减少代码的复杂度。

  5. 生成代码:在 Go 语言中,可以使用 protoc 工具来生成序列化和反序列化代码、RPC 客户端和服务端代码等。可以通过设置 protoc 的插件来生成不同类型的代码,例如 grpc-go 插件可以生成 gRPC 相关的代码。

  6. 使用版本控制:proto 文件是代码的一部分,应该与代码一同纳入版本控制系统中,并且应该使用标准的代码审查流程来确保代码的质量和可维护性。

  7. 使用测试:在编写 proto 文件时,应该编写相应的单元测试和集成测试,以确保 proto 文件的正确性和一致性。可以使用 Prototest 等测试框架来编写测试用例。


一、工程实践中如何更好的使用proto文件?

Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具:https://go-kratos.dev/

  1. proto文件可以用作http和rpc服务的生成标注写法
    我写了一个gin的服务,我还要手动去维护api文档,手动去yapi上维护 后期维护和迭代很简单, 改了任何代码你都可以直接生成api
    可以直接将proto生成swagger文件,然后一键导入到yapi上,这样就可以直接在yapi上查看api文档了
  2. 在kratos中对proto的依赖更加重, 可以用来定义一些错误码, 并生成go源码直接使用
  3. kratos甚至将配置文件都给你映射成proto文件
    业内很多框架都开始逐步接受将proto文件作为核心的标准去写一系列插件去自动生成代码
    proto validate

go-zero更溜,goctl,保姆式的框架 api文件 go-zero和kratos的一套设计理念


二、protoc命令如何查询依赖的proto文件以及执行原理

1. protoc命令如何查询依赖的proto文件

中文官方文档:https://go-kratos.dev/docs/component/api

安装方式按照官方即可

官方示例proto:

syntax = "proto3";

package helloworld.v1;

import "google/api/annotations.proto";

option go_package = "github.com/go-kratos/service-layout/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "dev.kratos.api.helloworld.v1";
option java_outer_classname = "HelloWorldProtoV1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply)  {
    option (google.api.http) = {
        // 定义一个 GET 接口,并且把 name 映射到 HelloRequest
        get: "/helloworld/{name}",
        // 可以添加附加接口
        additional_bindings {
            // 定义一个 POST 接口,并且把 body 映射到 HelloRequest
            post: "/v1/greeter/say_hello",
            body: "*",
        }
    };
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

这是一个使用 Protocol Buffer 版本 3 语法编写的文件。它定义了一个名为 “helloworld.v1” 的包,并导入了 “google/api/annotations.proto” 文件。

文件中还定义了一个服务 (service) “Greeter” ,它包含一个方法 (method) “SayHello” ,这个方法接收一个类型为 “HelloRequest” 的参数,并返回一个类型为 “HelloReply” 的响应。这个方法还使用了 “google.api.http” 注释来定义 HTTP 接口。具体地,它定义了一个 GET 接口,将路径中的 “name” 映射到 “HelloRequest”“name” 字段,并且还可以使用 POST 接口和附加绑定(additional bindings)。

“HelloRequest” 是一个请求消息,包含一个名为 “name” 的字符串字段。

“HelloReply” 是一个响应消息,包含一个名为 “message” 的字符串字段。

google/api/annotations.proto这个import是go-kratos就有的 可以直接从库里拷贝过来用
可以用Everything查询到直接拷贝过来用
尽量使用第三方引用,而不是引用线上的
拷贝下来的third_party可以将第三方源码放到里面 也可以放一些其他的 这是一种定义方式 可灵活运用

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

GoLand中如果需要protobuf插件识别到这是一个第三方引用就得设置protobuf的识别路径:
在这里插入图片描述

然后通过grpc插件生成go源码:

确定protobuf版本为最新版

cd到proto目录下

protoc --proto_path=../third_party --proto_path=. --go_out=. --go-grpc_out=. api.proto

--proto_path:protoc命令会查找所需要import的文件
--go_out:生成go的源码在哪
--go-grpc_out:生成go的grpc源码在哪
最后指明输入的是什么

其实 --go-grpc_out和一些其他的命令都是通过插件运行的 并不是protoc本身自带的,后面详解

注意: 不要把goland里proto插件和--proto_path对应起来 这是完全不一样的运行方式

2. protoc执行的插件加载原理是什么?

在使用 protoc 工具生成代码时,可以通过指定插件来生成不同类型的代码,例如生成 gRPC 相关的代码需要使用 grpc-go 插件。插件的加载是通过 protoc 的插件机制实现的,具体原理如下:

  1. protoc 工具会在执行时检查命令行参数中是否指定了插件。如果指定了插件,工具会将插件路径添加到环境变量 PATH 或者 PLUGIN_PATH 中。

  2. protoc 在执行时会扫描输入的 .proto 文件,并根据文件中定义的语法和语义信息生成一个中间表示,也就是 AST(抽象语法树)。

  3. protoc 会将生成的 AST 通过 proto 文件中指定的插件进行处理。这些插件实际上是由 protoc 调用的独立可执行文件,这些插件需要实现 protoc 的插件规范,并按照约定的方式接收和处理 AST 数据。

  4. 插件会将 AST 转换成目标语言的代码,并输出到指定的目录中。

  5. protoc 工具会根据插件生成的代码和用户指定的选项生成目标代码文件,例如 Go 语言中的 .go 文件。

protoc 执行过程 会从标注输入 读取到你的参数 回去查询 protoc-gen-{NAME} go_out 会去找 protoc-gen-go.exe

总之,插件机制可以使 protoc 工具灵活地扩展,以生成更多类型的代码或者执行更多的任务。插件必须按照 protoc 的插件规范实现,并且可以独立编写和发布。

3. proto文件中的package和go_package的作用

在 protobuf 中,package 和 go_package 是两个不同的概念,其作用分别如下:

  1. package:在 proto 文件中,package 是指定当前文件的命名空间,用于避免不同文件中的命名冲突。它的作用类似于 Go 语言中的 package 关键字。
    例如:一个 proto 文件的 package 声明为 example.foo,那么该文件中定义的所有消息、服务和枚举类型都将在命名空间 example.foo 下。

  2. go_package:go_package 是在 proto 文件中指定生成 Go 代码的包名和路径,它的作用是将生成的 Go 代码放在指定的包路径下。
    例如:如果一个 proto 文件中的 go_package 声明为 example.com/foo,那么生成的 Go 代码将会放在 example.com/foo 包下。

需要注意的是,go_package 的值应该是一个完整的包名,它包含了生成的代码的包路径和包名。通常情况下,go_package 的值应该和项目中的实际包路径保持一致,这样可以方便地导入和使用生成的代码。

在开发中,不建议发生冲突,应该使用合理的目录来避免这种错误

总之,package 和 go_package 在 proto 文件中都扮演了重要的角色,它们的正确使用可以使得生成的代码更加清晰易懂,并且方便代码的组织和维护。


三、protoc插件开发原理

开发 protoc 插件的原理如下:

  1. 插件是一个独立的可执行文件,它需要按照 protoc 的插件规范实现,这些规范包括:
    • 插件需要实现标准输入和标准输出,用于与 protoc 工具进行通信。
    • 插件需要读取 protoc 工具传递的输入 AST(抽象语法树),并将处理结果输出到标准输出中。
    • 插件需要指定插件的类型和插件的名字,可以在 proto 文件中通过 option 来指定。
  1. 插件开发者需要选择一种编程语言来实现插件。通常情况下,开发者可以选择自己熟悉的编程语言,例如 Go、Python、Java 等。但是,不同编程语言的实现方式会有所不同。

  2. 插件开发者需要了解 protoc 工具的使用方式和命令行参数,以及 proto 文件的语法和语义。在开发过程中,可以通过 protoc 工具和 -I 参数指定 proto 文件所在的路径。

  3. 开发者需要根据 proto 文件中定义的消息、服务和枚举类型,设计生成代码的逻辑和生成文件的格式。在生成代码时,需要遵循目标语言的规范和最佳实践,以生成高质量的代码。

总之,插件开发者需要了解插件规范和 protoc 工具的使用方式,以及目标语言的规范和最佳实践,以开发高效、可靠的插件,并生成高质量的代码。开发好的插件可以使得 protoc 工具更加灵活和强大,以适应不同的需求。

体验流程

官方示例:https://github.com/go-kratos/kratos/tree/main/cmd/protoc-gen-go-errors

作用:它可以在生成 Go 代码时,自动根据 proto 文件中的错误定义生成对应的错误类型。

目的是体验流程,调试,不是为了运行它

源码执行流程:

package main

import (
	"flag"

	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/types/pluginpb"
)

func main() {
	//接受输入的proto
	flag.Parse()
	var flags flag.FlagSet
	protogen.Options{
		ParamFunc: flags.Set,
	}.Run(func(gen *protogen.Plugin) error {
		//运行之后gen会拿到proto文件 可以是多个
		gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
		//它可以接受多个proto文件进行反射,反射功能它已经做好了
		//详细的struct可以自己点进去看
		//实际我们开发过程中,它帮我们生成代码,我们直接拿着用就行
		for _, f := range gen.Files {
			if !f.Generate {
				continue
			}
			//逻辑
			//generateFile(gen, f)
		}
		return nil
	})
}

它的大致原理是通过template.go模板语法,经过其他逻辑填充好,进行生成代码输出就行了
自己可以去template.go看一下,生成好的代码大致就是内样

官方只是做了说明,并没有说如何去调试它,如果用goland进行调试,得改源码

自己开发插件时执行过程是protoc来启动你的,并不是单独运行的,所以说你输入的 其实是protoc输入的 但是protoc是一个c文件,没办法去控制这个c文件进行调试

接着看执行逻辑,点进Run里后再点进run看源码:

func run(opts Options, f func(*Plugin) error) error {
	if len(os.Args) > 1 {
		return fmt.Errorf("unknown argument %q (this program should be run by protoc, not directly)", os.Args[1])
	}
	in, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return err
	}
	req := &pluginpb.CodeGeneratorRequest{}
	if err := proto.Unmarshal(in, req); err != nil {
		return err
	}
	gen, err := opts.New(req)
	if err != nil {
		return err
	}
	if err := f(gen); err != nil {
		// Errors from the plugin function are reported by setting the
		// error field in the CodeGeneratorResponse.
		//
		// In contrast, errors that indicate a problem in protoc
		// itself (unparsable input, I/O errors, etc.) are reported
		// to stderr.
		gen.Error(err)
	}
	resp := gen.Response()
	out, err := proto.Marshal(resp)
	if err != nil {
		return err
	}
	if _, err := os.Stdout.Write(out); err != nil {
		return err
	}
	return nil
}

os.Stdin是标准输入流做的,是protoc提供的输入流,[]byte,不需要知道他是哪来的
然后通过Unmarshal进行反解成&pluginpb.CodeGeneratorRequest{}

那我们想要控制这个输入流,让他指定我们需要的这个输入流呢?

这就得改源码 修改这个输入流了 这块理解到这里就大差不差了

改源码逻辑后续我会更新


四、gin转发到grpc服务的原理和实现

1. 自己写.pb.go体验其原理

目的:proto映射成gin,把rpc的服务映射成http的服务

首先写一个rpc的服务,我想用gin集成进来,如何写, 然后再通过protoc来生成

效果:

  1. 直接开发rpc服务
  2. 我可以一键将rpc服务转换成http服务
  3. 有开发插件的能力,可以在插件里加一定的业务逻辑
  4. 这个插件一改,就会自动生成所需要的文件,更新迭代也很快,一个命令就可以

hello world级别的rpc服务:

项目目录:

  • api
    • api.proto
    • gin_grpc.pb.go
  • gin_grpc
    • app
      • app.go
    • server.go

api.proto:

syntax = "proto3";

//这段后续再讲解
//go:generate protoc -I. --go_out=. --go-grpc_out=.  hello.proto

package template;

import "google/api/annotations.proto";

option go_package = "./;v1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option(google.api.http) = {
      post:"/v1/sayhello"
      body:"*"
    };
  }
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {
    option(google.api.http) = {
      post:"/v1/sayhelloagain"
      body:"*"
    };
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

生成grpc和模板所需文件
protoc -I. --go_out=. --go-grpc_out=. api.proto

gin_grpc.pb.go:

package v1

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 类似于grpc的写法 这个名称尽量和proto保持有关系
type Greeter struct {
	server GreeterServer
	router gin.IRouter
}

// New进行外部调用 手动注册
func NewGreeterHttpServer(server GreeterServer, router gin.IRouter) *Greeter {
	return &Greeter{server: server, router: router}
}

// 实例化这个过程 自动注册
func RegisterGreeterHttpServer(server GreeterServer, router gin.IRouter) {
	//我现在想用gin. Default,如果开发中我想使用其他的方式实例化gin 把权力交给外部
	g := &Greeter{server: server, router: router}
	g.RegisterService()
}

// 然后"生成"这个SayHello 这个_0是为了防止冲突
func (g *Greeter) SayHello_0(c *gin.Context) {
	//入参定义
	var in HelloRequest
	//入参
	if err := c.BindJSON(&in); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	//调用生成grpc的方法   尽量松耦合
	//在struct定义这个接口保持松耦合  这样就能通过SayHello转一次
	out, err := g.server.SayHello(c, &in)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, out)
}

// 注入路径当中
func (g *Greeter) RegisterService() {
	//路由映射
	g.router.Handle("POST", "/v1/greeter/register", g.SayHello_0)
}

server.go:

package gin_grpc

import (
	"context"
	"fmt"

	hpb "NewGo/api"
)

type helloServer struct {
	hpb.UnimplementedGreeterServer
}

func NewHelloServer() *helloServer {
	return &helloServer{}
}

func (h *helloServer) SayHello(ctx context.Context, request *hpb.HelloRequest) (*hpb.HelloReply, error) {
	return &hpb.HelloReply{
		Message: fmt.Sprintf("Hello %s", request.Name),
	}, nil
}

func (h *helloServer) SayHelloAgain(ctx context.Context, request *hpb.HelloRequest) (*hpb.HelloReply, error) {
	return &hpb.HelloReply{
		Message: fmt.Sprintf("Hello %s again", request.Name),
	}, nil
}
//注册到grpc中   这段代码没啥说的
var _ hpb.GreeterServer = &helloServer{}

app.go:

package main

import (
	hpb "NewGo/api"
	"NewGo/gin_grpc"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	helloSrv := gin_grpc.NewHelloServer()
	engine := gin.Default()
	//把注册函数和gin绑定起来
	hpb.RegisterGreeterHttpServer(helloSrv, engine)
	//http服务   使用gin启动也行   ,尽量把gin 扔到http里做 主要是优雅退出会方便一点
	server := &http.Server{
		Addr:    ":8082",
		Handler: engine,
	}
	//支持自动生成端口以及自定义ip和端口
	_ = engine.SetTrustedProxies(nil)
	//启动   可严谨判断
	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		panic(err)
	}
}

启动后访问一下:

整个项目中只有.pb.go是自动生成的 我们是为了体验才自己写的 还有一个好处,就是可以自动生成Swagger文档,这样就不需要自己写api文档了

注意是post加json发送
在这里插入图片描述
这样就理解了rpc转http的执行原理是什么了
为后续开发自定义插件做铺垫

2. 细节纠错

  • 文件命名的规范:应该是由开发者决定的,自己去写死这个 也没问题,自己决定比较好点
  • proto中自动注册所需要的import:安装"google.golang.org/genproto/googleapis/api/annotations"即可 不要冲突注册过程 尽量别破坏插件中原有的逻辑

五、go的template实现动态生成代码

学习template语法文章:https://colobu.com/2019/11/05/Golang-Templates-Cheatsheet/#Range

学过django或者flask里Jinjia2模板会入门很快的

proto里importpackagego_package不需要自己填充 proto gen会帮我们做这些

重点是把service填充好

示例:

package main

import (
	"bytes"
	"fmt"
	"html/template"
	"strings"
)

var tpl = `
type {{$.Name}}HTTPServer struct {
	server {{$.Name}}Server
	router gin.IRouter
}

// 实例化这个过程 自动注册
func Register{{$.Name}}HttpServer(server {{$.Name}}Server, router gin.IRouter) {
	//我现在想用gin. Default,如果开发中我想使用其他的方式实例化gin 把权力交给外部
	g := &{{$.Name}}HTTPServer{server: server, router: router}
	g.RegisterService()
}
{{ range .Methods }}
// 然后"生成"这个SayHello 这个_0是为了防止冲突
func (g *{{$.Name}}HTTPServer) {{ .HandlerName }}(c *gin.Context) {
	//入参定义
	var in {{ .Request }}
	//入参
	if err := c.BindJSON(&in); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	//调用生成grpc的方法   尽量松耦合
	//在struct定义这个接口保持松耦合  这样就能通过SayHello转一次
	out, err := g.server.{{ .Name }}(c, &in)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, out)
}
{{ end }}
// 注入路径当中
func (g *{{$.Name}}HTTPServer) RegisterService() {
	//路由映射
{{ range .Methods }}
	g.router.Handle("{{ .Method }}", "{{ .Path }}", g.{{ .HandlerName }})
{{ end }}
}

`

type serviceDesc struct {
	Name    string
	Methods []method
}
type method struct {
	Name    string
	Request string
	Reply   string
	//http rule
	Path   string
	Method string //指的是post还是get等
	Body   string
}

func (m *method) HandlerName() string {
	return m.Name + "_0"
}
func main() {
	//模板
	//缓冲区
	buf := new(bytes.Buffer)
	tmpl, err := template.New("http").Parse(strings.TrimSpace(tpl))
	if err != nil {
		panic(err)
	}
	//模仿
	s := serviceDesc{
		Name: "Greeter",
		Methods: []method{
			{
				Name:    "SayHello",
				Request: "HelloRequest",
				Reply:   "HelloReply",
				Path:    "/v1/sayhello",
				Method:  "POST",
				Body:    "*",
			},
		},
	}
	//把内容输出到buf里面
	err = tmpl.Execute(buf, s)
	if err != nil {
		return
	}
	fmt.Println(buf.String())
}

六、protoc生成gin的插件

插件:
链接:https://pan.baidu.com/s/1pEJ8xxo81FGJJoV2rQ2Y6A?pwd=1234
提取码:1234

通过大量的前置工作 这里面插件的代码应该能看懂
注释写的很清楚直接go build就可以 没问题的

示例:

目录结构:

  • tools
    • generator(下载的插件)
    • google(third_party里的google文件夹)
    • api.proto
    • main.go

api.proto:

syntax = "proto3";

//go:generate protoc -I. --go_out=. --go-grpc_out=.  hello.proto

package template;

import "google/api/annotations.proto";

option go_package="./;v1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v1/sayhello"
      body: "*"
    };
  }
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v1/sayhelloagain"
      body: "*"
    };
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

main.go:

package main

import (
	"flag"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/types/pluginpb"

	"NewGo/tools/generator"
)

func main() {
	flag.Parse()
	var flags flag.FlagSet
	protogen.Options{
		ParamFunc: flags.Set,
	}.Run(func(gen *protogen.Plugin) error {
		gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
		for _, f := range gen.Files {
			if !f.Generate {
				continue
			}
			generator.GenerateFile(gen, f)
		}
		return nil
	})
}

说明一下: 如果想在main里调试的话 到generator里的generator.go文件把这段注释打开
如果是用protoc生成的话把这段注释加上

func GenerateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile {
	if len(file.Services) == 0 {
		return nil
	}

	//设置生成的文件名,文件名会被protoc使用,生成的文件会被放在相应的目录下
	filename := file.GeneratedFilenamePrefix + "_gin.pb.go"
	g := gen.NewGeneratedFile(filename, file.GoImportPath)

	//该注释会被go的ide识别到, 表示该文件是自动生成的,尽量不要修改
	g.P("// Code generated by protoc-gen-gin. DO NOT EDIT.")
	g.P()
	//会提取到proto中option go_package 然后写入
	g.P("package ", file.GoPackageName)

	//该函数是注册全局的packge 的内容,但是此时不会写入
	//g.Content()之后才能看到真正写入的内容 注册即可
	g.QualifiedGoIdent(ginPkg.Ident(""))
	g.QualifiedGoIdent(httpPkg.Ident(""))

	for _, service := range file.Services {
		genService(file, g, service)
	}

	//自己写文件看结果
	//f, err := os.Create("api_gin.pb.go")
	//
	//if err != nil {
	//	//log.Fatal(err)
	//}
	//
	//defer f.Close()
	//
	//contentStr, _ := g.Content()
	//_, _ = f.WriteString(string(contentStr))

	return g
}

然后直接到tools目录里运行go build
把生成后的exe文件重命名为protoc-gen-gin.exe
放到go目录下的bin文件夹:

在这里插入图片描述
这时候使用protoc命令就可以使用这个插件对proto进行生成gin源码了

示例:

api.proto:

syntax = "proto3";

//go:generate protoc -I. --go_out=. --go-grpc_out=.  hello.proto

package template;

import "google/api/annotations.proto";

option go_package = "./;v1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option(google.api.http) = {
      post:"/v1/sayhello"
      body:"*"
    };
  }
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {
    option(google.api.http) = {
      post:"/v1/sayhelloagain"
      body:"*"
    };
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

打开终端进入这个目录运行:

protoc --proto_path=. --proto_path=../third_party --go_out=. --go-grpc_out=. --gin_out=. api.proto
使用了--proto_path就不需要-I.了   自己指明比较好

这里使用到的--proto_path=../third_party是因为proto文件里指明的第三方文件 不是插件里指明的
在这里插入图片描述

然后写一个服务端:server.go和启动文件:app.go进行测试

server.go:

package gin_grpc

import (
	"context"
	"fmt"

	hpb "NewGo/api"
)

type helloServer struct {
	hpb.UnimplementedGreeterServer
}

func NewHelloServer() *helloServer {
	return &helloServer{}
}

func (h *helloServer) SayHello(ctx context.Context, request *hpb.HelloRequest) (*hpb.HelloReply, error) {
	return &hpb.HelloReply{
		Message: fmt.Sprintf("Hello %s", request.Name),
	}, nil
}

func (h *helloServer) SayHelloAgain(ctx context.Context, request *hpb.HelloRequest) (*hpb.HelloReply, error) {
	return &hpb.HelloReply{
		Message: fmt.Sprintf("Hello %s again", request.Name),
	}, nil
}

var _ hpb.GreeterServer = &helloServer{}

app.go:

package main

import (
	hpb "NewGo/api"
	"NewGo/gin_grpc"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	helloSrv := gin_grpc.NewHelloServer()
	engine := gin.Default()
	//把注册函数和gin绑定起来
	hpb.RegisterGreeterServerHTTPServer(helloSrv, engine)
	//http服务   使用gin启动也行   ,尽量把gin 扔到http里做 主要是优雅退出会方便一点
	server := &http.Server{
		Addr:    ":8082",
		Handler: engine,
	}
	//支持自动生成端口以及自定义ip和端口
	_ = engine.SetTrustedProxies(nil)
	//启动   可严谨判断
	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		panic(err)
	}
}

直接运行app.go
进行post访问:
加json进行请求

在这里插入图片描述

在这里插入图片描述

插件是完全自定义的,如果公司有一套标准的话可以嵌入到插件里自动生成 就不用每次进行手动写了,公司应该都有一套标准,自己写很麻烦,尽量用模板进行生成,还可以加入【链路追踪】【熔断降级、限流】等功能。
我连实习都没开始,我是废物其他无权评价

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用protoc.exe将.proto文件编译go语言的源代码文件的实现原理如下: 1. 解析.proto文件 首先,protoc.exe会读取.proto文件,并解析其中定义的消息类型、服务等信息。具体来说,它会使用protobuf编译器的解析器解析.proto文件,将其中的语法结构转化为内部的语法树。 2. 生成AST 接着,protoc.exe会根据.proto文件解析出来的语法树,生成一个AST(抽象语法树)。这个AST包含了.proto文件中定义的所有消息类型、服务等信息,以及它们的字段名、类型等详细信息。 3. 代码生成 有了AST之后,protoc.exe就可以根据AST生成go语言的源代码文件。具体来说,它会使用内置的代码生成器,根据AST生成对应的go语言代码。生成的代码包括: - 消息类型的定义:一个消息类型对应一个go语言的结构体,其中包含了定义的所有字段。 - 序列化和反序列化代码:生成了用于将消息类型序列化字节数组、以及将字节数组反序列化消息类型的代码。 - 服务类型的定义:一个服务类型对应一个go语言的接口类型,其中包含了定义的所有方法。 4. 编译 最后,生成go语言代码需要通过go编译器进行编译,生成实际的可执行文件。 下面是一个使用protoc.exe将.proto文件编译go语言的源代码文件的示例代码: ``` protoc --go_out=. example.proto ``` 这个命令会将example.proto文件编译go语言的源代码文件,并将生成的代码文件存放在当前目录下的example.pb.go文件中。其中,`--go_out`参数表示生成go语言代码,后面的`.`表示生成的代码文件存放在当前目录。 总之,使用protoc.exe将.proto文件编译go语言的源代码文件的原理是通过解析.proto文件生成AST,然后根据AST生成对应的go语言代码,最后通过编译器进行编译。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jzin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值