适用于前端的 NestJS 的 protoc 基础入门

一、为什么?

NestJS 中使用 gRPC 实现微服务通信, proto 文件编译成 ts 文件避免手动 TS 类型文件。

二、安装

sh 复制代码
# linux
apt install -y protobuf-compiler
protoc --version  # 查看安装是否成功,和版本

# mac
brew install protobuf
protoc --version   # 查看安装是否成功,和版本

# pnpm
pnpm global add protoc # 查看安装是否成功,和版本

三、protobuf 类型系统

回顾 proto3 基础知识:

四、ts-proto 插件

关于 protoc typescript 插件有很多,这使用 ts-proto

五、基于 pnpm 实战 gRPC + protoc + ts-proto

考虑到微服务,这里使用 pnpm workspace 作为基础工具, 在 pnpm workspace 子项目中完成对 proto 文件进行编译。

1) 初始化

sh 复制代码
cd <your_dir>
pnpm init
touch pnpm-workspace.yaml

追加 workspace 内容:

yaml 复制代码
- packages:
  - "packages/*"

初始化子项目

sh 复制代码
cd workspace
mkdir ts-proto-test
pnpm init
pnpm add protoc ts-protoc

注意: npm 包的 protoc 中 "postinstall": "node scripts/postinstall.js" 可能需要等待。

2) 编写 proto 文件

以上是一个 user 的 crud/count/list 操作的proto, 其中有非标量 google.protobuf.Timestamp 用于生成 TS 的 Date 类型。

proto 复制代码
syntax = "proto3";
package user;

import "google/protobuf/timestamp.proto";

service UserService {
  rpc CreateUser(CreateUserRequest) returns (UserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (UserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UserResponse);
  rpc GetUser(GetUserRequest) returns (UserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc GetUsersCount(GetUsersCountRequest) returns (GetUsersCountResponse);
}
message User {
  string id = 1;
  string username = 2;
  string password = 3;
  string email = 4;
  optional string role = 5;
  google.protobuf.Timestamp createdAt = 6;
  google.protobuf.Timestamp updatedAt = 7;
}

message CreateUserRequest {
  string uesrname = 1;
  string password = 2;
  string email = 3;
  optional string role = 4;
  google.protobuf.Timestamp createdAt = 5;
  google.protobuf.Timestamp updatedAt = 6;
}

message DeleteUserRequest {
  string id = 1; 
}
message UpdateUserRequest {
  string id = 1; 
  string username = 2;
  string password = 3;
  string email = 4;
  string role = 5;
  google.protobuf.Timestamp updatedAt = 6;
}

message GetUserRequest {
  string id = 1; 
}

message UserResponse {
  User user = 1; 
}

message ListUsersRequest {
  int32 page = 1;
  int32 pageSize = 2;
}

message ListUsersResponse {
  repeated User users = 1;
}

message GetUsersCountRequest {}

message GetUsersCountResponse {
  int32 count = 1;
}

3) 编写 protoc 编译命令

sh 复制代码
{
    "scripts": 
    "proto-ts": "protoc --plugin=./node_modules/ts-proto/protoc-gen-ts_proto.CMD --ts_proto_out=./ ./src/proto/*.proto --ts_proto_opt=outputEncodeMethods=false,outputJsonMethods=false,outputClientImpl=false"
}
  • --plugin 指定插件: ./node_modules/ts-proto/protoc-gen-ts_proto.CMD, windows 是 CMD 或者ps1 文件,其他的平台不需要此后缀。
  • --ts_proto_out: 指定编译后 TS 的输出路径和 proto 文件路径, 根据自己的项目自定义路径。
  • outputEncodeMethods/outputJsonMethods/outputClientImpl 一些与 proto encode/json/client 相关。

4) 编译后的结果

ts 复制代码
export const protobufPackage = "user";

export interface User {
  id: string;
  username: string;
  password: string;
  email: string;
  role?: string | undefined;
  createdAt: Date | undefined;
  updatedAt: Date | undefined;
}

export interface CreateUserRequest {
  uesrname: string;
  password: string;
  email: string;
  role?: string | undefined;
  createdAt: Date | undefined;
  updatedAt: Date | undefined;
}

export interface DeleteUserRequest {
  id: string;
}

export interface UpdateUserRequest {
  id: string;
  username: string;
  password: string;
  email: string;
  role: string;
  updatedAt: Date | undefined;
}

export interface GetUserRequest {
  id: string;
}

export interface UserResponse {
  user: User | undefined;
}

export interface ListUsersRequest {
  page: number;
  pageSize: number;
}

export interface ListUsersResponse {
  users: User[];
}

export interface GetUsersCountRequest {
}

export interface GetUsersCountResponse {
  count: number;
}

export interface UserService {
  CreateUser(request: CreateUserRequest): Promise<UserResponse>;
  DeleteUser(request: DeleteUserRequest): Promise<UserResponse>;
  UpdateUser(request: UpdateUserRequest): Promise<UserResponse>;
  GetUser(request: GetUserRequest): Promise<UserResponse>;
  ListUsers(request: ListUsersRequest): Promise<ListUsersResponse>;
  GetUsersCount(request: GetUsersCountRequest): Promise<GetUsersCountResponse>;
}

可选被编译成联合类型(undefined), 通过引入 google/protobuf/timestamp.proto 解决了 proto 原生没有 Date 的问题,配合其他的类型的时候就更加方便了。google/protobuf/timestamp.proto 文件,会在项目中生成对应的 ts 类型文件加和文件,不需要的话需要删除。

有了 google/protobuf/timestamp.proto 示例,扩展其他其他的数据类型方法也是一样的。基于 protoc npm 包,我们可以找到其他的扩展:

  • any
  • api
  • descriptor
  • duration
  • empty
  • field_mask
  • source_context
  • struct
  • timestamp
  • type
  • wrappers

等等类型

六、小结

在 NestJS 中使用 gRPC 配合 Prisma ORM 数据库操作的时候,类型不一致。proto 的原生类型不足以支持 TS 中的类型,需要在编译的时候插件支持。本文主要解决了 protoc 在 pnpm workspace 中认知、使用与实践,希望能够帮助大家。

相关推荐
灵感__idea4 小时前
Hello 算法:贪心的世界
前端·javascript·算法
GreenTea6 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
killerbasd7 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
吴声子夜歌7 小时前
ES6——二进制数组详解
前端·ecmascript·es6
我是大猴子8 小时前
Spring代理类为何依赖注入失效?
java·后端·spring
码事漫谈8 小时前
手把手带你部署本地模型,让你Token自由(小白专属)
前端·后端
ZC跨境爬虫8 小时前
【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级
前端·爬虫·scrapy·html
爱上好庆祝8 小时前
svg图片
前端·css·学习·html·css3
王夏奇8 小时前
python中的__all__ 具体用法
java·前端·python
大家的林语冰9 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js