vscode中golang的配置
- 设置依赖管理
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下载依赖
安装go插件
view —— Command Palatte —— 选择Go: Install/Update Tools 安装所有工具
在coolcar目录下新建server目录,使用File-Add Folder to Workspace添加server目录到vscode
这里不能直接打开coolcar目录,因为go的插件与项目根目录有关,所以我们在这里分别将wx和server目录添加到我们的workspace
- file —— preference —— setting
- 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编译器的安装
安装protobuf编译器
https://github.com/protocolbuffers/protobuf/releases
下载解压后,将bin目录添加到环境变量,在终端执行protoc命令可以看到对应输出安装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)
}
执行结果:
完整流程:
- 在proto文件中定义server接口要实现GetTrip方法,在这里只给出了GetTrip的定义没有给出具体的实现
- 然后我们在第一步中实现了TripServiceServer接口,在这里第一次实现了GetTrip方法
- 随后在建立服务端的时候将实现了TripServiceServer接口的Service结构体注册到服务器
- 在客户端向服务器发送请求调用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,可以看到返回数据
- 此处的流程为从Web端发送请求/trip/123到GRPC Gateway,GRPC Gateway在初始化的时候就建立了到8081端口的连接
- 根据yaml文件的配置将该请求翻译为针对GetTrip的请求,并且可以得到url中的id就是真正送给gettrip的id,注意yaml中的id对应的就是GetTripRequest中的id这里需要对应(这里具体的实现可以看trip.pb.gw.go的源码进一步理解)
- 接收到请求后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目录下
- 在miniprogram目录下新建appoption.ts,将typeings目录下的index.d.ts的内容复制到apption.ts中,删除typings目录(可以生成的文件都可以删掉,使用npm install都可以再生成)
- 删除package-lock.json(npm安装时生成的文件)
- 剩下的文件移动到miniprogram目录下
- project.config.json中删除miniprogramRoot,此时该文件已经在miniprogram目录下了
- tsconfig.json中删除typeRoots,typings我们已经删除了
- miniprogram目录下运行npm install命令
- 在tsconfig.json中添加"types": [“miniprogram-api-typings”]。这样就可以在小程序中正常使用type了,此时的目录为wx/miniprogram/node_modules/miniprogram-api-typings/types
- 运行npm run tsc,解决报错
- 重新在开发者工具中导入项目,项目根目录为wx/miniprogram
调整后的项目如下所示:
https://github.com/shn-1/coolcar2024/tree/0d606611189fa9cf222859a0ade14608635ee9f8
小程序请求的强类型化
把protobuf转为ts代码,可以获取接收数据的类型
小程序目录miniprogram下运行
npm install protobufjs
,注意要把package.json中的安装版本设置为6.11.4
新建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
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