利用pont加速前后端对接速度

1,627 阅读8分钟

前言

目前前端前后端联调的时候会存在以下问题:

  • 后端接口更新通知不及时:后端接口更新,无法及时通知到前端;
  • 重复编写接口代码:需要前端重复编写请求接口代码;
  • 对字段困难:在对接口的时候总是要打开相应的swagger或者YAPI去对字段;
  • mock数据困难:部分团队将mock数据放置在YAPI,有时候YAPI的数据更新不及时,前端的接口就会版本落后了;

而以上的问题pont能够很好的帮你去处理。现根据团队使用了近一年的pont使用做了以下分享。

认识pont

Pont 把 swagger、rap、dip 等多种接口文档平台,转换成 Pont 元数据。Pont 利用接口元数据,可以高度定制化生成前端接口层代码,接口 mock 平台和接口测试平台。详细介绍

在官网中都有很多详细的说明,这里就不在做赘述了。这里主要说具体在项目中的实践

在vue-cli中使用pont

主要

1. 在项目中安装pont-engine

yarn add pont-engine -D

2. 在项目中建pont-config.json文件

建议在src文件夹同级中建config,并将其放置在里面

{
  "outDir": "../src/api", // 生成代码的存放路径,使用相对路径即可
  // 配置每个数据来源, 一个项目里面往往会有多个数据源,建议这样配置
  "origins": [
    {
      // 接口平台提供数据源的 open api url(需要免登),目前只支持 Swagger。
      "originUrl": "https://petstore.swagger.io/v2/swagger.json",
      // 建议配置,后面可以在模板文件里面使用
      "name": "petstore",
      // 配置是否启用多数据源,建议默认开启
      "usingMultipleOrigins": true
    }
  ],
  // 本地mock配置
  "mocks": {
    // 是否开启本地mock
    "enable": true,
    // 本地mock的接口,注意看看该端口是否被占用
    "port": 8081
  },
  // 生成模板文件路径,使用相对路径即可
  "templatePath": "./pont-template"
}

说明: 所有的参数说明都在配置里面说明了,使用的时候因为这个是JSON文件,应该将其注释去除后才使用

3. 在项目中建自定义代码生成器的文件pont-template

import {
  CodeGenerator,
  Interface,
  Mod,
  BaseClass,
  Surrounding,
} from "pont-engine";
import * as pont from "pont-engine";

export default class MyGenerator extends CodeGenerator {
  /** 获取某个基类的类型定义代码 */
  getBaseClassInDeclaration(base: BaseClass): string {
    return `class ${base.name} {
      ${base.properties
        .map((prop) => {
          // 替换 defs. 不使用 defs 命名空间
          let propertyCode = prop
            .toPropertyCode(Surrounding.typeScript, true)
            .replace(/defs\./g, "");

          if ((prop.dataType as any).reference) {
            // 这里可以通过正则将类型进行字符串的替换
            propertyCode = propertyCode
              .replace(/\?/, "")
              .replace(/content:/, "content?:")
              .replace(/payload:/, "payload?:");
          }
          return propertyCode;
        })
        .join("\n")}
    }
    `;
  }

  /** 获取所有基类的类型定义代码,一个 namespace */
  getBaseClassesInDeclaration(): string {
    const content = `namespace ${this.dataSource.name || "defs"} {
      ${this.dataSource.baseClasses
        .map(
          (base) => `
        export ${this.getBaseClassInDeclaration(base)}
      `
        )
        .join("\n")}
    }
    `;

    return content;
  }

