安装 proto

$cd /usr/local 
$sudo wget https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip
$sudo mkdir protoc
$cd protoc
$sudo unzip ../protoc-3.20.1-linux-x86_64.zip
$cd bin
$sudo cp protoc /xxx #复制到$GOPATH/bin目录下
$protoc --version

安装 protoc-gen-go

我是在 deepin 的linux系统里安装的,go 项目管理使用了 go mod。

按网络上找的教程

go get github.com/golang/protobuf/protoc-gen-go

执行上述命令就会在 $GOPATH/bin 目录下生成 protoc-gen-go 可执行文件。
我按上述操作一直无法生成 protoc-gen-go 可执行文件,只是把 github.com/golang/protobuf/protoc-gen-go 及所需的包 下载到了 $GOPATH/pkg/mod 目录下了。
后面一直百度查询与尝试,成功在 $GOPATH/bin 目录下生成 protoc-gen-go 可执行文件

  1. go get google.golang.org/protobuf/cmd/protoc-gen-go
  2. go install google.golang.org/protobuf/cmd/protoc-gen-go
    上述两个命令 是在项目下执行的(项目下有 go.mod 文件)

安装 protoc-gen-micro

go get github.com/micro/micro/v2/cmd/protoc-gen-micro

go install github.com/micro/micro/v2/cmd/protoc-gen-micro

上述操作直接在 $GOPATH/bin 目录生成了 protoc-gen-micro 可执行文件,生成的 protoc-gen-micro 是v2版本的。
上述两个命令 是在项目下执行的(项目下有 go.mod 文件)

生成 v3 版本的

go install github.com/asim/go-micro/cmd/protoc-gen-micro/v3@latest

将 .proto 文件转换成 store.pb.go 文件

新增文件 store.proto ,在 目录 frame.service 下

syntax = "proto3";
package store.srv;

service Store{
    rpc GetStoreDetail(StoreIdRequest) returns(StoreInfoResponse);
}

// 请求参数
message StoreIdRequest {
    string id = 1;
}

// 返回参数
message StoreInfoResponse{
    string id = 1;
}
}

protoc --proto_path=. --go_out=. store.proto

执行上述命令,报错:

protoc-gen-go: unable to determine Go import path for "store.proto"

Please specify either:
        • a "go_package" option in the .proto source file, or
        • a "M" argument on the command line.

See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

--go_out: protoc-gen-go: Plugin failed with status code 1.

解决方式:
在 student.proto 文件内加上一行:

option go_package = “./proto”;

文件最终内容如下:

syntax = "proto3"; // 指定版本
package store.srv; // 指定包名
option go_package = "./proto";  // 指定生成位置

service Store{
    rpc GetStoreDetail(StoreIdRequest) returns(StoreInfoResponse);
}

// 请求参数
message StoreIdRequest {
    string id = 1;
}

// 返回参数
message StoreInfoResponse{
    string id = 1;
}

protoc --proto_path=. --go_out=. store.proto 命令解析

protoc 命令详解

$ protoc
用法: protoc [OPTION] PROTO_FILES
解析proto文件并根据给定的选项生成输出:
  -IPATH, --proto_path=PATH   指定搜索目录,可多次指定,默认为当前工作目录。
  --version                   显示版本信息并退出
  -h, --help                  显示帮助文档并退出
  --encode=MESSAGE_TYPE       从标准输入读取给定类型的文本格式消息,从标准输出写入二进制文件。消息类型必须在原始文件或导入中定义。
  --decode=MESSAGE_TYPE       从标准输入中读取给定类型的二进制消息,向标准输出中写入文本格式。消息类型必须定义在proto文件或其导入的文件中。
  --decode_raw                从标准输入读取任意协议消息,向标准输出写入原始标记或文本格式的值。
  --descriptor_set_in=FILES   指定文件分隔符列表,每个都包含了一个文件描述符集合。
  -oFILE,                     写入FileDescriptorSet
  --include_imports           当使用--descriptor_set_out时, 同时包含输入文件的依赖项
  --include_source_info       当使用--descriptor_set_out时无需剥离FileDescriptorProto中的SourceCodeInfo
  --dependency_out=FILE       指定依赖输出文件
  --error_format=FORMAT       设置打印错误格式,默认gcc,可选msvs。
  --print_free_field_numbers  打印给定proto文件中消息定义的可用字段号
  --plugin=EXECUTABLE         指定使用插件的可执行文件
  --cpp_out=OUT_DIR           产生C++头文件和源文件
  --csharp_out=OUT_DIR        产生C#源文件
  --java_out=OUT_DIR          产生Java源文件
  --javanano_out=OUT_DIR      产生Java Nano源文件
  --js_out=OUT_DIR            产生JavaScript源文件
  --objc_out=OUT_DIR          产生Objective C头文件和源文件
  --php_out=OUT_DIR           产生PHP源文件
  --python_out=OUT_DIR        产生Python源文件
  --ruby_out=OUT_DIR          产生Ruby源文件
  @<filename>                 从文件中读取选项和文件名

