@shixinde/apifox-swagger
v4.3.1
Published
从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具
Maintainers
Readme
@shixinde/apifox-swagger
一个用于从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具。
功能特性
- 🚀 支持从 Apifox 云端 API 导出文档
- 🏠 支持从本地 Apifox 客户端导出文档
- 📝 自动生成 TypeScript 类型定义
- 🔧 支持命令行工具和编程接口
- ⚙️ 支持配置文件
- 🛠️ 自动处理 OpenAPI 格式兼容性
安装
npm install @shixinde/apifox-swagger
# 或
yarn add @shixinde/apifox-swagger
# 或
pnpm add @shixinde/apifox-swagger使用方法
1. 命令行工具
安装后可以直接使用 apifox-swagger 命令:
云端模式(推荐)
# 基本用法 - 导出整个项目
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output
# 导出指定文件夹
apifox-swagger apifox-swagger --projectId 266643 --folderId 123456 --folderName "用户模块" --outdir ./output
# 使用 token 参数
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --token your-access-token
# 生成类型安全的 API 工具
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types
# 或使用环境变量
APIOFX_ACCESS_TOKEN=your-token apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types命令行选项
--projectId <projectId>: Apifox 项目 ID(云端导出时必需)--outdir <outdir>: 输出目录(默认:./apifox)--token <token>: 访问令牌(可替代环境变量)--local: 从本地 Apifox 客户端导出--folderId <folderId>: 指定文件夹 ID--folderName <folderName>: 指定文件夹名称
2. 编程接口
基本用法
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:
export default {
projectId: '266643',
outdir: './src/types',
useLocal: false,
// token: 'your-token', // 可选,也可通过环境变量设置
};创建导出脚本 scripts/export-api.js:
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 中添加脚本:
{
"scripts": {
"export-api": "node scripts/export-api.js"
}
}3. 环境变量
可以通过环境变量设置访问令牌:
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 参考
exportSwagger(options)
导出 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 访问令牌
- 登录 Apifox 网页版
- 进入个人设置 → API 访问令牌
- 创建新的访问令牌
- 复制令牌并妥善保存
获取项目 ID
- 在 Apifox 中打开你的项目
- 查看浏览器地址栏,URL 中包含项目 ID
- 例如:
https://www.apifox.cn/web/project/266643中的266643就是项目 ID
故障排除
云端模式问题
检查访问令牌:
- 确保
APIFOX_ACCESS_TOKEN环境变量已设置 - 或使用
--token参数传递令牌
- 确保
检查项目信息:
- 验证项目 ID 是否正确
- 确保有项目访问权限
网络连接:
- 验证网络连接是否正常
- 检查是否有代理或防火墙限制
- 确保有项目的访问权限
TypeScript 类型生成失败
- 检查 OpenAPI 文档格式是否正确
- 确保安装了所有必需的依赖
基本用法
# 生成类型文件
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types单个类型文件使用
// 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<paths, '/api/omo/course/order/new', 'post'>('/api/omo/course/order/new', 'post')
// 4. 提取类型信息
type OrderAPI = MakeURL<paths, '/api/omo/course/order/new', 'post'>
type RequestData = OrderAPI['jsonData'] // 请求体类型
type ResponseData = OrderAPI['responseData'] // 响应体类型
type QueryParams = OrderAPI['query'] // 查询参数类型
type PathParams = OrderAPI['pathParams'] // 路径参数类型多个类型文件使用
如果你有多个 API 模块,可以配置多个类型文件:
// apifox.config.js
export default {
types: {
outdir: './src/swagger',
// 多个类型文件配置
files: [
{ filename: 'user', projectId: '266643' },
{ filename: 'order', projectId: '2364644' },
{ filename: 'product', projectId: '2364645' }
]
}
};方式一:统一类型导入(推荐)
为了简化使用,建议创建一个统一的类型导入文件:
// 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'然后在业务代码中直接使用:
// 在业务代码中使用
import { makeURL, type MakeURL, type AllPaths } from '../types/api'
// 现在可以直接使用所有 API 路径,无需指定具体的 paths 类型
const userLogin = makeURL<AllPaths, '/api/user/login', 'post'>('/api/user/login', 'post')
const orderCreate = makeURL<AllPaths, '/api/order/create', 'post'>('/api/order/create', 'post')
const productList = makeURL<AllPaths, '/api/product/list', 'get'>('/api/product/list', 'get')
// 类型推断也会正常工作
type UserLoginAPI = MakeURL<AllPaths, '/api/user/login', 'post'>
type OrderCreateAPI = MakeURL<AllPaths, '/api/order/create', 'post'>
type ProductListAPI = MakeURL<AllPaths, '/api/product/list', 'get'>方式二:分别导入
如果需要明确区分不同模块的类型,也可以分别导入:
// 分别导入不同模块的类型
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<UserPaths, '/api/user/login', 'post'>
const userLogin = makeURL<UserPaths, '/api/user/login', 'post'>('/api/user/login', 'post')
// 订单相关 API
type OrderCreateAPI = MakeURL<OrderPaths, '/api/order/create', 'post'>
const orderCreate = makeURL<OrderPaths, '/api/order/create', 'post'>('/api/order/create', 'post')
// 产品相关 API
type ProductListAPI = MakeURL<ProductPaths, '/api/product/list', 'get'>
const productList = makeURL<ProductPaths, '/api/product/list', 'get'>('/api/product/list', 'get')高级用法
1. 创建类型安全的 API 客户端
// 使用统一的类型导入
import {
makeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths,
type AllPaths
} from '../types/api'
// 通用 API 调用函数
function apiCall<
U extends keyof AllPaths,
M extends InferMethodFromPaths<AllPaths, U>
>(
url: U,
method: M,
data?: MakeRequest<AllPaths, U, M>
): Promise<MakeResponse<AllPaths, U, M>> {
// 实现 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 集成
import { useState, useEffect } from 'react'
import {
makeURL,
type MakeRequest,
type MakeResponse,
type InferMethodFromPaths,
type AllPaths
} from '../types/api'
function useAPI<U extends keyof AllPaths, M extends InferMethodFromPaths<AllPaths, U>>(
url: U,
method: M,
data?: MakeRequest<AllPaths, U, M>
) {
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<MakeResponse<AllPaths, U, M> | null>(null)
const [error, setError] = useState<Error | null>(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 <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{result?.orders.map(order => (
<div key={order.id}>{order.title}</div>
))}
</div>
)
}类型说明
makeURL<TPaths, U, M>(url, method): 创建类型安全的 URL 和方法组合,返回只读元组[url, method]MakeURL<TPaths, U, M>: 包含完整 API 信息的类型,包括请求参数和响应数据MakeRequest<TPaths, U, M>: 请求参数类型,包括 query、pathParams、formData、jsonDataMakeResponse<TPaths, U, M>: 响应数据类型InferMethodFromPaths<TPaths, U>: 从路径推断可用的 HTTP 方法paths: 从 Swagger 文档生成的所有 API 路径的类型定义
重要提示:
makeURL函数主要用于运行时 URL 构建,返回元组类型- 进行类型推断时,应直接使用
MakeURL<TPaths, U, M>类型,而不是从makeURL函数返回值推断 - 错误用法:
type API = MakeURL<UserPaths, (typeof makeURL(...))[0], (typeof makeURL(...))[1]> - 正确用法:
type API = MakeURL<UserPaths, '/api/path', 'get'>
其中:
TPaths: 从 Swagger 文档导入的 paths 类型U: API 路径,如/api/user/loginM: HTTP 方法,如get、post等
最佳实践
- 统一类型导入:创建
src/types/api.ts文件,统一导入和导出所有 API 类型,避免在每个业务文件中重复导入多个 paths 类型 - 统一的 API 客户端:创建一个统一的 API 调用函数,所有接口调用都通过它进行
- 类型复用:将常用的类型定义导出,在多个文件中复用
- 错误处理:结合 TypeScript 的联合类型,优雅地处理 API 错误
- 自动更新:将类型生成集成到构建流程中,确保类型定义始终是最新的
- 模块化管理:对于大型项目,可以按业务模块分别生成类型文件,然后通过统一的类型文件合并导出
许可证
MIT
完整使用示例
以下是一个完整的使用示例,展示如何在实际项目中使用类型安全的 API 工具:
// 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<UserPaths, '/api/org/class/list', 'get'>
type ClassCreateAPI = MakeURL<UserPaths, '/api/org/class/create', 'post'>
type ClassUpdateAPI = MakeURL<UserPaths, '/api/org/class/update', 'put'>
// 注意:如果出现 "类型不能赋给类型never的参数" 错误,请检查:
// 1. 是否正确导入了 paths 类型:import type { paths as UserPaths } from '../../output/swagger/all'
// 2. 路径是否存在于 Swagger 文档中
// 3. HTTP 方法是否正确
// 4. 是否已经运行了代码生成命令:npx apifox-swagger
// 创建类型安全的 URL
const classListUrl = makeURL<UserPaths, '/api/org/class/list', 'get'>('/api/org/class/list', 'get')
const classCreateUrl = makeURL<UserPaths, '/api/org/class/create', 'post'>('/api/org/class/create', 'post')
const classUpdateUrl = makeURL<UserPaths, '/api/org/class/update', 'put'>('/api/org/class/update', 'put')
// 更简洁的类型推断方式
// 注意:makeURL 函数返回的是元组,不能直接用于类型推断
// 正确的方式是直接使用 MakeURL 类型
type ClassListURL = MakeURL<UserPaths, '/api/org/class/list', 'get'>;
// 如果需要运行时的 URL 构建,可以这样使用:
const url = makeURL<UserPaths, '/api/org/class/list', 'get'>('/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<UserPaths, '/api/org/class/create', 'post'>;
type CreateRequest = CreateURL['jsonData']; // 创建班级的请求数据类型
type CreateResponse = CreateURL['responseData']; // 创建班级的响应数据类型
type UpdateURL = MakeURL<UserPaths, '/api/org/class/update', 'put'>;
type UpdateRequest = UpdateURL['jsonData']; // 更新班级的请求数据类型
type UpdatePathParams = UpdateURL['pathParams']; // 更新班级的路径参数类型(如 id)
// 带查询参数的 API 示例
type SearchURL = MakeURL<UserPaths, '/api/org/class/search', 'get'>;
type SearchQuery = SearchURL['query']; // 搜索查询参数类型(如 keyword, page, size)
type SearchResponse = SearchURL['responseData']; // 搜索响应数据类型
// 文件上传 API 示例
type UploadURL = MakeURL<UserPaths, '/api/org/class/upload', 'post'>;
type UploadFormData = UploadURL['formData']; // 文件上传表单数据类型
type UploadResponse = UploadURL['responseData']; // 文件上传响应数据类型
// 如果需要运行时 URL 构建,可以这样使用:
const createUrl = makeURL<UserPaths, '/api/org/class/create', 'post'>('/api/org/class/create', 'post');
const updateUrl = makeURL<UserPaths, '/api/org/class/update', 'put'>('/api/org/class/update', 'put');
// 通用 API 调用函数
function apiCall<
U extends keyof UserPaths,
M extends InferMethodFromPaths<UserPaths, U>
>(
url: U,
method: M,
data?: MakeRequest<UserPaths, U, M>
): Promise<MakeResponse<UserPaths, U, M>> {
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<UserPaths, '/api/org/class/list', 'get'>;
classCreate: MakeURL<UserPaths, '/api/org/class/create', 'post'>;
classUpdate: MakeURL<UserPaths, '/api/org/class/update', 'put'>;
classUpload: MakeURL<UserPaths, '/api/org/class/upload', 'post'>;
};
// 提取所有响应类型
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<UserPaths, '/api/org/class/list', 'get'>['query']) {
return apiCall('/api/org/class/list', 'get', { query: params })
},
// 创建班级
async create(data: MakeRequest<UserPaths, '/api/org/class/create', 'post'>['jsonData']) {
return apiCall('/api/org/class/create', 'post', { jsonData: data })
},
// 更新班级
async update(
id: string,
data: MakeRequest<UserPaths, '/api/org/class/update', 'put'>['jsonData']
) {
return apiCall('/api/org/class/update', 'put', {
pathParams: { id },
jsonData: data
})
}
}
// React Hook 示例
import { useState, useEffect } from 'react'
export function useClassList(params?: MakeRequest<UserPaths, '/api/org/class/list', 'get'>['query']) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState<MakeResponse<UserPaths, '/api/org/class/list', 'get'> | null>(null)
const [error, setError] = useState<Error | null>(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 <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{data?.list?.map(item => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
))}
</div>
)
}
// 工具函数
function getToken(): string {
return localStorage.getItem('token') || ''
}贡献
欢迎提交 Issue 和 Pull Request!