  /** 获取接口内容的类型定义代码 */
  getInterfaceContentInDeclaration(inter: Interface): string {
    let bodyParams = inter.getBodyParamsCode();
    if (bodyParams.includes("defs.")) {
      bodyParams = bodyParams.replace(/defs\./g, "");
    }
    // 这里可以将部分统一在请求拦截的参数全部剔除出来,从请求拦截的时候再添加这些参数
    const paramsCode = inter.getParamsCode().replace(/tenantId:/g, "openId?:");

    // 将空声明清除
    const isEmptyParams =
      paramsCode.replace(/(\n|\s)/g, "") === "classParams{}";

    const requestArgs = [];

    !isEmptyParams && requestArgs.push("params: Params");
    bodyParams && requestArgs.push(`bodyParams: ${bodyParams}`);
    requestArgs.push(`options?: RequestConfig`);
    const requestParams = requestArgs.join(", ");

    let responseType = inter.responseType;
    if (responseType.includes("defs.")) {
      responseType = responseType.replace(/defs\./g, "");
    }
    const resArr = responseType.split("<");
    const lastItem = resArr[resArr.length - 1];

    let resStr = responseType;
    if (lastItem && lastItem.indexOf(".") > -1) {
      resArr.splice(resArr.length - 1, 0, "Required");
      resStr = resArr.join("<") + ">";
    }
    const urlParamsKeysStr = (inter.path.match(/(?<={)(.*?)(?=})/g) || [])
      .map((item) => `'${item}'`)
      .join("|");
    // 获取URL中的keys
    // 在params中获取对应的值
    // 额外
    return `
      ${isEmptyParams ? "" : "export " + paramsCode}
      export type ResponseType = Promise<${resStr}>
      ${
        urlParamsKeysStr ? `export type UrlParamType = ${urlParamsKeysStr}` : ""
      }
      export function getUrl(${
        urlParamsKeysStr ? "params: Pick<Params, UrlParamType>" : ""
      }): string;
      export function request(${requestParams}): ResponseType;
    `;
  }

  /** 获取公共的类型定义代码 */
  getCommonDeclaration(): string {
    return `
    import {RequestConfig} from "@/utils/fetch";
    type Required<T> = { [P in keyof T]-?: T[P] };
    type Pick<T, K extends keyof T> = { [P in K]: T[P] };
    `;
  }

  /** 获取接口实现内容的代码 */
  getInterfaceContent(inter: Interface): string {
    const bodyParams = inter.getBodyParamsCode();
    const paramsCode = inter.getParamsCode();
    const isEmptyParams =
      paramsCode.replace(/(\n|\s)/g, "") === "classParams{}";
    const contentType =
      inter.consumes && inter.consumes.length
        ? inter.consumes[0]
        : "application/json";

    const requestArgs: string[] = [];
    !isEmptyParams && requestArgs.push(`params`);
    bodyParams && requestArgs.push(`bodyParams`);
    requestArgs.push("options");
    const requestParams = requestArgs.join(", ");

    return `
    import { fetch, urlResolve } from "@/utils/fetch";

    const getWholePath = (path) => {
      const prefix = ''
      return prefix + path
    }
    /**
     * @desc 获取请求的URL
     */
    export function getUrl(paramsObj) {
      return urlResolve(getWholePath('${inter.path}'), paramsObj)
    }

    /**
     * @desc ${inter.description}
     */
    export function request(${requestParams}) {
      const fetchOption = Object.assign({
        url: getWholePath('${inter.path}'),
        method: '${inter.method}',
        headers: {
          'Content-Type': '${contentType}'
        },
        ${isEmptyParams ? "" : "" + "params: params,"}
        ${bodyParams ? "data: bodyParams" : ""}
      },
      options)
      return fetch(fetchOption);
    }
   `;
  }

  /** 获取单个模块的 index 入口文件 */
  getModIndex(mod: Mod): string {
    return `
      /**
       * @description ${mod.description}
       */
      ${mod.interfaces
        .map((inter) => {
          return `import * as ${inter.name} from './${inter.name}';`;
        })
        .join("\n")}

      export {
        ${mod.interfaces.map((inter) => inter.name).join(", \n")}
      }
    `;
  }

  /** 获取所有模块的 index 入口文件 */
  getModsIndex(): string {
    let conclusion = `
      export const API = {
        ${this.dataSource.mods.map((mod) => mod.name).join(", \n")}
      };
    `;

    // dataSource name means multiple dataSource
    if (this.dataSource.name) {
      conclusion = `
        export const ${this.dataSource.name} = {
          ${this.dataSource.mods.map((mod) => mod.name).join(", \n")}
        };
      `;
    }

    return `
      ${this.dataSource.mods
        .map((mod) => {
          return `import * as ${mod.name} from './${mod.name}';`;
        })
        .join("\n")}

      ${conclusion}
    `;
  }

  /** 获取接口类和基类的总的 index 入口文件代码 */
  getIndex(): string {
    let conclusion = `
      export * from './mods/';
    `;

    // dataSource name means multiple dataSource
    if (this.dataSource.name) {
      conclusion = `
        export { ${this.dataSource.name} } from "./mods/";
      `;
    }

    return conclusion;
  }
}

