自动化生成前端API请求代码:基于YApi的高效开发实践

用 TypeScript 实现 API 方法代码生成工具

在现代前端开发中,API 的调用几乎是每个项目中不可或缺的一部分。为了提高开发效率和代码的可维护性,通常需要生成 API 请求的相关代码。然而,手写这些 API 方法可能会变得繁琐且容易出错。为此,我们可以利用工具自动生成 API 方法代码,确保代码的统一性和规范性。

今天,我们就来一起探讨如何通过 TypeScript 和一些流行的工具,快速生成高效的 API 请求代码,特别是在处理大型项目时,这种自动化方法能大大提升工作效率。

github:github.com/yangboxun/y...

背景与需求

在处理 API 接口时,我们通常会面临以下几个常见的需求:

  1. 接口参数动态生成:API 请求的参数往往是动态的,不同的接口可能有不同的请求类型(如 JSON、Form Data 等)。
  2. 代码重复:对于大量的接口方法,手动书写每个 API 的请求代码显得非常冗余,容易出错。
  3. 接口数据变化:API 接口可能会随时发生变化,如何保证 API 请求代码与接口定义的同步?

解决方案:API 代码生成工具

我们可以创建一个 API 代码生成工具,根据从 API 文档中获取的接口信息,自动生成对应的 API 请求方法。以下是实现过程的详细分析。

1. 工具结构和配置

首先,我们需要定义一个配置对象 ApiGeneratorOptions,包括 API 的基本信息、请求 token、目标语言等。

csharp 复制代码
interface ApiGeneratorOptions {
  baseUrl: string,  // API 基础 URL
  token: string,    // API token
  targetLanguage: string,  // 目标编程语言,支持 JavaScript 和 TypeScript
  apiTemplate?: string  // 自定义 API 模板
}

2. 获取 API 列表

通过发送 GET 请求从远程 API 获取接口列表,并以 JSON 格式返回。我们利用 axios 来处理 HTTP 请求。

javascript 复制代码
async fetchApiList() {
  try {
    const { data } = await axios.get(`${this.opts.baseUrl}/api/plugin/export`, {
      params: { 
        type: 'json',
        status: 'all',
        isWiki: 'false',
        token: this.opts.token
      },
    })
    return data || []
  } catch (error) {
    console.error('获取接口数据时出错:', error)
    throw error;
  }
}

3. 生成 API 方法代码

通过 ejs 模板引擎,我们可以动态生成 API 请求代码。在这里,我们为每个接口生成一个 API 请求方法,包括请求类型、路径、请求参数和响应类型等。

