proto2api如何把Protocol Buffer转换成TS文件
一、前言
如果厌倦了手写各种后端调用的api文件,可以试试 proto2api 的ts 生成功能,一个命令自动把Protocol Buffer文件定义的各种message、enum、services翻译成需要的TypeScript中的interface、enum、function
举个例子:
有个hello.proto,通过 proto2api 命令就可以自动翻译成了hello.ts,如下图所示
上面的效果只需要下面一行命令就可以实现。
# 如果没有安装 proto2api
# sudo npm install -g proto2api
proto2api -d /xxx/xxx.proto -o xxx/api
上面这个例子算是比较简单的,在实际项目中,一个PB几百行并不少见,甚至上千行都有可能,更何况还有一堆的依赖文件,就算有再大的耐心,如果多几个PB尼?
会面对一系列问题,比如:
- 效率性问题 ,一个个翻译,耗时巨大
- 准确性问题 ,工作量一旦多了起来,单纯靠人会面对准确性问题。
- 及时性问题 ,后端修改了PB,前端不一定能及时修改
- 类型问题 ,PB类型非常广,但js的类型有限,另外还有类型不一致的问题,比如int64,到了前端就变成了string类型
- 其他问题 ,前端喜欢用驼峰,而PB推荐用下划线;PB定义了默认值,前端如何写入
通过proto2api命令行生成TS文件就可以一次性解决上述问题
二、命令行说明
Usage: proto2api [options]
Convert proto file to api file
Options:
-V, --version output the version number
--debug Load code with ts-node for debug
-d, --dir <type> Directory address of Protocol Buffers. eq: /path/pbdir or /path/hello.proto
-o, --output <type> Output api path
--apiName <type> apiName (default: "webapi")
--apiPath <type> apiPath (default: "~/utils/api")
--ignore [ignore...] Ignore unnecessary generated pb files (default: "google|swagger")
-h, --help display help for command
这里可能需要额外说明的是三个参数
2.1 --apiName
这个值用来代表导出的webapi的名字,这个导出对象需要实现http的get、post、put、delete、patch5个常用的http请求操作,用来支撑PB中关于service的定义。
备注:如果不知道如何实现,下面有实际代码
2.2 --apiPath
这个代表webapi的路径
2.3 --ignore
对PB文件进行生成时,PB有可能还引入了一些第三方的插件,比如google官方的http插件、swagger插件,这些插件通常是用来增强gRPC某些功能,但是在纯前端领域我们不需要关注这些,那么可以默认把它忽略掉,这样就不会生成对应的TS代码。
注意:当前很多使用gRPC基本上都用了http、swagger的插件,默认情况下会自动把这两个插件忽略生成。
具体用法如下所示
## 以下都是忽略google、swagger的插件
proto2api -d xx/svc.proto -o apps/bot/api --ignore google\|swagger
proto2api -d xx/svc.proto -o apps/bot/api --ignore google swagger
# 选择全量输出
proto2api -d xx/svc.proto -o apps/bot/api --ignore
三、webapi的说明
前端每次接口的调用,都是一次http的请求,而 http请求总共有9种 ,我们和后端常用的有5种,每次请求api基本上主要的内容有:url、method、header、request、response,如下所示。
webapi就是用来处理所有api请求的抽象,这里提供了一个基于axios的模板,方便大家使用
import Axios, { AxiosRequestConfig, Method, AxiosInstance } from 'axios';
* 解析路由参数responseType
const reg = /:[a-z|A-Z]+/g;
export function parseParams(url: string): Array<string> {
const ps = url.match(reg);
if (!ps) {
return [];
return ps.map((k) => k.replace(/:/, ''));
* 按照url和params生成相应的url
* @param url
* @param params
export function genUrl(url: string, params: WebReq['params']) {
if (!params) {
return url;
const ps = parseParams(url);
ps.forEach((k) => {
const reg = new RegExp(`:${k}`);
url = url.replace(reg, params[k]);
const path: Array<string> = [];
for (const key of Object.keys(params)) {
if (!ps.find((k) => k === key)) {
path.push(`${key}=${params[key]}`);
return url + (path.length > 0 ? `?${path.join('&')}` : '');
export interface WebReq {
params?: { [index: string]: any };
forms?: any;
// export interface Re{
// code: number;
// result: T;
// msg: string;
export interface CustomAxiosRequestConfig extends AxiosRequestConfig {
contentType?: 'application/json' | 'multipart/form-data' | 'text/plain';
export interface Headers {
'Content-Type': string;
export interface Options {
headers?: Headers;
config?: CustomAxiosRequestConfig;
export class Webapi {
public axios: AxiosInstance;
public reqInterceptors: number;
public resInterceptors: number;
constructor(options: Options) {
this.axios = Axios.create(options.config);
get<T>(
url: string,
params?: WebReq['params'],
forms?: WebReq['forms'],
config?: AxiosRequestConfig
): Promise<T> {
return this.api<T>(url, { params, forms }, 'get', config);
delete<T>(
url: string,
params?: WebReq['params'],
forms?: WebReq['forms'],
config?: AxiosRequestConfig
): Promise<T> {
return this.api<T>(url, { params, forms }, 'delete', config);
post<T>(
url: string,
params?: WebReq['params'],
forms?: WebReq['forms'],
config?: AxiosRequestConfig
): Promise<T> {
return this.api<T>(url, { params, forms }, 'post', config);
put<T>(
url: string,
params?: WebReq['params'],
forms?: WebReq['forms'],
config?: AxiosRequestConfig
): Promise<T> {
return this.api<T>(url, { params, forms }, 'put', config);
patch<T>(
url: string,
params?: WebReq['params'],
forms?: WebReq['forms'],
config?: AxiosRequestConfig
): Promise<T> {
return this.api<T>(url, { params, forms }, 'patch', config);
api<T>(
url: string,
req: WebReq,
method: Method = 'get',
config?: AxiosRequestConfig
): Promise<T> {
if (url.match(/:/) || method.match(/get|delete/i)) {
// 如果路由是带挂参的,先看params再看forms
url = genUrl(url, req.params || req.forms);
method = method.toLocaleLowerCase() as Method;
switch (method) {
case 'get':
return this.axios.get(url, config);
case 'delete':
return this.axios.delete(url, config);
case 'post':
return this.axios.post(url, req.forms || req.params, config);
case 'put':
return this.axios.put(url, req.forms || req.params, config);
case 'patch':
return this.axios.patch(url, req.forms || req.params, config);
default:
return this.axios.get(url, config);
}
备注:如果不习惯用axios,可以基于上面这个模板写一个类似的,只要提供好get、post这些常用http请求就可以了。
在实际业务中有很大差异,所以还需要做一定程度的本地化才行
// ~/utils/api.ts
import { Webapi, ApiBaseUrl } from '../webapi';