export class FileStructures extends pont.FileStructures {
  getModsDeclaration(originCode: string): string {
    return `
      export ${originCode}
    `;
  }

  getBaseClassesInDeclaration(originCode: string): string {
    return `
      export ${originCode}
    `;
  }

  getDataSourcesDeclarationTs(): string {
    const dsNames = (this as any).generators.map((ge) => ge.dataSource.name);

    return `
      ${dsNames
        .map((name) => {
          return `export {${name}} from './${name}/api';`;
        })
        .join("\n")}
      export as namespace defs;
    `;
  }

  getDataSourcesTs(): string {
    const dsNames = (this as any).generators.map((ge) => ge.dataSource.name);

    return `
      ${dsNames
        .map((name) => {
          return `import { ${name} } from "./${name}";`;
        })
        .join("\n")}
      import defs from './api';

      export type apitype = typeof defs;
      export const api = {${dsNames.join(",")}} as apitype;

    `;
  }
}
// eslint-disable-next-line
import { CodeGenerator, Interface, Mod, BaseClass, Surrounding } from 'pont-engine'
import * as pont from 'pont-engine'

export default class MyGenerator extends CodeGenerator {
  /** 获取某个基类的类型定义代码 */
  getBaseClassInDeclaration(base: BaseClass) {
    return `class ${base.name} ${base.name === 'Payload' || base.name === 'PageBean' ? '<T0>' : ''} {
      ${base.properties
    .map(prop => {
      // 替换 defs. 不使用 defs 命名空间
      let propertyCode = prop.toPropertyCode(Surrounding.typeScript, true).replace(/defs\./g, '')

      // 如果属性是范型参考,则属性为必选
      // 例如 data?: T0 , creditCustomerConsumptionDailyVo?: CreditManagerV2.AggregateAllTransactionDetailsWithinDimensions[]
      if (prop.dataType.reference || base.name === 'Payload' || base.name === 'PageBean') {
        propertyCode = propertyCode.replace(/\?/, '')
          .replace(/content\:/, 'content?:')
          .replace(/payload\:/, 'payload?:')
      }
      return propertyCode
    })
    .join('\n')}
    }
    `
  }

  /** 获取所有基类的类型定义代码,一个 namespace */
  getBaseClassesInDeclaration() {
    const content = `namespace ${this.dataSource.name || 'defs'} {
      ${this.dataSource.baseClasses
    .map(
      base => `
        export ${this.getBaseClassInDeclaration(base)}
      `
    )
    .join('\n')}
    }
    `

    return content
  }

  /** 获取接口内容的类型定义代码 */
  getInterfaceContentInDeclaration(inter: Interface) {
    let bodyParams = inter.getBodyParamsCode()
    if (bodyParams.includes('defs.')) {
      bodyParams = bodyParams.replace(/defs\./g, '')
    }
    const paramsCode = inter.getParamsCode()
      .replace(/tenantId\:/g, 'tenantId?:')
      .replace(/userId\:/g, 'userId?:')
      .replace(/username\:/g, 'username?:')

    const isEmptyParams = paramsCode.replace(/(\n|\s)/g, '') === 'classParams{}'

    const requestArgs = []

    // @ts-ignore
    !isEmptyParams && requestArgs.push('params: Params')
    // @ts-ignore
    bodyParams && requestArgs.push(`bodyParams: ${bodyParams}`)
    // @ts-ignore
    requestArgs.push(`options?: RequestConfig`)
    const requestParams = requestArgs.join(', ')

    let responseType = inter.responseType
    if (responseType.includes('defs.')) {
      responseType = responseType.replace(/defs\./g, '')
    }
    const resArr = responseType.split('<')
    const lastItem = resArr[resArr.length - 1]

    let resStr = responseType
    if (lastItem && lastItem.indexOf('.') > -1) {
      resArr.splice(resArr.length - 1, 0, 'Required')
      resStr = resArr.join('<') + '>'
    }
    const urlParamsKeysStr = (inter.path.match(/(?<={)(.*?)(?=})/g) || []).map(item => `'${item}'`).join('|')
    // 获取URL中的keys
    // 在params中获取对应的值
    return `
      ${isEmptyParams ? '' : 'export ' + paramsCode}
      export type ResponseType = Promise<${resStr}>
      ${urlParamsKeysStr ? `export type UrlParamType = ${urlParamsKeysStr}` : ''}
      export function getUrl(${urlParamsKeysStr ? 'params: Pick<Params, UrlParamType>' : ''}): string;
      export function request(${requestParams}): ResponseType;
    `
  }