ini 复制代码
// 生成 API 方法代码
  async generateApiMethods(apiList: ApiInterface[]) {
    let apiMethods = [],
        tsTypes = []
    let defaultApiTemplate =  
    `
      /**
      * <%= title %>
      * @method <%= method %>
      * @path <%= path %>
      */
      export async function <%= methodName %>_<%= method.toLowerCase() %>(params){
        return request({
          url: '<%= path %>',
          method: '<%= method.toLowerCase() %>',
          data: params
        })
      }
    `
    if(this.opts.targetLanguage === 'typescript') {
      defaultApiTemplate = `
        /**
        * <%= title %>
        * @method <%= method %>
        * @path <%= path %>
        */
        <%= requestTypeDef %>
        <%= responseTypeDef %>
        export async function <%= methodName %>_<%= method.toLowerCase() %>(params:<%= requestType %>){
          return request<<%= responseType %>>({
            url: '<%= path %>',
            method: '<%= method.toLowerCase() %>',
            data: params
          })
        }
      `
    }
    for(let api of apiList) {
      let requestJsonSchema:any
      const { path, method, title, req_body_other, res_body, req_body_type, req_body_form, req_query,req_params } = api;
      const methodName = path
        .replace(/[^a-zA-Z0-9]/g, '_')
        .replace(/_+/g, '_')
        .replace(/^_|_$/g, '');
      if(isPostMethod(method)) {
        switch(req_body_type) {
          case 'form': 
          requestJsonSchema = convertToJsonSchema(req_body_form)
          break
          case 'json': 
          if (req_body_other) {
            requestJsonSchema = JSON.parse(req_body_other)
            if (requestJsonSchema.type === 'object') {
              // 将 additionalProperties 设为 false
              requestJsonSchema.additionalProperties = false
            }
          }
          break
          default:
            break
        }
      }
  
      // 处理查询数据
      if(req_query && req_query.length>0) {
        const queryJsonSchema = convertToJsonSchema(req_query)
        if(requestJsonSchema) {
          requestJsonSchema.properties = {
            ...requestJsonSchema.properties,
            ...queryJsonSchema.properties,
          }
          requestJsonSchema.required = [
            ...(Array.isArray(requestJsonSchema.required) ? requestJsonSchema.required : []),
            ...(Array.isArray(queryJsonSchema.required)
              ? queryJsonSchema.required
              : []),
          ]
          
        } else {
          requestJsonSchema = queryJsonSchema
        }
        
      }
  
      // 处理路径参数
      if (req_params && req_params.length) {
        const paramsJsonSchema = convertToJsonSchema(req_params)
          if(requestJsonSchema) {
            requestJsonSchema.properties = {
              ...requestJsonSchema.properties,
              ...paramsJsonSchema.properties,
            }
            requestJsonSchema.required = [
              ...(Array.isArray(requestJsonSchema.required) ? requestJsonSchema.required : []),
              ...(Array.isArray(paramsJsonSchema.required)
                ? paramsJsonSchema.required
                : []),
            ]
            
          } else {
            requestJsonSchema = paramsJsonSchema
          }
      }
  
      const requestType = changeCase.pascalCase(`Request_${methodName}`);
      const responseType = changeCase.pascalCase(`Response_${methodName}`);
      const requestTypeDef = await jsonSchemaToTypes(JSON.stringify(requestJsonSchema), requestType);
      const responseTypeDef = await jsonSchemaToTypes(res_body, responseType);
      
      const apiCode = ejs.render(this.opts?.apiTemplate || defaultApiTemplate, {
        title,
        method,
        path,
        methodName,
        requestTypeDef,
        responseTypeDef,
        requestType,
        responseType,
      })
        
      const tsTypesCode = `
        ${requestTypeDef}
        ${responseTypeDef}
      `
      apiMethods.push(apiCode)
      
      if(this.opts.targetLanguage === 'typescript') {
        tsTypes.push(
          tsTypesCode
        );
      }
      
    }
    const apiFormatCode = await formatCode(apiMethods.join('\n'))
    const typesFormatCode = await formatCode(tsTypes.join('\n'))
    return {
      codes: apiFormatCode,
      tsTypes: typesFormatCode
    }
  }

4. 生成最终代码

最后,我们通过调用 generateCode 方法来整合所有 API 接口,并生成最终的 API 请求代码。

javascript 复制代码
async generateCode() {
  const apiList: Category[] = await this.fetchApiList()

  if (apiList.length === 0) {
    console.error('未找到 API 数据')
    return;
  }

  for (let apis of apiList) {
    apis._apiCodes = await this.generateApiMethods(apis.list)
  }
  return apiList
}

5. 代码格式化

我们利用 formatCode 方法对生成的代码进行格式化,以保持代码风格的一致性。这个方法可以保证生成的代码符合团队的规范要求。

csharp 复制代码
// 格式化生成的代码
export function formatCode(code: string): Promise<string> {
  const options = {
    parser: 'typescript',
    singleQuote: true,
    semi: true,
  };
  return prettier.format(code, options);
}

const apiFormatCode = await formatCode(apiMethods.join('\n'))
const typesFormatCode = await formatCode(tsTypes.join('\n'))

最终效果

经过上述步骤,我们可以得到按需生成的 API 请求方法代码。例如,生成的一个 API 方法如下:

php 复制代码
/**
* 获取用户信息
* @method GET
* @path /user/{userId}
*/
export async function get_user(params: { userId: string }) {
  return request({
    url: '/user/' + params.userId,
    method: 'get',
    data: params
  })
}

同时,我们也会生成与之相关的 TypeScript 类型定义:

typescript 复制代码
export interface Request_get_user {
  userId: string;
}

export interface Response_get_user {
  id: string;
  name: string;
  email: string;
}

总结

通过本工具,我们可以实现 API 请求代码的自动化生成,极大地减少了手动编写的工作量,也能确保代码的一致性和准确性。特别是在面对多个接口和复杂的 API 请求时,这种自动化生成工具可以显著提高开发效率,降低出错率。

如果你正面临相似的需求,不妨尝试将这种自动化生成方法引入到你的开发流程中,相信会让你的工作变得更加高效!


希望这篇文章对你有所帮助,提升了你对 API 自动化生成的理解。欢迎留言分享你的想法与经验!

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师4 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学4 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端