使用orval自动拉取swagger文档并生成ts接口

orval 介绍

orval 是一个开源的基于 OpenAPI 的接口测试工具,它可以自动从 Swagger/OpenAPI 文档中拉取接口信息并生成生成前端接口代码。

官方文档:orval.dev/

安装

zsh 复制代码
pnpm add orval

配置

这里以 swagger 文档为例:

先创建一个 api.gen.config.js 文件,用来配置 swagger 信息:

js 复制代码
const path = require('path')

module.exports = [
  {
    name: 'task', // 接口生成的文件夹名称
    url: 'https://orion-gateway.sit.sf-express.com/task/v2/api-docs', // swagger 文档地址
    filters: { tags: [/^(?!gexAoi$)/] }, // 过滤某些接口
    outDir: path.resolve(__dirname, './src/api/task/index.ts'), // 输出文件路径
  },
]

创建一个 orval 的配置文件 orval.config.ts:

ts 复制代码
import type { Options } from 'orval'

import config from '../../api.gen.config.js'

function initConfig() {
  const result: { [key: string]: Options } = {}

  if (!Array.isArray(config)) {
    console.error('config must be an array')
    return
  }

  config.forEach(item => {
    const { name, filters, outDir } = item

    const options: Options = {
      input: {
        // target: url,
        target: `../swagger/${name}.json`,
        filters,
        override: {
          transformer: './transformer.cjs',
        },
      },
      output: {
        target: outDir,
        mode: 'tags',
        override: {
          mutator: {
            path: './mutator.ts',
            name: 'request',
          },
        },
      },
      hooks: {
        afterAllFilesWrite: ['npx prettier --write'],
      },
    }
    result[name] = options
  })

  return result
}

export default initConfig()

如果 swagger 文档可以直接通过请求获取,不需要鉴权,那么 input.target 字段可以直接填写 swagger 文档的地址。

如果需要鉴权,那么可以先把 swagger 文档下载到本地,然后配置 input.target 字段为本地文件路径。

获取 swagger 文档

javascript 复制代码
// scripts/fetch-swagger.ts
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

import config from '../../api.gen.config.js'

async function fetchSwagger() {
  config.forEach(async item => {
    const { name, url } = item
    const outputPath = path.resolve(__dirname, `../swagger/${name}.json`)
    const auth = 'Basic ' + Buffer.from('admin:Uac@2024').toString('base64')
    const res = await fetch(url, {
      headers: {
        Authorization: auth,
      },
    })

    const data = await res.json()

    // ✅ 创建目录
    fs.mkdirSync(path.dirname(outputPath), { recursive: true })

    // ✅ 写入文件
    fs.writeFileSync(outputPath, JSON.stringify(data, null, 2))
    console.log(`✅ Swagger downloaded to ./swagger/${name}.json`)
  })
}

fetchSwagger().catch(console.error)

生成接口

如果 香自定义请求方法,可以新建 mutator.ts, 并在 orval.config.ts 中配置到 output.override 字段。

typescript 复制代码
import axios from 'axios'
import type { AxiosRequestConfig } from 'axios'

export const request = <T>(config: AxiosRequestConfig, options?: AxiosRequestConfig): Promise<T> => {
  const promise = axios.request({
    ...config,
    ...options,
  })

  return promise
}

如果想修改 swagger 中的内容,比如可以通过修改 tags 的名字来修改生成的文件名。

可以新建 transformer.cjs,并在 orval.config.ts 中配置到 input.override 字段:

javascript 复制代码
// 名字是否包含中文
function includeChinese(str) {
  return /.*[\u4e00-\u9fa5]+.*$/.test(str)
}

// 去除所有空格然后首字母小写
function formatVariable(str) {
  return str.replace(/\s/g, '').replace(/^[A-Z]/, s => s.toLowerCase())
}

// 是否是合法的js变量名
function isValidVariableName(str) {
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str)
}

// 从路径中获取文件名
function getModuleNameFromPath(path) {
  const parts = path.split('/').filter(Boolean) // 去掉空
  // 假设格式固定为 /task/moduleName/apiName
  return parts.length >= 2 ? formatVariable(parts[1]) : null
}

function formatTagName(name, description) {
  if (includeChinese(name) || !isValidVariableName(name)) {
    if (includeChinese(description) || !isValidVariableName(description) || !description) {
      return null
    } else {
      return formatVariable(description)
    }
  }
  return formatVariable(name)
}

module.exports = inputSchema => {
  const tags = inputSchema.tags.reduce((acc, { name, description }) => {
    if (includeChinese(name) || !isValidVariableName(name)) {
      // 默认先尝试 description
      let formatName = formatTagName(name, description) ? formatTagName(name, description) : null
      // 如果 description 没有,就从 paths 里找 moduleName
      if (!formatName) {
        const pathEntry = Object.entries(inputSchema.paths).find(([_, pathItem]) =>
          Object.values(pathItem).some(operation => operation.tags.includes(name))
        )

        if (pathEntry) {
          const [path] = pathEntry
          formatName = getModuleNameFromPath(path) ? getModuleNameFromPath(path) : formatVariable(name)
        }
      }

      console.log(name, '---->', formatName)

      return { ...acc, [name]: formatName }
    } else {
      return acc
    }
  }, {})

  return {
    ...inputSchema,
    tags: inputSchema.tags.map(el => (tags[el.name] ? { ...el, name: tags[el.name] } : el)),
    paths: Object.entries(inputSchema.paths).reduce(
      (acc, [path, pathItem]) => ({
        ...acc,
        [path]: Object.entries(pathItem).reduce(
          (pathItemAcc, [verb, operation]) => ({
            ...pathItemAcc,
            [verb]: {
              ...operation,
              tags: operation.tags.map(tag => (tags[tag] ? tags[tag] : tag)),
            },
          }),
          {}
        ),
      }),
      {}
    ),
  }
}

最后新建入口文件 index.js

javascript 复制代码
import { fileURLToPath, URL } from 'node:url'

import orval from 'orval'

const configPath = fileURLToPath(new URL('./orval.config.ts', import.meta.url))

orval.generate(configPath)

最后在 package.json 中添加脚本:

json 复制代码
{
  "scripts": {
    "api:gen": "node ./api-gen/src/fetch-swagger.js && node ./api-gen/src/index.js"
  }
}

执行 pnpm run api:gen 即可生成接口文件。

相关推荐
trsoliu几秒前
2025年Web前端最新趋势:React基金会成立、AI编码助手崛起与Astro极速搜索
前端·javascript·react.js
一 乐3 分钟前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·商城推荐系统
亿元程序员9 分钟前
为什么游戏公司现在都喜欢用protobuf?
前端
鹏多多11 分钟前
React瀑布流Masonry-Layout插件全方位指南:从基础到进阶实践
前端·javascript·react.js
fruge18 分钟前
前端数据可视化实战:Chart.js vs ECharts 深度对比与实现指南
前端·javascript·信息可视化
卓码软件测评27 分钟前
借助大语言模型实现高效测试迁移:Airbnb的大规模实践
开发语言·前端·javascript·人工智能·语言模型·自然语言处理
IT_陈寒32 分钟前
SpringBoot 3.0实战:这套配置让我轻松扛住百万并发,性能提升300%
前端·人工智能·后端
♡喜欢做梦33 分钟前
Spring Web MVC 入门秘籍:从概念到实践的快速通道(上)
前端·spring·mvc
Dragon Wu40 分钟前
Taro 自定义tab栏和自定义导航栏
前端·javascript·小程序·typescript·前端框架·taro
艾小码1 小时前
2025年前端菜鸟自救指南:从零搭建专业开发环境
前端·javascript