go从0到1项目实战体系六十九:GRPC实例

说明:

①. 模拟一个商品service、一个订单service.

②. 客户端可以是gRpc这种测试程序,也可以是一些web框架来连接:
   a. 如:gin,都可以直接来连服务端.
   b. 也可以是直接用网关来做.

③. 有些系统是不支持GRPC的,也不想改造,应该提供api的方式给它调用.

1. 客户端代码:

1.1 引入其它的proto文件:

(1). 订单相关的proto文件:
grpcserver\pbfiles\Models.proto:

syntax="proto3";
package services;
import "google/protobuf/timestamp.proto";
import "validate.proto";
// 商品模型实体
message OrderModel {
    int32 order_id = 1;
    string order_no = 2;
    float order_price = 3 [(validate.rules).float.gt = 1];
	google.protobuf.Timestamp order_time = 4;
	repeated OrderDetail order_details = 5;      // 嵌套模型下面的子模型
}
// 子订单模型
message OrderDetail{
    int32 detail_id=1;
    string order_no=2;
    int32 prod_id=3;
    float prod_price=4;
    int32 prod_num=5;
}:. GOPATH\pkg\mod\github.com\golang\protobuf@v1.3.2\ptypes\timestamp\timestamp.proto
②. 使用第三方库进行字段验证:
   a. 可以提交到gin web框架中验证,验证完之后,直接调用grpc方法.
   b. web框架可以在struct中写各种规则,但是model.pb.go是自动生成出来.
   c. validate在生成部分就写上验证,写起来比较麻烦,但是好处在于可以生成不同语言的验证器.
   d. github:https://github.com/envoyproxy/protoc-gen-validate
   e. 安装:
	  go get -u github.com/envoyproxy/protoc-gen-validate
	  在GOPATH\bin\protoc-gen-validate.exe会多一个这样的exe文件,编译的时候会调用这个插件.. 拷贝下图validate.proto到grpc\pbfiles目录中.其实就是为了方便import "validate.proto".

(2). 订单相关proto文件:

syntax="proto3";
package services;
// 引入其它的proto文件
import "Models.proto";
// 请求时,是以orderModel的字段做为json参数来提交,需要把body部分进行映射成orderModel.
message OrderRequest {
  OrderModel order_model = 1;
}
message OrderResponse {
  int32 status = 1;
  string message = 2;
}
service OrderService {
  rpc NewOrder(OrderModel) returns (OrderResponse) {};
  rpc AddOrder(OrderRequest) returns (OrderResponse) {
	option (google.api.http) = {
		post: "/v1/addorder"
		// 这里的order_model要跟上面的order_model是绑定的,因为类型是OrderModel实体,提交参数的时候,就可以通过OrderModel这个实体,编译的实体获取得到.
		body: "order_model"
	};
  };
}:. 全部写在一个proto文件,不好维护:
   a. 商品的详情、商品的订单信息不能放到一个proto文件.
   b. 拆分专门放实体proto文件,如商品订单、商品分类、商品优惠券...

(3). 商品相关proto文件:
grpcserver\pbfiles\Prod.proto:

syntax = "proto3";
package services;
// 使用枚举获取分区商品库存
enum ProdAreas {
  A=0;
  B=1;
  C=2;
}
// 实现的业务:传入一个商品ID,获取一个商品库存.
message  ProdRequest {
  int32 prod_id = 1;
}
message ProdResponse {
  int32 prod_stock = 1;
}
// 实现的业务:获取多少条商品库存.
message QuerySize {
  int32 size = 1;            // 每页多少条
}
message ProdListResponse {
  // 这里同样不能写ProdResponse[]
  // ProdResponse有点类似类名,prodres有点类似变量名.
  repeated ProdResponse prodres = 1;
}
// 创建一个service
service ProdService {
	// 获取一条商品库存的方法
    rpc GetProdStock (ProdRequest) returns (ProdResponse) {
		rpc GetProdStock (ProdRequest) returns (ProdResponse) {
        option (google.api.http) = {
            get: "/v1/prod/{prod_id}"
        };
	};
	// 获取多条商品的库存
	rpc GetProdStocks (QuerySize) returns (ProdListResponse) {}
}:. grpc是跨平台的,不能在Contracts方法返回值中写[]ProdResponse.
   a. 这是go的语法,不能写在proto文件里面.. Repeated语法修饰符:
   a. 返回字段可以重复任意多次(包括0).
   b. 可以认为就是一个PHP中的数组或go的切片.. ProdListResponse就是一个切片,里面包含了ProdResponse
   type ProdListResponse struct {
	 Prodres  []*ProdResponse `protobuf:"bytes,1,rep...`
	 ...
   }. 商品一般库存有区域之分:
   a. 如A区有10个、B区有12个、C区有20.
   b. 加入枚举类型,可以让用户选择到底是A区域、还是B区域.

(4). 增加中间文件生成go文件批处理:
grpcserver\gen.bat:

cd pbfiles && protoc --go_out=plugins=grpc:../services  Prod.proto
protoc --go_out=plugins=grpc:../services --validate_out=lang=go:../services Models.proto
protoc --go_out=plugins=grpc:../services  Order.proto
protoc --grpc-gateway_out=logtostderr=true:../services Prod.proto
protoc --grpc-gateway_out=logtostderr=true:../services Order.proto
cd ../

注:
①. 生成的验证文件grpc\services\Models.pb.validate.go.

1.2 接口实现类:

(1). 商品接口实现类:
grpcserver\services\ProdService.go:

package services
// 具体的实现类
import (
	"context"
)
type ProdService struct {
}
// 获取一个商品的一个库存方法
func(this *ProdService) GetProdStock(ctx context.Context, request *ProdRequest) (*ProdResponse, error) {
	var stock int32 = 100
	fmt.Println(request.ProdArea)           // A
	fmt.Println(request.GetProdArea())      // A,打印的值是一样的
	if (request.ProdArea == ProdAreas_A) {
		stock = 10
	}
	return &ProdResponse{ProdStock:stock}, nil
}
// 获取多少条商品库存方法
func(s *ProdService) GetProdStocks(ctx context.Context, request *QuerySize) (*ProdListResponse, error) {
	stock := []*ProdResponse{
		&ProdResponse{ProdStock: 20},
		&ProdResponse{ProdStock: 22},
	}
	return &ProdListResponse{Prodres: stock}, nil
}:. 没有实现接口的话,server.go中的以下代码会报错:
   services.RegisterProdServiceServer(rpcServer, new(services.ProdService))

(2). 订单接口实现类:
grpcserver\services\OrderService.go:

package services
import (
	"context"
	"fmt"
)
type OrderService struct {
}
func(this *OrderService) NewOrder(cxt context.Context, request *OrderModel) (*OrderResponse, error) {
	fmt.Println(request)
	// order_id:1 order_no:"DD123456" order_price:12.3 order_time:<seconds:1576819779 >
	return &OrderResponse{Status: 200, Message: "success"}, nil
}
func(this *OrderService) AddOrder(cxt context.Context, request *OrderRequest) (*OrderResponse, error) {
	fmt.Println(request)
	// OrderPrice必须大于1,否则报错:invalid OrderModel.OrderPrice: value must be greater than 1
	err := request.OrderModel.Validate()
	if err != nil {
		return &OrderResponse{Status: 400, Message: err.Error()},nil
	}
	return &OrderResponse{Status: 200, Message: "success"},nil
}

1.3 服务server.go代码:

(1). 封装公共证书函数:
grpcserver\helper\CertHelper.go:

package helper
import (
	"crypto/tls"
	"crypto/x509"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
)
// 获取服务端证书配置
func GetServerCreds() credentials.TransportCredentials {
	cert, _ := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
	certPool := x509.NewCertPool()
	ca, _ := ioutil.ReadFile("cert/ca.pem")
	certPool.AppendCertsFromPEM(ca)
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.VerifyClientCertIfGiven,
		ClientCAs:    certPool,
	})
	return creds
}
// 获取客户端证书配置
func  GetClientCreds() credentials.TransportCredentials {
	cert, _ := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key")
	certPool := x509.NewCertPool()
	ca, _ := ioutil.ReadFile("cert/ca.pem")
	certPool.AppendCertsFromPEM(ca)
	creds:=credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ServerName: "localhost",
		RootCAs:      certPool,
	})
	return creds
}