–proto_path=PATH 指定搜索目录,可多次指定,默认为当前工作目录。
–go_out=. 产生GO源文件目录,默认为当前工作目录。
store.proto 转换执行的 proto 文件

生成 store.pb.micro.go 文件

protoc --go_out=. --micro_out=. store.proto

规则与上面生成 store.pb.go 一样。上述命令是 同时生成 store.pb.go,store.pb.micro.go 两个文件

安装 consul 服务发现软件

这个可以直接使用 docker 安装

docker pull consul

运行

docker run -itd --name consul -p 8500:8500 consul:latest

查询日志

docker logs 4e90f273e754

暴露 8500 端口,方便本地查看服务信息
127.0.0.1:8500 / 0.0.0.0:8500 访问,可以查看 consul 的服务信息

正式开启一个微服务项目

上面几步操作完成后 可以生成 store.pb.go,store.pb.micro.go 两个文件。
新建 store 微服务的 实现文件方法:store.service.go

package proto

import (
	"context"
	"fmt"
)

type StoreServiceDemo struct {}

func (s *StoreServiceDemo) GetStoreDetail(context.Context, *StoreIdRequest, *StoreInfoResponse) error {
	fmt.Println("GetStoreDetail")
	rep.Id = req.Id
	rep.StoreName = "StoreName"
	rep.Bindphone = "Bindphone"
	rep.Email = "Email"
	return nil
}

这里的 结构体可以随意命名,但结构体必须实现 store.proto 文件里的 GetStoreDetail 微服务方法。

之后就是写启动微服务的代码了,如下

package main

import (
	"fmt"
	"frame.service/proto"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/registry"
	"github.com/micro/go-plugins/registry/consul/v2"
)

func main() {
	//连接 consul
	ConsulClient := consul.NewRegistry(registry.Addrs("127.0.0.1:8500"))

	service := micro.NewService(
		micro.Name("go.micro.srv.test"),
		micro.Version("latest"),
		micro.Registry(ConsulClient),		// 服务注册到 consul
	)

	// Register Handler
	_ = proto.RegisterStoreHandler(service.Server(), new(proto.StoreServiceDemo))

	_ = service.Run()
}

上述代码分几步

  1. 连接 consul
  2. 创建 micro 服务:设定服务名称,版本,服务注册位置(这里是把服务注册到连接的 consul)
  3. 注册服务 Store
  4. 启动微服务

之后就可以在 consul 127.0.0.1:8500 上看到新注册的微服务了
在这里插入图片描述

微服务访问

这里我是用 gin 访问成功,新建目录 api,api 下生成 proto 的 store.pb.go 和 store.pb.micro.go 两个文件。代码如下

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

	storeService "frame.service/api/proto"

	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/registry"
	"github.com/micro/go-plugins/registry/consul/v2"

	"github.com/gin-gonic/gin"
)

func main() {
	// gin 路由
	router := gin.Default()

	router.GET("/", func(c *gin.Context) {
		// consul 
		consulReg := consul.NewRegistry(func(options *registry.Options) {
			options.Addrs = []string{"127.0.0.1:8500"}
		})
		service := micro.NewService(
			micro.Registry(consulReg),
		)
		
		// 连接 go.micro.srv.test 微服务
		mc := storeService.NewStoreService("go.micro.srv.test", service.Client())

		// 请求 go.micro.srv.test 微服务 的 GetStoreDetail 方法
		resp, err := mc.GetStoreDetail(context.TODO(), &storeService.StoreIdRequest{Id: "123465"})
		if err != nil {
			log.Println(err.Error())
			c.String(http.StatusBadRequest, "hello")
			return
		}

		// 输出结果
		respStr, _ := json.Marshal(resp)
		c.String(http.StatusOK, string(respStr))

	})

	// 启动路由
	router.Run(":8848")
}

gin 访问结果如下:
在这里插入图片描述