租辆酷车小程序开发(二)—— 接入微服务GRPC

发布于:2024-11-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

vscode中golang的配置

  1. 设置依赖管理
    go env -w GO111MODULE=on
    go env -w GOPROXY=https://goproxy.cn,direct

GO111MODULE=auto 在$GOPATH/src 外面且根目录有go.mod 文件时,开启模块支持
GO111MODULE=off 无模块支持,go会从GOPATH 和 vendor 文件夹寻找包
GO111MODULE=on 模块支持,go会忽略GOPATH和vendor文件夹,只根据go.mod下载依赖

  1. 安装go插件
    在这里插入图片描述

  2. view —— Command Palatte —— 选择Go: Install/Update Tools 安装所有工具

  3. 在coolcar目录下新建server目录,使用File-Add Folder to Workspace添加server目录到vscode

这里不能直接打开coolcar目录,因为go的插件与项目根目录有关,所以我们在这里分别将wx和server目录添加到我们的workspace
在这里插入图片描述

  1. file —— preference —— setting
    在这里插入图片描述
  2. server目录下输入go mod init coolcar 新建go文件测试能否正常运行
    三种执行方法:
    Run —— Run Without Debugging
    命令行—— go run hello.go
    Code Runner插件
    在这里插入图片描述
    断点调试:
    添加断点 —— Run —— Start Debugging

GRPC

gRPC 是Google公司开发的一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。

RPC(Remote Procedure Call)远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,简单的理解是一个节点请求另一个节点提供的服务。RPC只是一套协议,基于这套协议规范来实现的框架都可以称为 RPC 框架,比较典型的有 Dubbo、Thrift 和 gRPC。

向服务器发送请求需要关注的问题:

  • 协议:http/http2/TCP
  • 服务器地址:api.coolcar.cn
  • 路径:/trip
  • 参数
  • 数据类型
  • 数据编码:JSON/XML/Protobuf
  • 安全性:token
  • 错误处理:http status

GRPC:

  • 协议:HTTP/2
  • 方法:POST
  • 路径:服务器地址/Service/Method(Ps:地址/TripService/GetTrip)
  • 参数:body
  • 安全性:HTTP2协议/header中存放token
  • 数据:二进制
  • 数据结构:二进制数据使用ProtoBuf进行编码

优点:

  • 高效的数据传输
  • 语言无关的领域模型定义

其他DSL/IDL:

  • Thrift
  • Swagger
    • 使用yaml描述
    • 描述REST API
  • Goa

ProtoBuf编译器的安装

  1. 安装protobuf编译器
    https://github.com/protocolbuffers/protobuf/releases
    下载解压后,将bin目录添加到环境变量,在终端执行protoc命令可以看到对应输出

  2. 安装go语言插件,(本项目使用的grpc gateway是v1版本,目前grpc gateway已经有较大的更新),这三个插件安装在GOPATH的bin目录下
    go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@latest
    go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@latest
    go install github.com/golang/protobuf/protoc-gen-go@latest
    在这里插入图片描述
    protoc-gen-go:生成go语言代码
    protoc-gen-grpc-gateway:生成grpc-gateway代码
    protoc-gen-swagger:从protoc生成swagger‘

ProtoBuf的使用

.proto文件

vscode中安装vscode-proto3插件
server目录下新建proto目录,在proto目录下新建trip.proto文件
proto文件只是定义了一种传输的数据格式,不包含方法

// 语法
syntax = "proto3"; 
// proto文件的package,用处不大
package coolcar;

option go_package="coolcar/proto/gen/go;trippb";

// 定义数据格式(不存在方法只是定义一种数据结构)
message Trip {
    // (二进制数据流不知道字段在哪里开始在哪里结束所以要在变量后声明字段顺序,与json不同的是json中:后面跟的是对应的数据)
    string start = 1; // 第一个字段
    string end = 2; // 第二个字段
    int64 duration_sec = 3; // 注明单位
    int64 fee_cent = 4;
}