(2). 服务server.go代码:
grpcserver\service.go:

package main
import (
	"fmt"
	"google.golang.org/grpc"
	"grpc.com/helper"
	"grpc.com/services"
	"net"
)
func main()  {
	//创建gRPC服务
	rpcServer:=grpc.NewServer(grpc.Creds(helper.GetServerCreds()))

	// 商品服务注册
	services.RegisterProdServiceServer(rpcServer, new(services.ProdService))
	// 订单服务注册
	services.RegisterOrderServiceServer(rpcServer, new(services.OrderService))

	lis, _ := net.Listen("tcp",":8081")
	err = rpcServer.Serve(lis)
}

1.4 gateway代码:

package main
import (
	"context"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"google.golang.org/grpc"
	"grpc.com/helper"
	"grpc.com/services"
	"log"
	"net/http"
)
func main () {
	gwmux := runtime.NewServeMux()
	opt := []grpc.DialOption{grpc.WithTransportCredentials(helper.GetClientCreds())}
	grpcEndpoint := "localhost:8081"
	err := services.RegisterProdServiceHandlerFromEndpoint(context.Background(), gwmux, grpcEndpoint, opt)
	err1 := services.RegisterOrderServiceHandlerFromEndpoint(context.Background(), gwmux, grpcEndpoint, opt)
	if err != nil || err1 != nil {
		log.Fatal(err)
	}
	httpServer := &http.Server{
		Addr: ":8080",
		Handler:gwmux,
	}
	httpServer.ListenAndServe()
}

2. 客户端代码:

(1). 复制中间文件生成好的go文件到客户端:

grpcserver\services\Prod.pb.go
grpcserver\services\Order.pb.go
grpcserver\services\Models.pb.go

(2). 客户端调用gRpc代码:

package main
import (
	"client.com/services"
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	// 这个包来帮助创建类型,不是go内置的类型,是需要第三方包
	"github.com/golang/protobuf/ptypes/timestamp"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
)
func main() {
	cert, _ := tls.LoadX509KeyPair("cert/client.pem","cert/client.key")
	certPool := x509.NewCertPool()
	ca, _ := ioutil.ReadFile("cert/ca.pem")
	certPool.AppendCertsFromPEM(ca)
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ServerName: "localhost",
		RootCAs:      certPool,
	})
	client,err := grpc.Dial(":8081", grpc.WithTransportCredentials(creds))
	if err != nil{
		log.Fatal(err)
	}
	defer client.Close()

	// 创建prod服务的客户端
	prodClient := services.NewProdServiceClient(client)

	// 接口一:获取一个商品一个库存
	res, _ := prodClient.GetProdStock(
		context.Background(),
		// 如果不写第二个参数,默认就是0,返回的值是10
		&services.ProdRequest{ProdId:12, ProdArea:services.ProdAreas_B},
	)
	fmt.Println(res)                           // 100

	// 接口二:获取多个商品的库存
	res, _ := prodClient.GetProdStocks(
		context.Background(),
		&services.QuerySize{Size: 10},
	)
	fmt.Println(res.Prodres)                   // [prod_stock:20  prod_stock:22 ]
	fmt.Println(res.Prodres[0].ProdStock)      // 20

	// 创建order服务的客户端
	orderClient := services.NewOrderServiceClient(client)
	// 调用gRPC接口
	t := timestamp.Timestamp{Seconds: time.Now().Unix()}
	res, _ := orderClient.NewOrder(
		context.Background(),
		&services.OrderModel{OrderId:1, OrderNo: "DD123456", OrderPrice: 12.30, OrderTime: &t,},
	)
	fmt.Println(res)                           // status:200 message:"success"
}

3. gateway http api请求:

POST:http://localhost:8080/v1/addorder
{
	"order_no":"20190809",
	"order_id":"1152",
	"order_details":[
		{"order_no":"20190809","prod_id":"101","prod_price":"20.0","prod_num":"3"}
	]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值