  /** 获取公共的类型定义代码 */
  getCommonDeclaration() {
    return `
    import {RequestConfig} from '@@/utils/fetch'
    type Required<T> = { [P in keyof T]-?: T[P] };
    type Pick<T, K extends keyof T> = { [P in K]: T[P] };
    `
  }

  /** 获取接口实现内容的代码 */
  getInterfaceContent(inter: Interface) {
    const bodyParams = inter.getBodyParamsCode()
    const paramsCode = inter.getParamsCode()
    const isEmptyParams = paramsCode.replace(/(\n|\s)/g, '') === 'classParams{}'
    const contentType =
      inter.consumes && inter.consumes.length ? inter.consumes[0] : 'application/json'

    const requestArgs: string[] = []
    !isEmptyParams && requestArgs.push(`params`)
    bodyParams && requestArgs.push(`bodyParams`)
    requestArgs.push('options')
    const requestParams = requestArgs.join(', ')

    return `
    import { fetch, urlResolve } from '@@/utils/fetch';
    import { MEMBER_SERVER, STOCK_SERVER } from '@@/const/global-context.js'

    const serverPrefix = {
      'memberServer': MEMBER_SERVER,
      'stockServe': STOCK_SERVER
    }

    const getWholePath = (path) => {
      const prefix = serverPrefix['${this.dataSource.name}'] || ''
      return prefix + path
    }
    /**
     * @desc 获取请求的URL
     */
    export function getUrl(paramsObj) {
      return urlResolve(getWholePath('${inter.path}'), paramsObj)
    }

    /**
     * @desc ${inter.description}
     */
    export function request(${requestParams}) {
      const fetchOption = Object.assign({
        url: getWholePath('${inter.path}'),
        method: '${inter.method}',
        headers: {
          'Content-Type': '${contentType}'
        },
        ${isEmptyParams ? '' : '' + 'params: params,'}
        ${bodyParams ? 'data: bodyParams' : ''}
      },
      options)
      return fetch(fetchOption);
    }
   `
  }

  /** 获取单个模块的 index 入口文件 */
  getModIndex(mod: Mod) {
    return `
      /**
       * @description ${mod.description}
       */
      ${mod.interfaces
    .map(inter => {
      return `import * as ${inter.name} from './${inter.name}';`
    })
    .join('\n')}

      export {
        ${mod.interfaces.map(inter => inter.name).join(', \n')}
      }
    `
  }

  /** 获取所有模块的 index 入口文件 */
  getModsIndex() {
    let conclusion = `
      export const API = {
        ${this.dataSource.mods.map(mod => mod.name).join(', \n')}
      };
    `

    // dataSource name means multiple dataSource
    if (this.dataSource.name) {
      conclusion = `
        export const ${this.dataSource.name} = {
          ${this.dataSource.mods.map(mod => mod.name).join(', \n')}
        };
      `
    }

    return `
      ${this.dataSource.mods
    .map(mod => {
      return `import * as ${mod.name} from './${mod.name}';`
    })
    .join('\n')}

      ${conclusion}
    `
  }

  /** 获取接口类和基类的总的 index 入口文件代码 */
  getIndex() {
    let conclusion = `
      export * from './mods/';
    `

    // dataSource name means multiple dataSource
    if (this.dataSource.name) {
      conclusion = `
        export { ${this.dataSource.name} } from './mods/';
      `
    }

    return conclusion
  }
}

export class FileStructures extends pont.FileStructures {
  getModsDeclaration(originCode: string, usingMultipleOrigins: boolean) {
    return `
      export ${originCode}
    `
  }

  getBaseClassesInDeclaration(originCode: string, usingMultipleOrigins: boolean) {
    return `
      export ${originCode}
    `
  }

  getDataSourcesDeclarationTs() {
    const dsNames = (this as any).generators.map(ge => ge.dataSource.name)

    return `
      ${dsNames
    .map(name => {
      return `export {${name}} from './${name}/api';`
    })
    .join('\n')}
      export as namespace defs;
    `
  }

