从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具
npm install @shixinde/apifox-swagger一个用于从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具。
- 🚀 支持从 Apifox 云端 API 导出文档
- 🏠 支持从本地 Apifox 客户端导出文档
- 📝 自动生成 TypeScript 类型定义
- 🔧 支持命令行工具和编程接口
- ⚙️ 支持配置文件
- 🛠️ 自动处理 OpenAPI 格式兼容性
``bash`
npm install @shixinde/apifox-swagger或
yarn add @shixinde/apifox-swagger或
pnpm add @shixinde/apifox-swagger
安装后可以直接使用 apifox-swagger 命令:
#### 云端模式(推荐)
`bash基本用法 - 导出整个项目
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output
#### 命令行选项
-
--projectId : Apifox 项目 ID(云端导出时必需)
- --outdir : 输出目录(默认:./apifox)
- --token : 访问令牌(可替代环境变量)
- --local: 从本地 Apifox 客户端导出
- --folderId : 指定文件夹 ID
- --folderName : 指定文件夹名称
$3
#### 基本用法
`javascript
import { exportSwagger } from '@shixinde/apifox-swagger';// 从云端导出
const result = await exportSwagger({
projectId: '266643',
token: 'your-access-token',
outdir: './output',
useLocal: false
});
// 从云端导出并生成类型文件
const result = await exportSwagger({
projectId: '266643',
token: 'your-access-token',
outdir: './output',
useLocal: false,
// generateTypes: true
});
// 从本地客户端导出
const result = await exportSwagger({
outdir: './output',
useLocal: true
});
`#### 使用配置文件
创建
apifox.config.js:`javascript
export default {
projectId: '266643',
outdir: './src/types',
useLocal: false,
// token: 'your-token', // 可选,也可通过环境变量设置
};
`创建导出脚本
scripts/export-api.js:`javascript
import { exportSwagger } from '@shixinde/apifox-swagger';
import config from '../apifox.config.js';async function runExport() {
try {
const result = await exportSwagger({
...config,
token: config.token || process.env.APIFOX_ACCESS_TOKEN
});
console.log('导出成功:', result);
} catch (error) {
console.error('导出失败:', error.message);
}
}
runExport();
`在
package.json 中添加脚本:`json
{
"scripts": {
"export-api": "node scripts/export-api.js"
}
}
`$3
可以通过环境变量设置访问令牌:
`bash
export APIFOX_ACCESS_TOKEN=your-access-token
`或在
.env 文件中:`
APIFOX_ACCESS_TOKEN=your-access-token
APIFOX_PROJECT_ID=266643
`输出文件
工具会在指定的输出目录下创建
swagger 文件夹,包含:-
all.json: OpenAPI/Swagger JSON 文档
- all.ts: TypeScript 类型定义文件-
index.ts: 类型安全的 API 工具,包含 makeURL 函数和相关类型定义如果指定了文件夹名称,文件名会使用文件夹名称替代
all。API 参考
$3
导出 Swagger 文档的主要函数。
#### 参数
-
options.projectId (string): Apifox 项目 ID(云端导出时必需)
- options.token (string): 访问令牌(可选,优先级高于环境变量)
- options.outdir (string): 输出目录
- options.useLocal (boolean): 是否使用本地客户端(默认:false)
- options.folderId (string): 文件夹 ID(可选)
- options.folderName (string): 文件夹名称(可选)
#### 返回值
返回一个 Promise,解析为包含导出信息的对象。
获取 Apifox 访问令牌
1. 登录 Apifox 网页版
2. 进入个人设置 → API 访问令牌
3. 创建新的访问令牌
4. 复制令牌并妥善保存
获取项目 ID
1. 在 Apifox 中打开你的项目
2. 查看浏览器地址栏,URL 中包含项目 ID
3. 例如:
https://www.apifox.cn/web/project/266643 中的 266643 就是项目 ID故障排除
$3
1. 检查访问令牌:
- 确保
APIFOX_ACCESS_TOKEN 环境变量已设置
- 或使用 --token 参数传递令牌2. 检查项目信息:
- 验证项目 ID 是否正确
- 确保有项目访问权限
3. 网络连接:
- 验证网络连接是否正常
- 检查是否有代理或防火墙限制
- 确保有项目的访问权限
$3
1. 检查 OpenAPI 文档格式是否正确
2. 确保安装了所有必需的依赖
$3
`bash
生成类型文件
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types
`#### 单个类型文件使用
`typescript
// 1. 导入生成的 paths 类型
import type { paths } from '../swagger/all'// 2. 导入类型工具
import {
makeURL,
type MakeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths
} from '@shixinde/apifox-swagger/types'
// 3. 创建类型安全的 URL(传入 paths 类型)
const orderUrl = makeURL('/api/omo/course/order/new', 'post')
// 4. 提取类型信息
type OrderAPI = MakeURL
type RequestData = OrderAPI['jsonData'] // 请求体类型
type ResponseData = OrderAPI['responseData'] // 响应体类型
type QueryParams = OrderAPI['query'] // 查询参数类型
type PathParams = OrderAPI['pathParams'] // 路径参数类型
`#### 多个类型文件使用
如果你有多个 API 模块,可以配置多个类型文件:
`javascript
// apifox.config.js
export default {
types: {
outdir: './src/swagger',
// 多个类型文件配置
files: [
{ filename: 'user', projectId: '266643' },
{ filename: 'order', projectId: '2364644' },
{ filename: 'product', projectId: '2364645' }
]
}
};
`##### 方式一:统一类型导入(推荐)
为了简化使用,建议创建一个统一的类型导入文件:
`typescript
// src/types/api.ts - 创建统一的类型导入文件
import type { paths as UserPaths } from '../swagger/user'
import type { paths as OrderPaths } from '../swagger/order'
import type { paths as ProductPaths } from '../swagger/product'// 合并所有 API 路径类型
export type AllPaths = UserPaths & OrderPaths & ProductPaths
// 重新导出工具类型
export {
makeURL,
type MakeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths
} from '@shixinde/apifox-swagger/types'
`然后在业务代码中直接使用:
`typescript
// 在业务代码中使用
import { makeURL, type MakeURL, type AllPaths } from '../types/api'// 现在可以直接使用所有 API 路径,无需指定具体的 paths 类型
const userLogin = makeURL('/api/user/login', 'post')
const orderCreate = makeURL('/api/order/create', 'post')
const productList = makeURL('/api/product/list', 'get')
// 类型推断也会正常工作
type UserLoginAPI = MakeURL
type OrderCreateAPI = MakeURL
type ProductListAPI = MakeURL
`##### 方式二:分别导入
如果需要明确区分不同模块的类型,也可以分别导入:
`typescript
// 分别导入不同模块的类型
import type { paths as UserPaths } from '../swagger/user'
import type { paths as OrderPaths } from '../swagger/order'
import type { paths as ProductPaths } from '../swagger/product'import {
makeURL,
type MakeURL,
type MakeRequest,
type MakeResponse
} from '@shixinde/apifox-swagger/types'
// 用户相关 API
type UserLoginAPI = MakeURL
const userLogin = makeURL('/api/user/login', 'post')
// 订单相关 API
type OrderCreateAPI = MakeURL
const orderCreate = makeURL('/api/order/create', 'post')
// 产品相关 API
type ProductListAPI = MakeURL
const productList = makeURL('/api/product/list', 'get')
`$3
#### 1. 创建类型安全的 API 客户端
`typescript
// 使用统一的类型导入
import {
makeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths,
type AllPaths
} from '../types/api'// 通用 API 调用函数
function apiCall<
U extends keyof AllPaths,
M extends InferMethodFromPaths
>(
url: U,
method: M,
data?: MakeRequest
): Promise> {
// 实现 API 调用逻辑
return fetch(url, {
method: method as string,
headers: {
'Content-Type': 'application/json',
},
body: data?.jsonData ? JSON.stringify(data.jsonData) : undefined,
}).then(res => res.json())
}
// 使用示例
async function createOrder() {
const result = await apiCall('/api/omo/course/order/new', 'post', {
jsonData: {
// 这里会有完整的类型提示
courseId: '123',
quantity: 1
}
})
// result 也有正确的类型
console.log(result.orderId)
}
`#### 2. React Hook 集成
`typescript
import { useState, useEffect } from 'react'
import {
makeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths,
type AllPaths
} from '../types/api'function useAPI>(
url: U,
method: M,
data?: MakeRequest
) {
const [loading, setLoading] = useState(false)
const [result, setResult] = useState | null>(null)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
apiCall(url, method, data)
.then(setResult)
.catch(setError)
.finally(() => setLoading(false))
}, [url, method, data])
return { loading, result, error }
}
// 在组件中使用
function OrderComponent() {
const { loading, result, error } = useAPI('/api/omo/course/order/list', 'get', {
query: { page: 1, size: 10 }
})
if (loading) return
Loading...
if (error) return Error: {error.message}
return (
{result?.orders.map(order => (
{order.title}
))}
)
}
`$3
-
makeURL: 创建类型安全的 URL 和方法组合,返回只读元组 [url, method]
- MakeURL: 包含完整 API 信息的类型,包括请求参数和响应数据
- MakeRequest: 请求参数类型,包括 query、pathParams、formData、jsonData
- MakeResponse: 响应数据类型
- InferMethodFromPaths: 从路径推断可用的 HTTP 方法
- paths: 从 Swagger 文档生成的所有 API 路径的类型定义重要提示:
-
makeURL 函数主要用于运行时 URL 构建,返回元组类型
- 进行类型推断时,应直接使用 MakeURL 类型,而不是从 makeURL 函数返回值推断
- 错误用法:type API = MakeURL
- 正确用法:type API = MakeURL其中:
-
TPaths: 从 Swagger 文档导入的 paths 类型
- U: API 路径,如 /api/user/login
- M: HTTP 方法,如 get、post 等$3
1. 统一类型导入:创建
src/types/api.ts 文件,统一导入和导出所有 API 类型,避免在每个业务文件中重复导入多个 paths 类型
2. 统一的 API 客户端:创建一个统一的 API 调用函数,所有接口调用都通过它进行
3. 类型复用:将常用的类型定义导出,在多个文件中复用
4. 错误处理:结合 TypeScript 的联合类型,优雅地处理 API 错误
5. 自动更新:将类型生成集成到构建流程中,确保类型定义始终是最新的
6. 模块化管理:对于大型项目,可以按业务模块分别生成类型文件,然后通过统一的类型文件合并导出许可证
MIT
完整使用示例
以下是一个完整的使用示例,展示如何在实际项目中使用类型安全的 API 工具:
`typescript
// src/api/types.ts - 导入生成的类型
// 第一步:导入生成的 paths 类型
import type { paths as UserPaths } from '../../output/swagger/all'// 第二步:导入类型工具
import {
makeURL,
type MakeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths
} from '@shixinde/apifox-swagger/types'
// 第三步:定义具体的 API 类型
type ClassListAPI = MakeURL
type ClassCreateAPI = MakeURL
type ClassUpdateAPI = MakeURL
// 注意:如果出现 "类型不能赋给类型never的参数" 错误,请检查:
// 1. 是否正确导入了 paths 类型:import type { paths as UserPaths } from '../../output/swagger/all'
// 2. 路径是否存在于 Swagger 文档中
// 3. HTTP 方法是否正确
// 4. 是否已经运行了代码生成命令:npx apifox-swagger
// 创建类型安全的 URL
const classListUrl = makeURL('/api/org/class/list', 'get')
const classCreateUrl = makeURL('/api/org/class/create', 'post')
const classUpdateUrl = makeURL('/api/org/class/update', 'put')
// 更简洁的类型推断方式
// 注意:makeURL 函数返回的是元组,不能直接用于类型推断
// 正确的方式是直接使用 MakeURL 类型
type ClassListURL = MakeURL;
// 如果需要运行时的 URL 构建,可以这样使用:
const url = makeURL('/api/org/class/list', 'get');
// 提取各种类型
type Response = ClassListURL['responseData']; // 响应数据类型
type Query = ClassListURL['query']; // 查询参数类型
type PathParams = ClassListURL['pathParams']; // 路径参数类型
type FormData = ClassListURL['formData']; // 表单数据类型
type JsonData = ClassListURL['jsonData']; // JSON 数据类型
// 导出具体的业务类型
export type Classes = Response['data']; // 班级列表数据类型
export type ClassItem = Classes[0]; // 单个班级数据类型
// 示例:不同 API 的类型提取
// 正确的方式:直接使用 MakeURL 类型
type CreateURL = MakeURL;
type CreateRequest = CreateURL['jsonData']; // 创建班级的请求数据类型
type CreateResponse = CreateURL['responseData']; // 创建班级的响应数据类型
type UpdateURL = MakeURL;
type UpdateRequest = UpdateURL['jsonData']; // 更新班级的请求数据类型
type UpdatePathParams = UpdateURL['pathParams']; // 更新班级的路径参数类型(如 id)
// 带查询参数的 API 示例
type SearchURL = MakeURL;
type SearchQuery = SearchURL['query']; // 搜索查询参数类型(如 keyword, page, size)
type SearchResponse = SearchURL['responseData']; // 搜索响应数据类型
// 文件上传 API 示例
type UploadURL = MakeURL;
type UploadFormData = UploadURL['formData']; // 文件上传表单数据类型
type UploadResponse = UploadURL['responseData']; // 文件上传响应数据类型
// 如果需要运行时 URL 构建,可以这样使用:
const createUrl = makeURL('/api/org/class/create', 'post');
const updateUrl = makeURL('/api/org/class/update', 'put');
// 通用 API 调用函数
function apiCall<
U extends keyof UserPaths,
M extends InferMethodFromPaths
>(
url: U,
method: M,
data?: MakeRequest
): Promise> {
const baseURL = 'https://api.example.com'
return fetch(
${baseURL}${url}, {
method: method as string,
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${getToken()}
},
body: data?.jsonData ? JSON.stringify(data.jsonData) : undefined,
}).then(res => {
if (!res.ok) {
throw new Error(HTTP ${res.status}: ${res.statusText})
}
return res.json()
})
}// 实际使用示例
async function examples() {
// 1. 获取班级列表(GET 请求,带查询参数)
const classList = await apiCall('/api/org/class/list', 'get', {
query: {
page: 1,
size: 10,
keyword: '高一'
}
});
// classList 的类型会被自动推断为对应的响应类型
console.log(classList.data); // 类型安全的访问
// 2. 创建班级(POST 请求,带 JSON 数据)
const newClass = await apiCall('/api/org/class/create', 'post', {
jsonData: {
name: '高一(1)班',
teacherId: 123,
capacity: 50
}
});
console.log(newClass.data.id); // 新创建班级的 ID
// 3. 更新班级(PUT 请求,带路径参数和 JSON 数据)
const updatedClass = await apiCall('/api/org/class/update', 'put', {
pathParams: {
id: 456
},
jsonData: {
name: '高一(2)班',
capacity: 45
}
});
console.log(updatedClass.message); // 更新结果消息
// 4. 文件上传(POST 请求,带表单数据)
const uploadResult = await apiCall('/api/org/class/upload', 'post', {
formData: {
file: new File([''], 'students.xlsx'),
classId: 789
}
});
console.log(uploadResult.data.uploadedCount); // 上传的学生数量
}
// 类型提取的高级用法
type ApiEndpoints = {
// 班级相关 API
classList: MakeURL;
classCreate: MakeURL;
classUpdate: MakeURL;
classUpload: MakeURL;
};
// 提取所有响应类型
type ApiResponses = {
[K in keyof ApiEndpoints]: ApiEndpoints[K]['responseData'];
};
// 提取所有请求类型
type ApiRequests = {
[K in keyof ApiEndpoints]: {
query: ApiEndpoints[K]['query'];
pathParams: ApiEndpoints[K]['pathParams'];
jsonData: ApiEndpoints[K]['jsonData'];
formData: ApiEndpoints[K]['formData'];
};
};
// 业务层类型定义
export type ClassListResponse = ApiResponses['classList'];
export type ClassCreateRequest = ApiRequests['classCreate']['jsonData'];
export type ClassUpdateRequest = ApiRequests['classUpdate']['jsonData'];
export type ClassUploadFormData = ApiRequests['classUpload']['formData'];
// 具体的 API 调用函数
export const classAPI = {
// 获取班级列表
async getList(params?: MakeRequest['query']) {
return apiCall('/api/org/class/list', 'get', { query: params })
},
// 创建班级
async create(data: MakeRequest['jsonData']) {
return apiCall('/api/org/class/create', 'post', { jsonData: data })
},
// 更新班级
async update(
id: string,
data: MakeRequest['jsonData']
) {
return apiCall('/api/org/class/update', 'put', {
pathParams: { id },
jsonData: data
})
}
}
// React Hook 示例
import { useState, useEffect } from 'react'
export function useClassList(params?: MakeRequest['query']) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState | null>(null)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
classAPI.getList(params)
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [params])
return { loading, data, error, refetch: () => classAPI.getList(params) }
}
// 在组件中使用
function ClassListComponent() {
const { loading, data, error } = useClassList({ page: 1, size: 10 })
const handleCreate = async (formData: any) => {
try {
await classAPI.create({
name: formData.name,
description: formData.description
// TypeScript 会提供完整的类型提示
})
// 重新获取列表
} catch (err) {
console.error('创建失败:', err)
}
}
if (loading) return
Loading...
if (error) return Error: {error.message}
return (
{data?.list?.map(item => (
{item.name}
{item.description}
))}
)
}// 工具函数
function getToken(): string {
return localStorage.getItem('token') || ''
}
``欢迎提交 Issue 和 Pull Request!