option go_package="coolcar/proto/gen/go;trippb";:定义了引用的格式,生成的go代码的package为trippb,导入go代码时使用import trippb "coolcar/proto/gen/go"导入

生成go语言代码

protoc -I =. --go_out=paths=source_relative:gen/go trip.proto

  • -I=. :输入目录为当前目录
  • --go_out=paths=source_relative:gen/go
    • go_out:生成go语言代码
    • paths=source_relative:相对路径,xx=xx是送给go_out插件的参数
    • gen/go:生成go语言的路径
  • trip.proto:源文件

生成文件如下,这里的package为.proto文件中go_package所定义
在这里插入图片描述

使用生成的代码

导入使用该生成的文件,路径在.proto文件的option go_package="coolcar/proto/gen/go;trippb中定义
在这里插入图片描述
fmt.Println(&trip):输出要取地址,否则会有警告,这里建议在使用proto时一律取地址
b, err := proto.Marshal(&trip):编码为二进制流
err = proto.Unmarshal(b, &trip2):将二进制流解码

服务器将Marshal的二进制数据流通过grpc服务的tcp端口发给客户端,客户端收到后Unmarshal就可以获得原始数据

b, err = json.Marshal(&trip2):编码为json格式方便交互(在trip.pb.go中已经定义了对应的json字段)

微服务之间通过grpc传递二进制流互相通信,暴露给前端小程序使用json进行通信

在这里插入图片描述

package main

import (
	trippb "coolcar/proto/gen/go"
	"encoding/json"
	"fmt"

	"google.golang.org/protobuf/proto"
)

func main() {
	trip := trippb.Trip{
		Start:       "abc",
		End:         "def",
		DurationSec: 3600,
		FeeCent:     10000,
	}
	fmt.Println(&trip)

	// 二进制流编码
	b, err := proto.Marshal(&trip)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%X\n", b)

	// 二进制流解码
	var trip2 trippb.Trip
	// 需要加地址才能写入
	err = proto.Unmarshal(b, &trip2)
	if err != nil {
		panic(err)
	}
	// 凡是引用一律加地址
	fmt.Println(&trip2)

	// 转json
	b, err = json.Marshal(&trip2)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", b)
}

proto的数据类型

Trigger Suggest 快捷键可以查看可选类型

复合类型

message Location {
    double latiture = 1;
    double longitude = 2;
}
message Trip {
    string start = 1;
    Location start_pos = 5;
    string end = 2;
    Location end_pos = 6;
    int64 duration_sec = 3;
    int64 fee_cent = 4;
}

### repeate

repeated类型

定义go语言中的切片
repeated Location path_locations = 7; => PathLocations []*Location

message Location{
    double latitude = 1;
    double longitude =2;
}
message Trip {
    string start = 1; 
    Location start_pos = 5; //新加字段序号往后排
    repeated Location path_locations = 7;
    string end = 2; 
    Location end_pos = 6;
    int64 duration_sec = 3; 
    int64 fee_cent = 4;
}

生成的go语言代码如下:
在这里插入图片描述
使用方法:
在这里插入图片描述

枚举类型

message Location{
    double latitude = 1;
    double longitude =2;
}
enum TripStatus{
    TS_NOT_SPECIFIED = 0;
    NOT_STARTED = 1;
    IN_PROGRESS = 2;
    FINISHED = 3;
    PAID = 4;
}
message Trip {
    string start = 1; 
    Location start_pos = 5; //新加字段序号往后排
    repeated Location path_locations = 7;
    string end = 2; 
    Location end_pos = 6;
    int64 duration_sec = 3; 
    int64 fee_cent = 4;
    TripStatus status = 8;
}

生成的go语言代码
在这里插入图片描述
使用方法:
在这里插入图片描述

ProtoBuf字段的可选性

问题:软件更新后新老版本的message字段不同,新老版本同时存在,如何处理新老版本之间的数据传输

假设新版本新加了TripStatus字段,此时当新版本向老版本传输数据时,老版本会忽略该字段