  getDataSourcesTs() {
    const dsNames = (this as any).generators.map(ge => ge.dataSource.name)

    return `
      ${dsNames
    .map(name => {
      return `import { ${name} } from './${name}';`
    })
    .join('\n')}
      import defs from './api';

      export type apitype = typeof defs;
      export const api = {${dsNames.join(',')}} as apitype;

    `
  }
}

TODO 特殊的说明

  • 可以对多暴露出来一个获取请求路径的方法,方便一些特殊组件的使用
  • 有一些接口里面包含的公共参数,有时候我们会在公共请求里面统一添加了,那么可以通过生成模板直接去掉该参数的声明
  • 不同的接口服务请求的上下文可能不一样,可以通过环境变量的形式传进来,然后注入到请求的链接上
  • 里面的utils/fetch应该为各自项目中的请求拦截收口文件,文章的项目demo链接里面有可以自行取用

4. 在Vue.prototype中注入$api

在入口文件中注入$api方便项目中的使用

// 注入$api,这样声明能够在使用的时候再进行引入,降低首屏的加载压力
Vue.prototype.$api = async function () {
  return await import('@/api')
}

5. 全局注入$api的声明

TS有较好的代码提示的作用,为了更加方便的使用,应该在全局里面注入其声明

// ts识别全局方法/变量
import VueRouter, { Route } from "vue-router";
import Vue from "vue";
import { Store } from "vuex";
import { api } from "./api/index";
declare global {
  interface window {
    require: any;
  }
}

// 识别 this.$route
declare module "vue/types/vue" {
  interface Vue {
    $router: VueRouter; // 这表示this下有这个东西
    $route: Route;
    $store: Store<any>;
    $api: () => Promise<{ api: typeof api }>;
  }
}

6. 原型链中使用pont

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>

<script>
export default {
  mounted() {
    const api = await this.$api()
    // 直接在原型链上获取到链式调用
    api.api.petstore.store.getInventory.request().then((res) => {
      console.log("res: ", res);
    });
    // 获取请求URL
    const url = api.api.petstore.store.getInventory.getUrl();
    console.log("url: ", url);
  },
}
</script>

7. 使用pont中的mock功能

只需要在vue-cli中将相关的proxy配置好即可

module.exports = {
  devServer: {
    proxy: {
      "/store": { // 写入需要拦截的接口
        target: "http://localhost:8081", // 你本地mock的地址,即你在pont-config.json
        ws: true,
        changeOrigin: true,
      },
    },
  },
};

在线demo仓库

github.com/Barretem/vu…

在nuxtJs中使用pont

1. 在项目中安装pont-engine

同上

2. 在项目中建pont-config.json文件

同上

3. 在项目中建自定义代码生成器的文件pont-template

同上

4. 从@nuxtjs/axios将请求方法映射出来

如果不将方法映射出来的话,只能在Vue.prototype上用$axios,对于统一的请求封装会比较麻烦 具体步骤如下:

1)在plugins文件夹下面新建一个axios文件,其内容如下:

import { setClient } from '@/utils/apiClient'
import { api } from '@/api'

export default function (ctx, inject) {
  const { $axios, app } = ctx
  // 映射$axios
  setClient(app.$axios)
  ctx.$api = api
  inject('api', api)

  $axios.onRequest((config) => {
    return config
  })

  $axios.onResponse((resp) => {
    return Promise.resolve(resp)
  })

  $axios.onError((error) => {
    // 将错误信息继续抛出,业务逻辑可以进行后续的操作
    return Promise.reject(error)
  })
}

2) 新建apiClient文件接收映射出来的$axios

import { AxiosRequestConfig, AxiosStatic } from 'axios'

interface ClientInter extends AxiosStatic {
  request<T = any>(config: AxiosRequestConfig): Promise<T>
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  options<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  post<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<T>
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
  patch<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<T>

  $request<T = any>(config: AxiosRequestConfig): Promise<T>
  $get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  $delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  $head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  $options<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
  $post<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<T>
  $put<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<T>
  $patch<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<T>
}

export type MethodsTypes =
  | 'request'
  | 'delete'
  | 'get'
  | 'head'
  | 'options'
  | 'post'
  | 'put'
  | 'patch'
  | '$get'
  | '$put'
  | '$delete'
  | '$post'

let client: ClientInter

export function setClient(newClient: ClientInter) {
  client = newClient
}

