一、环境配置
-
2、使用命令
go env
查看自己的go path
地址并且在go path
的目录下创建一个bin
文件夹 -
3、将第一点下载下来的东西解压出来将
bin
下面的protoc
拷贝到go path
下的bin
文件夹下 -
4、查看安装是否成功
# 查看版本 protoc --version # 查看常见命令 protoc
目前只支持这几种语言
-
5、配置支持
go
语言,依赖包的地址go get -u github.com/golang/protobuf/proto go get -u github.com/golang/protobuf/protoc-gen-go
二、测试probuf
转go
代码
-
1、书写简单的
proto
文件// helloWorld.proto // 版本,直接固定写3就好了 syntax = "proto3"; // 转换为go的包名,前面的.表示在当前目录下输出 option go_package = ".;profile"; message HelloWorld { string name = 1; int64 age = 2; double fee = 3; bool isLogin = 4; }
-
2、运行命令将
proto
文件转go
文件# protoc -I=.[当前目录] --go_out=plugins=grpc:.[当前目录] 文件名 protoc -I=. --go_out=plugins=grpc:. helloWorld.proto
三、测试
-
1、直接输出
package main import ( "fmt" "go_demo/01.protobuf/proto" ) func main() { hello := proto.HelloWorld{ Name: "张三", Age: 14, Fee: 20.9, IsLogin: false, } fmt.Println(&hello) }
四、其它的数据类型
-
1、复合数据类型,其实就是数据的嵌套
.... message Location { double latitude = 1; double longitude = 2; } message HelloWorld { string name = 1; // 注意这个地方与位置无关,只与后面的数字有关系 Location location = 5; int64 age = 2; double fee = 3; bool isLogin = 4; }
-
2、列表数据类型
message Location { double latitude = 1; double longitude = 2; } message HelloWorld { string name = 1; Location location = 5; int64 age = 2; double fee = 3; bool isLogin = 4; repeated Location locationList = 6; }
-
3、枚举类型
// 定义枚举类型 enum HelloStatus { NO_PAY = 0; PAY = 1; } message HelloWorld { string name = 1; Location location = 5; int64 age = 2; double fee = 3; bool isLogin = 4; repeated Location locationList = 6; HelloStatus status = 7; }
-
4、赋值操作
func main() { var hello = proto.HelloWorld{ Name: "张三", Age: 14, Fee: 20.9, IsLogin: false, Location: &proto.Location{ Latitude: 30, Longitude: 100, }, LocationList: []*proto.Location{ { Latitude: 100, Longitude: 10, }, }, Status: profile.HelloStatus_NO_PAY, } fmt.Println(&hello) }
五、其它的输出
-
1、使用
proto.Marshal
输出二进制文件var hell1 profile.HelloWord err = proto.Unmarshal(b, &hell1) fmt.Println(&hello)
-
2、输出
json
var hell1 profile.HelloWord b, err = json.Marshal(&hell1) fmt.Printf("%s\n", b)
六、实现客户端和服务器端数据交互
-
1、定义一个
proto
文件// 版本,直接固定写3就好了 syntax = "proto3"; // 转换为go的包名,前面的.表示在当前目录下输出 option go_package = ".;profile"; message Location { double latitude = 1; double longitude = 2; } message HelloWorld { string name = 1; Location location = 5; int64 age = 2; double fee = 3; bool isLogin = 4; repeated Location locationList = 6; } message GetHelloRequest { string id = 1; } message GetHelloResponse { string id = 1; HelloWorld helloWorld = 2; } // 这个服务是必须定义的 service HelloService { rpc GetHello(GetHelloRequest) returns (GetHelloResponse); }
-
2、运行命令生成
go
的文件,并且查看自己定义的service
func (c *helloServiceClient) GetHello(ctx context.Context, in *GetHelloRequest, opts ...grpc.CallOption) (*GetHelloResponse, error) { out := new(GetHelloResponse) err := c.cc.Invoke(ctx, "/HelloService/GetHello", in, out, opts...) if err != nil { return nil, err } return out, nil }
-
3、定义服务器端的方法
type Service struct { } func (*Service) GetHello(ctx context.Context, req *profile.GetHelloRequest) (*profile.GetHelloResponse, error) { return &profile.GetHelloResponse{ Id: req.Id, HelloWorld: &profile.HelloWorld{ Name: "张三", Age: 20, Fee: 100, IsLogin: true, Location: &profile.Location{ Longitude: 100, Latitude: 10, }, LocationList: []*profile.Location{ { Longitude: 10, Latitude: 100, }, }, }, }, nil }
-
4、启动服务器端的方法
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("服务器启动失败:%v", err) } // 启动服务 service :=grpc.NewServer() // Service是上面自定义的 profile.RegisterHelloServiceServer(service, &Service{}) log.Fatal(service.Serve(listen)) }
-
5、客户端的启动
package main import ( "context" "fmt" "go_demo/01.protobuf/profile" "google.golang.org/grpc" "log" ) func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { log.Fatalf("连接服务器端错误: %v", err) } client := profile.NewHelloServiceClient(conn) response, err := client.GetHello(context.Background(), &profile.GetHelloRequest{ Id: "1001", }) if err != nil { log.Fatalf("连接服务错误:%v", err) } fmt.Println(response) }
七、将protobuf
转换为http
请求
-
1、改造之前的
proto
文件syntax = "proto3"; // 添加这个 package collect; option go_package = ".;profile";
-
2、在同
proto
目录下创建一个yaml
文件# helloWorld.yaml文件 type: google.api.Service config_version: 3 http: rules: - selector: collect.HelloService.GetHello get: /hello/{id}
-
3、安装依赖包
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
-
4、修改运行命令
# 和之前的命令是一样的 protoc -I=. --go_out=plugins=grpc,paths=source_relative:. helloWorld.proto # 会生成一个helloWorld.pb.gw.go的文件 protoc -I=. --grpc-gateway_out=paths=source_relative,grpc_api_configuration=helloWorld.yaml:. helloWorld.proto
-
5、由于命令太多,不方便记住,在目录下创建一个
gen.sh
的文件,将两个命令放进去,需要运行的时候执行sh ./gen.sh
-
6、
grpc
的服务层和上面的一样的 -
7、定义
http
客户端package main import ( "context" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "go_demo/01.protobuf/profile" "google.golang.org/grpc" "log" "net/http" ) func main() { c := context.Background() c, cancel := context.WithCancel(c) defer cancel() mux := runtime.NewServeMux() err := profile.RegisterHelloServiceHandlerFromEndpoint( c, mux, "localhost:9000", // grpc服务器端地址 []grpc.DialOption{grpc.WithInsecure()}, ) if err != nil { log.Fatalf("连接grpc错误:%v", err) } err = http.ListenAndServe(":8080", mux) if err != nil { log.Fatalf("监听错误:%v", err) } }
-
8、直接在浏览器上输入
http://localhost:8080/hello/123
八、处理后端返回前端的数据结构
上面面临着两个问题
- 1、年龄字段我们本来定义的是整数,返回给前端的是字符类型
- 2、状态字段我们应该返回枚举类型的值,不应该是字符串的
解决办法
-
1、年龄字段通过修改数据类型将之前的
int64
改为现在的int32
数据类型 -
2、状态枚举类型通过
http
客户端的方式来修改... var mux = runtime.NewServeMux( runtime.WithMarshalerOption( runtime.MIMEWildcard, &runtime.JSONPb{ MarshalOptions: protojson.MarshalOptions{ UseEnumNumbers: true, // 枚举字段的值使用数字 UseProtoNames: true, }, UnmarshalOptions: protojson.UnmarshalOptions{ DiscardUnknown: true, // 忽略 client 发送的不存在的 poroto 字段 }, }, ), ) ...