老版本向新版本传输数据时,缺少了TripStatus字段,但是ProtoBuf中字段都是可选的,这里TripStatus的值会被赋为零值

go语言中每个类型都有一个零值,0,“”,false等

ProtoBuf中对字段赋零值和不填的效果是一样的(这里无法区分该值是赋值为0还是根本没有填,如果必须区分可以新加一个bool字段表明是否有值),若将这里DurationSec赋值为0,则跟没填的效果一样
在这里插入图片描述
打印结果为:在这里插入图片描述
这里的DurationSec不会被打印出来

在系统设计的时候需要为每个字段设计合适的零值:
例如:假设要新增一个功能可以让没有登陆的用户也可以使用我们的产品,我们需要区分这个行程是已登录的用户还是未登录的用户
假设在message新加了一个字段 bool isFromLoggedInUser,判断是否为登录的用户,这里老版本没有这个字段在向新版本传输数据时会赋值为false,即将所有老版本的用户都视为未登录的用户,这是错误的,因为老系统只允许登录的用户,也就是说老系统的用户都是已登录的用户。
应该添加的字段是 bool isFromGuestUser,这样老版本的用户该字段都为false,都不是未登录的用户,只有在新版本添加该功能后才会有用户被设置为true

GRPC服务器及客户端

定义服务

proto文件中添加如下定义

message GetTripRequest {
    string id = 1;
}

message GetTripResponse {
    string id = 1;
    Trip trip = 2;
}

service TripService{
    rpc GetTrip (GetTripRequest) returns (GetTripResponse);
}

使用protoc -I =. --go_out=plugins=grpc,paths=source_relative:gen/go trip.proto命令生成代码,这里新加了一个plugins=grpc参数

plugins=grpc, paths=source_relative 是两个参数
gen/go 是生成代码的路径

在生成的go语言代码中可以使用Go to Symbol in Editor搜索TripService
在这里插入图片描述
可以看到TripServiceClient是一个接口,其中包括GetTripRequest、GetTripResponse等
在这里插入图片描述
同样TripServiceServer也是一个接口
在这里插入图片描述
要实现这两个功能只需要实现这个接口即可


实现TripServiceServer接口

在server下新建tripservice目录,在目录下新建trip.go
定义Service结构体实现GetTrip方法,这样Service就实现了TripServiceServer接口

package trip

import (
	"context"
	trippb "coolcar/proto/gen/go"
)

type Service struct{}

func (*Service) GetTrip(c context.Context, req *trippb.GetTripRequest) (*trippb.GetTripResponse, error) {
	return &trippb.GetTripResponse{
		Id: req.Id,
		Trip: &trippb.Trip{
			Start:       "abc",
			End:         "def",
			DurationSec: 3600,
			FeeCent:     10000,
			StartPos: &trippb.Location{
				Latitude:  30,
				Longitude: 120,
			},
			EndPos: &trippb.Location{
				Latitude:  35,
				Longitude: 115,
			},
			PathLocations: []*trippb.Location{
				{
					Latitude:  31,
					Longitude: 119,
				},
				{
					Latitude:  32,
					Longitude: 118,
				},
			},
			Status: trippb.TripStatus_IN_PROGRESS,
		},
	}, nil
}

启动服务

1. 设置监听端口
2. 创建grpc.server
3. 将之前实现的TripServiceServer接口注册到server服务器
4. 服务器与监听端口绑定

package main

import (
	trippb "coolcar/proto/gen/go"
	trip "coolcar/tripservice"
	"log"
	"net"

	"google.golang.org/grpc"
)