// Request helpers
const reqMethods: Array<MethodsTypes> = [
  'request',
  'delete',
  'get',
  'head',
  'options', // url, config
  'post',
  'put',
  'patch', // url, data, config
  '$get',
  '$put',
  '$delete',
  '$post',
]

const service: ClientInter = {} as ClientInter

reqMethods.forEach((method: MethodsTypes) => {
  service[method] = (...rest: any[]): Promise<any> => {
    if (!client) throw new Error('apiClient not installed')
    return (client[method] as any).apply(null, rest)
  }
})
export const GET = service.get
export const POST = service.post
export const DELETE = service.delete
export const PUT = service.put

export default service

这个文件可以按照实际需求进行自定义,主要做了二封,兼容普通的axios

3) 结合封装好的axios,将其加入业务代码

这个文件主要糅合了一些自己需求的请求处理了,需要根据自己的需求高度自定义。这里就不贴代码了,有兴趣可以去文件结尾的代码仓库里面看。

5. 在Vue.prototype中注入$api

需要在nuxt.config.js中的plugins加入更改声明好的./plugins/axios.js

export default {
  // ...
  plugins: ['@/plugins/axios'],
  // ...
}

6. 全局注入$api的声明

同上

7. 原型链中使用pont

同上

8. 使用pont中的mock功能

export default {
  //...
  proxy: {
    '/store/inventory': {
      target: 'http://localhost:8081', // pont-config.json中配置的端口号
      changeOrigin: true,
    },
  },
  axios: { 
    proxy: true, 
    prefix: '/'
  },
  //...
}

在线demo仓库

github.com/Barretem/nu…

使用技巧

以下1~3是基于VSCode总结出来的使用技巧(需要安装vscode-pont插件)

1. 巧用mock数据

mock数据是可以编辑以及实时预览的 Apr-29-2021 18-46-27.gif

2. 快速检索接口

可以在vscode编辑器界面,同时按下 cmd + ctrl + p 然后输入pont进行接口查找 Apr-29-2021 18-49-46.gif

3. 监控后端接口改动

image.png 当接口后端接口改动的时候,vscode-pont插件就会显示相应的改动。这个时候可以先将本地更改commit(有一个接口的历史记录,避免以后跟后端有接口改动的问题进行扯皮),然后再进行更新。注意:接口声明的更新以及接口的改动都会导致插件提示改动

4. 统一团队的接口请求

在团队开发中,团队小伙伴经常将后端接口放置在service文件夹中,有些小伙伴在service添加了一些接口请求路径,又有一些小伙伴添加了业务处理逻辑进去,导致接口重用性很低。而用pont统一生成接口调用文件,很大程度的提升了接口的通用性

注意事项

1. 后端声明接口的时候需要将name和tags字段改为因为名字

image.png

2. mock接口冲突

在同时打开了多个pont项目的时候,而这些pont项目都启用了mock功能,各个项目直接的mock端口会有端口冲突的问题。导致mock服务无法使用。解决方式:不同的pont项目用不同的mock端口号

3. mock缓存数据没有自动清理

当pont-config.json中的originUrl更改的时候,.mock文件夹中的数据有可能存在没有更新的问题。解决方式:将.mock文件夹删除,重新打开vscode的问题(需要注意,有一些你自己自定义的mock数据有可能被删除)。

总结

以上总结了那么多pont的优点以及使用方式,那么pont的使用有没有什么缺点呢?缺点当然是有的。我们团队遇到的问题主要有: 1、当后端的接口类型声明更改(接口没改)的时候,对于前端的改动是毁灭性的,前端需要跟着后端的声明去改(如果只是接口请求链接改动的话,前端就无比轻松了,只需要重新生成接口文件即可); 2、当后端的swagger服务过多的时候,前端项目就会生成很多接口文件,项目打包构建起来的时候就会慢一些。ps:我们有一个项目同时调用了6个后台swagger的服务,这个时候有部分配置低的电脑有点吃不消了(如果要解决这个问题的话,可以通过事先声明调用的URL,然后模板根据这些URL生成部分接口文件); 当然,这些需要自己团队权衡利弊了。对于我来说:pont真香。今天的分享先这样了,有些不对的地方还望大佬们斧正,谢谢!

附录

本文中涉及到的代码仓库:

  1. 在vue-cli中使用pont在线demo

github.com/Barretem/vu…

  1. 在nuxtJs中使用pont

github.com/Barretem/nu…