自动化生成前端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 自动化生成的理解。欢迎留言分享你的想法与经验!

相关推荐
EnCi Zheng13 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen17 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技17 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人28 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实29 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha40 分钟前
三目运算符
linux·服务器·前端
晓晨的博客1 小时前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing1 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习