一、安装 openapi2ts
openapi2ts介绍:一个强大的工具,可以根据 OpenAPI 3.0 文档生成 TypeScript 请求代码。
官方地址:github.com/chenshuai21...
命令行执行下面命令
ts
npm i --save-dev @umijs/openapi
npm i --save-dev tslib
二、项目根目录配置
新建自定义的ts文件 在前端项目的根目录 下新建 openapi2ts.config.ts
,根据需要定制生成的代码
ts
export default {
requestLibPath: "import { request } from '@/utils/alova'",
schemaPath: 'http://localhost:9123/api/v3/api-docs',
serversPath: './src',
apiPrefix: '',
namespace: 'API',
}
三、alova 配置文件
对于alova安装,请自行搜索
项目根目录创建/src/utils/alova.ts
ts
import { createAlova } from 'alova'
import VueHook from 'alova/vue'
import adapterFetch from 'alova/fetch'
import { notification } from 'ant-design-vue'
// 创建 Alova 实例
const alovaInstance = createAlova({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:9123/api/',
statesHook: VueHook,
requestAdapter: adapterFetch(),
timeout: 10000,
// 请求拦截器
beforeRequest(method) {
// 添加 token 等认证信息
const token = localStorage.getItem('token')
if (token) {
method.config.headers = {
...method.config.headers,
'Authorization': `Bearer ${token}`
}
}
// 添加 Content-Type
if (method.type === 'POST' || method.type === 'PUT' || method.type === 'PATCH') {
method.config.headers = {
...method.config.headers,
'Content-Type': 'application/json'
}
}
},
// 响应拦截器
responded: {
async onSuccess(response: Response) {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json()
// 检查业务状态码
if (result.code !== undefined) {
switch (result.code) {
case 20000: // SUCCESS
return result
case 40000: // PARAMS_ERROR
notification.error({
message: '参数错误',
description: result.message || '请求参数错误'
})
throw new Error(result.message || '请求参数错误')
case 40100: // NOT_LOGIN_ERROR
notification.warning({
message: '未登录',
description: '请先登录后再操作'
})
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
throw new Error(result.message || '未登录')
case 40101: // NO_AUTH_ERROR
notification.error({
message: '权限不足',
description: result.message || '您没有权限执行此操作'
})
throw new Error(result.message || '权限不足')
case 40400: // NOT_FOUND_ERROR
notification.error({
message: '资源不存在',
description: result.message || '请求的资源不存在'
})
throw new Error(result.message || '资源不存在')
case 40300: // FORBIDDEN_ERROR
notification.error({
message: '禁止访问',
description: result.message || '禁止访问'
})
throw new Error(result.message || '禁止访问')
case 50000: // SYSTEM_ERROR
notification.error({
message: '系统错误',
description: result.message || '系统内部异常,请稍后重试'
})
throw new Error(result.message || '系统内部异常')
case 50001: // OPERATION_ERROR
notification.error({
message: '操作失败',
description: result.message || '操作失败,请重试'
})
throw new Error(result.message || '操作失败')
default:
notification.error({
message: '请求失败',
description: result.message || '未知错误'
})
throw new Error(result.message || '未知错误')
}
}
return result
},
onError(err: any) {
// 网络错误处理
console.error('Request failed:', err)
const status = err.response?.status || err.status
// 根据HTTP状态码进行处理
if (status === 401) {
notification.warning({
message: '未授权',
description: '请重新登录'
})
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
} else if (status === 403) {
notification.error({
message: '权限不足',
description: '您没有权限执行此操作'
})
} else if (status === 404) {
notification.error({
message: '资源不存在',
description: '请求的资源不存在'
})
} else if (status >= 500) {
notification.error({
message: '服务器错误',
description: '服务器内部错误,请稍后重试'
})
} else {
notification.error({
message: '网络错误',
description: err.message || '网络连接异常,请检查网络'
})
}
throw err
}
}
})
// 导出请求方法,根据method类型选择合适的alova方法
export const request = <TResponse = any>(url: string, options?: any) => {
const { method = 'GET', data, ...restOptions } = options || {}
switch (method.toUpperCase()) {
case 'POST':
return alovaInstance.Post(url, data, restOptions) as Promise<TResponse>
case 'PUT':
return alovaInstance.Put(url, data, restOptions) as Promise<TResponse>
case 'DELETE':
return alovaInstance.Delete(url, restOptions) as Promise<TResponse>
case 'PATCH':
return alovaInstance.Put(url, data, restOptions) as Promise<TResponse>
default:
return alovaInstance.Get(url, { ...restOptions, params: options?.params }) as Promise<TResponse>
}
}
export default alovaInstance
三、统一后端返回值工具类
在项目根目录创建一个script/update-api-response.js
ts
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// 获取当前脚本所在目录
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// API 目录路径(相对于项目根目录)
const projectRoot = path.join(__dirname, '..');
const apiDir = path.join(projectRoot, 'src', 'api');
// 排除的文件列表
const excludeFiles = [
'index.ts',
'typings.d.ts',
'fileTransferUtil.ts' // 工具类文件,可能不需要修改
];
// 正则表达式匹配 request<类型> 模式
const requestTypeRegex = /request<([^>]+)>/g;
/**
* 更新单个 API 文件
* @param {string} filePath - 文件路径
* @returns {object} - 更新结果 { updated: boolean, changeCount: number }
*/
function updateApiFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
// 查找所有匹配的内容
const matches = content.match(requestTypeRegex) || [];
if (matches.length === 0) {
return { updated: false, changeCount: 0 };
}
// 替换所有的 request<任何类型> 为 request<API.BaseResponseVoid>
const updatedContent = content.replace(requestTypeRegex, 'request<API.BaseResponseVoid>');
// 检查是否有变化
if (content !== updatedContent) {
fs.writeFileSync(filePath, updatedContent, 'utf8');
return { updated: true, changeCount: matches.length };
} else {
return { updated: false, changeCount: 0 };
}
} catch (error) {
console.error(`❌ 处理文件失败: ${path.basename(filePath)}`, error.message);
return { updated: false, changeCount: 0 };
}
}
/**
* 获取所有需要处理的 TypeScript 文件
* @returns {string[]} - 文件路径列表
*/
function getApiFiles() {
try {
const files = fs.readdirSync(apiDir);
return files
.filter(file => {
// 只处理 .ts 文件,排除 .d.ts 文件和指定的排除文件
return file.endsWith('.ts') &&
!file.endsWith('.d.ts') &&
!excludeFiles.includes(file);
})
.map(file => path.join(apiDir, file));
} catch (error) {
console.error(`❌ 读取 API 目录失败: ${error.message}`);
return [];
}
}
/**
* 主函数
*/
function main() {
console.log('🚀 开始批量更新 API 接口返回值类型...');
console.log(`📂 API 目录: ${apiDir}\n`);
// 检查 API 目录是否存在
if (!fs.existsSync(apiDir)) {
console.error(`❌ API 目录不存在: ${apiDir}`);
process.exit(1);
}
// 获取所有需要处理的文件
const apiFiles = getApiFiles();
if (apiFiles.length === 0) {
console.log('⚠️ 未找到需要处理的 TypeScript 文件');
return;
}
console.log(`📝 找到 ${apiFiles.length} 个文件需要处理:\n`);
let updatedCount = 0;
let totalInterfaces = 0;
// 处理每个文件
apiFiles.forEach(filePath => {
const fileName = path.basename(filePath);
console.log(`📄 正在处理: ${fileName}`);
const result = updateApiFile(filePath);
if (result.updated) {
updatedCount++;
totalInterfaces += result.changeCount;
console.log(`✅ 已更新: ${fileName} (${result.changeCount} 个接口)`);
} else if (result.changeCount === 0) {
console.log(`⏭️ 无接口需要更新: ${fileName}`);
} else {
console.log(`⏭️ 无需更新: ${fileName}`);
}
console.log('');
});
// 输出汇总信息
console.log('═'.repeat(50));
console.log('✨ 批量更新完成!');
console.log(`📊 统计信息:`);
console.log(` - 处理文件数: ${apiFiles.length}`);
console.log(` - 更新文件数: ${updatedCount}`);
console.log(` - 更新接口数: ${totalInterfaces}`);
if (updatedCount > 0) {
console.log('\n🎉 所有接口返回值已统一设置为 API.BaseResponseVoid');
} else {
console.log('\n✅ 所有文件均已是最新状态,无需更新');
}
}
// 运行主函数
main();
// 导出函数供其他模块使用
export { updateApiFile, getApiFiles };
3.1 功能说明
update-api-response.js
是一个自动化脚本,用于批量将 src/api/
目录下所有 TypeScript 文件中的接口返回值类型统一设置为 API.BaseResponseVoid
。 功能说明:
- 🔍 自动扫描 :自动扫描
src/api/
目录下的所有.ts
文件 - 🎯 智能过滤 :自动排除不需要处理的文件(如
index.ts
、typings.d.ts
等) - 🔄 批量替换 :将所有
request<任何类型>
替换为request<API.BaseResponseVoid>
- 📊 详细统计:提供处理结果的详细统计信息
- ✅ 安全处理:只修改需要修改的文件,避免不必要的变更
3.2 使用方法
方式一:使用 npm 脚本(推荐)
命令行执行
bash
npm run update-api
方式二:直接运行脚本
命令行执行
bash
node scripts/update-api-response.js
方式三:和openapi2ts脚本一起执行
修改 package.json
json
"openapi2ts": "openapi2ts && node scripts/update-api-response.js",