相关文章推荐
任性的跑步鞋  ·  SqlConnection Class ...·  2 天前    · 
打酱油的小蝌蚪  ·  【Golang | ...·  2 月前    · 
俊逸的黄瓜  ·  JS ...·  1 年前    · 
率性的春卷  ·  Android GPS ...·  1 年前    · 
首发于 前端沧海
proto2api如何把Protocol Buffer转换成TS文件

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';