func main() {
	// 监听端口
	lis, err := net.Listen("tcp", ":8081")
	if err != nil {
		// 不使用panic,Fatalf:输出之后程序退出
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	trippb.RegisterTripServiceServer(s, &trip.Service{})
	// 如果s.Serve没有出错的话会一直监听不会退出,如果出错则直接将返回的error输出
	log.Fatal(s.Serve(lis))
}

建立客户端向服务器发送请求

在server下新建client目录,目录下新建main.go
1. 建立到服务端的连接
2. 由连接建立client
3. 通过新建的client调用GetTrip方法,传入方法参数,交给server实现的方法处理

grpc.Dial如果不加第二个参数直接运行会报错提示不安全,我们按照错误的提示添加了第二个参数即可正常运行

这里调用的GetTrip方法为client调用的服务器上的方法,这个方法正是在之前实现接口时所实现的方法

package main

import (
	"context"
	trippb "coolcar/proto/gen/go"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 设置日志格式
	log.SetFlags(log.Lshortfile)
	// 建立连接
	conn, err := grpc.Dial("localhost:8081", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("cannot connect server: %v", err)
	}
	// 新建client
	tsClient := trippb.NewTripServiceClient(conn)
	// 调用GetTrip方法
	r, err := tsClient.GetTrip(context.Background(), &trippb.GetTripRequest{Id: "trip456"})
	if err != nil {
		log.Fatalf("cannot call GetTrip: %v", err)
	}
	fmt.Println(r)
}

执行结果:
在这里插入图片描述

完整流程:

  1. 在proto文件中定义server接口要实现GetTrip方法,在这里只给出了GetTrip的定义没有给出具体的实现
  2. 然后我们在第一步中实现了TripServiceServer接口,在这里第一次实现了GetTrip方法
  3. 随后在建立服务端的时候将实现了TripServiceServer接口的Service结构体注册到服务器
  4. 在客户端向服务器发送请求调用GetTrip方法,传入方法参数

REST vs RPC

HTTP协议

HTTP协议是基于TCP传输的,TCP协议只负责可靠的传输数据没有规定数据传输的格式,HTTP负责规定TCP协议传输的数据
Method:GET、PUT、POST、DELETE…
URL
DATA

RPC

RPC(Remote Procedure Call),远程过程调用,其中Procedure通常认为就是一个函数,RPC不需要指定远程服务器的地址,因为RPC是在已经建立连接的TCP服务之上的,只需要给定函数名称和函数参数就可以调用。
RPC在前后端之间的通信仍需借用HTTP协议,因为浏览器或小程序最方便的仍是发送HTTP请求,将RPC服务暴露在网上有两种风格的接口

RPC

  • Method
    • POST
  • URL
    • api.service.com/GetTrip
    • api.service.com/CreateTrip
  • Data
    • GetTripRequest
    • CreateTripRequest

REST
method是一个动词,动作的对象是url是一个名词

  • C
    • POST api.service.com/trip (POST是动词,后面跟的url是一个名词)
    • Data:JSON
  • R
    • GET api.service.com/trip/{id}
  • U
    • PUT api.service.com/trip/{id}
    • Data:JSON
  • D
    • DELETE api.service.com/trip/{id}

前端请求GRPC服务的两种方案架构

要想把GRPC服务直接给前端(小程序/web)使用,还需要GRPC over HTTP,小程序不能直接发GRPC请求,只能发HTTP请求。

假设项目中有一个微服务的架构其中有很多GRPC的server,在系统内部GRPC之间都通过TCP连接进行通信,这里的问题在于怎样把内部的GRPC服务暴露给前端的小程序(Web)

  • GRPC Gateway
  • GRPC Web Proxy

在这里插入图片描述

GRPC Web Proxy

该方案是给Web端使用的,Web端与GRPC Web Proxy建立HTTP连接,传输的是二进制数据流(之前protobuf序列化得到的二进制数据流),Web Proxy接收到二进制数据后,会分析其调用的GRPC服务的具体参数,然后将该二进制数据送到后台具体的GRPC服务,这里的GRPC Web Proxy也称为反向代理,外部请求发到服务集群时一律是发到GRPC Web Proxy,然后再由GRPC Web Proxy将请求分发到内网不同的服务上,在整个过程中传输的数据都是二进制数据。这个方案需要在Web端就将数据编码为二进制数据,这在go语言中比较容易,但是在html和js中比较麻烦。

GRPC Gateway

gRPC Gateway 是一个用于将 gRPC 服务暴露为 RESTful API 的代理工具。它通过在 gRPC 服务和 HTTP 请求之间提供一个转换层,使得使用 HTTP 和 RESTful 接口的客户端也可以与 gRPC 服务进行通信。简而言之,gRPC Gateway 允许你通过 HTTP REST 请求访问 gRPC 服务,从而使得你可以同时享受 gRPC 的高效性和 HTTP/JSON 的兼容性。

GRPC Gateway在小程序和web端都可以使用,前端通过HTTP协议向GRPC Gateway传输JSON格式的字符串,GRPC Gateway也是反向代理,将请求分发给内网的服务,但是GRPC Gateway在分发之前需要首先将JSON转为二进制数据流。

为什么要使用GRPC需要复杂的代理转发还要用GRPC进行开发呢?直接用REST API 内部都用HTTP协议直接进行传输不是更方便吗?
无论用GRPC还是REST API都要搭建一个类似的结构,内网各种服务之间畅通通信,内网到外网都需要反向代理向外暴露一个服务接口,不可能由微服务本身直接向外界暴露接口,对内对外总是要分成两部分来考虑,中间代理层无法省略,所以我们不如在代理层做一个GRPC的转换使得内网的服务都在GRPC中完成,GRPC的服务开发是非常方便的

GRPC Gateway的实现

定义接口

定义http rest层面上暴露的接口,在proto目录下新建trip.yaml文件
selector中coolcar为proto文件的package,TripService为服务名,GetTrip为定义的方法
对应selector的服务,暴露的接口为method为get,url为 /trip/{id}

type: google.api.Service
config_version: 3

http:
  rules:
  - selector: coolcar.TripService.GetTrip
    get: /trip/{id}

当收到的请求url方法为get并且请求路径为/trip/{id}就可以解析出请求的方法为TripService.GetTrip

生成代码

执行命令:
protoc -I =. --go_out=plugins=grpc,paths=source_relative:gen/go trip.proto
protoc -I =. --grpc-gateway_out=paths=source_relative,grpc_api_configuration=trip.yaml:gen/go trip.proto

在proto/gen/go目录下生成了trip.pb.go和trip.pb.gw.go文件,trip.pb.gw.go在一开始就说明了该文件是一个反向代理,将gRPC转换为RESTful接口

在这里插入图片描述

开启gateway服务

在main.go(服务端)中新建函数

  • context:在后面仔细讲,这里生成了一个没有具体内容的上下文,在这个上下文中连接后端的grpc服务,并在之后为上下文添加cancel的能力,在服务结束后断开连接
  • mux: multiplexer 一对多,分发器
  • “localhost:8081”:tripservice的地址
  • []grpc.DialOption:连接方式,这里选择不安全的方式,不加这个可能会提示连接不安全报错
  • trippb.RegisterTripServiceHandlerFromEndpoint:注册服务
    这里的trippb.RegisterTripServiceHandlerFromEndpoint函数就是建立了下图红色箭头所示的连接
    在这里插入图片描述
    在context的基础上建立连接,连接注册在mux上,连接对象是localhost:8081
  • http.ListenAndServe(“:8080”, mux):gateway的监听端口
func startGRPCGateway() {
	// context在后面仔细讲,这里生成了一个没有具体内容的上下文,在这个上下文中连接后端的grpc服务
	c := context.Background()
	// 为上下文添加cancel的能力,cancel是一个函数,调用cancel连接就会被断开
	c, cancel := context.WithCancel(c)
	// 服务结束断开连接
	defer cancel()

	// mux: multiplexer 一对多,分发器
	mux := runtime.NewServeMux()
	// 通过context c连接,c在函数返回后就会被cancel掉,这个连接注册在NewServeMux上:8081,连接方式为insecure通过tcp明文连接
	err := trippb.RegisterTripServiceHandlerFromEndpoint(
		c,
		mux,
		"localhost:8081",
		[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
	)
	if err != nil {
		log.Fatalf("cannot start grpc gateway: %v", err)
	}

	// http监听地址,8081是tripservice的地址与gateway的地址是不同的
	err = http.ListenAndServe(":8080", mux)
	if err != nil {
		log.Fatalf("cannot listen and server: %v", err)
	}
}

go 关键字用于启动一个新的并发执行的goroutine。Go语言中的goroutine是轻量级的线程,它们在Go运行时中被多路复用到线程上,当你在一个函数调用前加上go关键字时,Go运行时会为该函数创建一个新的goroutine,并立即开始执行,而不会阻塞调用它的goroutine。这意味着你可以同时运行多个函数,它们可以独立地执行,并且可以相互通信。

在这里插入图片描述
启动该服务,在浏览器(http协议) 访问http://localhost:8080/trip/123,可以看到返回数据
在这里插入图片描述

  1. 此处的流程为从Web端发送请求/trip/123到GRPC Gateway,GRPC Gateway在初始化的时候就建立了到8081端口的连接
  2. 根据yaml文件的配置将该请求翻译为针对GetTrip的请求,并且可以得到url中的id就是真正送给gettrip的id,注意yaml中的id对应的就是GetTripRequest中的id这里需要对应(这里具体的实现可以看trip.pb.gw.go的源码进一步理解)
  3. 接收到请求后GetTrip就可以获取tripid为123的行程返回二进制数据到Gateway,Gateway将数据转为JSON形式返回给Web

小程序访问GRPC Gateway

直接在app.ts中发送request请求

// app.ts
App<IAppOption>({
  globalData: {},
  onLaunch() {
    wx.request({
      url: 'http://localhost:8080/trip/trip123',
      method: 'GET',
      success: console.log,
      fail: console.error
    })
    // 登录
    wx.login({
      success: res => {
        console.log(res.code)
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      },
    })
  },
})

每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信。
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html

我们在本地调试的时候可以设置为不校验合法域名
在这里插入图片描述
访问返回结果:
在这里插入图片描述

数据类型填坑

在这里插入图片描述
小程序的返回中duration和fee都是string的形式,但是我们希望是number的形式,考虑原因是duration和fee在proto文件中的数据类型是int64,因为太大了所以转为了字符串,我们尝试将类型改为int32重新生成就是number的格式


在这里插入图片描述
枚举类型传输的本质是一个数值,这里的IN_PROGRESS实际上是2,考虑兼容性我们将这里的status设置为数值
在定义gateway服务的mux时添加转换方式

	mux := runtime.NewServeMux(runtime.WithMarshalerOption(
		runtime.MIMEWildcard, &runtime.JSONPb{
			EnumsAsInts: true,
			OrigName:    true,
		},
	))

在这里插入图片描述

小程序项目结构调整

为了给小程序加入proto类型需要引入第三方包,为了引入第三方包需要首先调整小程序的目录结构,把小程序所有的内容都移动到miniprogram目录下

  1. 在miniprogram目录下新建appoption.ts,将typeings目录下的index.d.ts的内容复制到apption.ts中,删除typings目录(可以生成的文件都可以删掉,使用npm install都可以再生成)
  2. 删除package-lock.json(npm安装时生成的文件)
  3. 剩下的文件移动到miniprogram目录下
  4. project.config.json中删除miniprogramRoot,此时该文件已经在miniprogram目录下了
  5. tsconfig.json中删除typeRoots,typings我们已经删除了
  6. miniprogram目录下运行npm install命令
  7. 在tsconfig.json中添加"types": [“miniprogram-api-typings”]。这样就可以在小程序中正常使用type了,此时的目录为wx/miniprogram/node_modules/miniprogram-api-typings/types
  8. 运行npm run tsc,解决报错
  9. 重新在开发者工具中导入项目,项目根目录为wx/miniprogram

调整后的项目如下所示:
https://github.com/shn-1/coolcar2024/tree/0d606611189fa9cf222859a0ade14608635ee9f8

小程序请求的强类型化

把protobuf转为ts代码,可以获取接收数据的类型

  1. 小程序目录miniprogram下运行 npm install protobufjs,注意要把package.json中的安装版本设置为6.11.4
    在这里插入图片描述

  2. 新建miniprogram/service/proto_gen目录,在server/proto目录下运行命令,从protobuf生成js文件
    ../../wx/miniprogram/node_modules/.bin/pbjs -t static -w es6 trip.proto --no-create --no-decode --no-verify --no-delimited -o ../../wx/miniprogram/service/proto_gen/trip_pb.js

  3. server/proto目录下运行命令,从js文件生成ts文件
    ../../wx/miniprogram/node_modules/.bin/pbts -o ../../wx/miniprogram/service/proto_gen/trip_pb.d.ts ../../wx/miniprogram/service/proto_gen/trip_pb.js
    在这里插入图片描述

在app.ts中获取返回值在这里插入图片描述这里直接编译运行会报错,需要在小程序开发工具的本地设置中开启将JS编译成ES5,并且在trip_pb.js的开头添加import * as $protobuf from "protobufjs";


这里只拿到了部分的返回值,这是因为在传参的时候存在驼峰命名和下划线命名,在生成的Trip的接口定义中使用的都是驼峰命名,因此需要把下划线命名改为驼峰命名才能识别
在这里插入图片描述
一种方式是在后端将mux的OrigName设置为false,这样传递的参数都是驼峰命名
在这里插入图片描述
但是约定在通常网络传输中都是使用下划线命名
第二种方法,我们将OrigName改回false,在小程序上安装npm install camelcase-keys,这里的版本为6.2.2
在这里插入图片描述
在传递参数的时候将其转换为驼峰命名,在开发工具中重新构建npm
在这里插入图片描述
此时接收到的就是驼峰命名
在这里插入图片描述


这里接收到的status是2,我们需要解析出2对应的实际字符串
console.log('status is', coolcar.TripStatus[getTripRes.trip?.status!])


通过以上的方法我们可以.出接受数据的内容有哪些,方便开发
在这里插入图片描述

通过proto文件定义,既可以在go语言中实现也可以在typescript中实现,两端都通过proto文件定死,保证了前后端对接口的理解一致


项目目录中的miniprogram_npm是通过开发者工具构建npm生成的,它是node_modules的子集,只有在dependencies中的库才会被编译进miniprogram_npm,当小程序打包发布时miniprogram_npm会被打包发给所有的用户,node_modules不会打包,这里的protobufjs文件有些大,暂时可以接受,后期可以再优化
在这里插入图片描述


我们在trip_pb.js中添加了一行代码import * as $protobuf from "protobufjs";当重新生成这个文件时这段代码又没有了,这里需要通过shell脚本实现,将生成的文件暂存,然后将代码先输出到js文件,再将暂存的文件追加到js文件后,再删除临时文件

$PBTS_BIN_DIR/pbjs -t static -w es6 trip.proto --no-create --no-encode --no-decode --no-verify --no-delimited -o $PBTS_OUT_DIR/trip_pb_tmp.js
echo 'import * as $protobuf from "protobufjs";\n' > $PBTS_OUT_DIR/trip_pb.js
cat $PBTS_OUT_DIR/trip_pb_tmp.js >> $PBTS_OUT_DIR/trip_pb.js
rm $PBTS_OUT_DIR/trip_pb_tmp.js

miniprogram_npm是通过开发工具构建npm生成的,我们不希望将它也上传到git仓库,在.gitignore中添加miniprogram_npm (在vscode终端直接使用code ..\..\.gitignore就可以在vscode中打开该文件)


trip_pb.js等虽然是生成的文件,但是执行的命令比较复杂,所以我们将它也上传到git仓库,在.gitignore中添加!**/wx/miniprogram/service/proto_gen/*.js


修改README
在这里插入图片描述