hookehuyr

feat(apifox): 集成 Apifox API 管理系统并添加自动化同步工具

新增 Apifox API 集成功能,包括配置管理、自动化同步脚本和详细文档
- 添加 .env.apifox 配置文件模板,用于管理 API Token 和项目 ID
- 新增 apifox-to-openapi.js 脚本,自动从 Apifox 获取接口并转换为 OpenAPI 格式
- 新增 test-apifox-connection.js 脚本,用于测试 Apifox 连接和配置
- 更新 package.json,添加 api:sync 和 api:test 命令
- 新增 QUICKSTART_APIFOX.md 快速开始指南和详细集成文档
- 更新 .gitignore,保护敏感配置文件不被提交
# Apifox API 配置示例
# 复制此文件为 .env.apifox 并填写实际的凭证信息
# Apifox API Token(在 Apifox → 个人设置 → API Token 中创建)
# 格式: aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_APIFOX_TOKEN=aps-your_token_here
# Apifox 项目 ID(从 URL 中获取)
# 示例: https://app.apifox.com/project/12345678/api-12345
# 项目 ID: 12345678
VITE_APIFOX_PROJECT_ID=your_project_id_here
# API 数据缓存时间(秒),默认 1 小时
VITE_APIFOX_CACHE_TIME=3600
......@@ -10,6 +10,7 @@ yarn-error.log*
pnpm-debug.log*
.env.local
.env.*.local
.env.apifox
unpackage/
.history/
.tmp/
......
# 🚀 Apifox 集成快速开始
## 第一步:获取 Apifox 凭证
### 1.1 获取 API Token
1. 访问 [Apifox](https://app.apifox.com) 并登录
2. 点击右上角头像 → **个人设置**
3. 选择 **API Token****新建 Token**
4. 输入名称(如"Manulife WeApp")
5. 复制生成的 Token(格式:`aps-xxxxx`
### 1.2 获取项目 ID
在 Apifox 中打开您的项目,URL 格式为:
```
https://app.apifox.com/project/{PROJECT_ID}/...
```
复制 `{PROJECT_ID}` 部分。
## 第二步:配置项目
### 2.1 创建配置文件
在项目根目录执行:
```bash
cp .env.apifox.example .env.apifox
```
### 2.2 填写凭证信息
编辑 `.env.apifox` 文件:
```bash
# 替换为你的实际值
VITE_APIFOX_TOKEN=aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_APIFOX_PROJECT_ID=12345678
```
## 第三步:同步 API
运行同步命令:
```bash
pnpm api:sync
```
这个命令会:
1. ✅ 从 Apifox 获取所有接口
2. ✅ 生成 OpenAPI 文档到 `docs/api-specs/`
3. ✅ 生成 API 代码到 `src/api/`
## 第四步:使用 API
在组件中导入并使用:
```javascript
import { getUserInfoAPI } from '@/api/user'
// 调用 API
const res = await getUserInfoAPI({ userId: '123' })
if (res.code === 1) {
console.log('用户信息:', res.data)
}
```
## 📚 更多信息
详细文档请查看:[Apifox 集成完整指南](./docs/apifox-integration-guide.md)
## ❓ 常见问题
**Q: 提示"未找到 .env.apifox 文件"**
```bash
# 解决方案:创建配置文件
cp .env.apifox.example .env.apifox
```
**Q: 提示"HTTP 401"**
- 检查 API Token 是否正确
- 确认 Token 未过期
**Q: 提示"项目不存在"**
- 检查项目 ID 是否正确
- 确认账号有项目访问权限
---
**需要帮助?** 查看完整文档或联系项目组
# Apifox API 集成指南
## 📖 概述
本项目集成了 Apifox API 管理系统,可以自动从 Apifox 获取接口数据并生成 API 代码。
## 🎯 功能特性
-**自动同步**: 从 Apifox 获取所有接口数据
-**OpenAPI 转换**: 将 Apifox 数据转换为 OpenAPI 3.0 格式
-**代码生成**: 自动生成 JavaScript API 接口代码
-**类型安全**: 生成完整的 JSDoc 类型注释
-**变更检测**: 自动检测 API 变更并显示差异
-**Mock 数据**: 支持生成 Mock 数据(可选)
## 🚀 快速开始
### 1. 获取 Apifox 凭证
#### 1.1 获取 API Token
1. 登录 [Apifox](https://app.apifox.com)
2. 点击右上角头像 → **个人设置**
3. 选择 **API Token****新建 Token**
4. 输入 Token 名称(如"Manulife WeApp")
5. 复制生成的 Token(格式:`aps-xxxxxxxxxxxxx`
#### 1.2 获取项目 ID
项目 ID 是 Apifox 项目 URL 中的一部分:
```
https://app.apifox.com/project/{PROJECT_ID}/...
```
例如:`https://app.apifox.com/project/12345678/api-12345` 的项目 ID 是 `12345678`
### 2. 配置项目
#### 2.1 创建 `.env.apifox` 文件
在项目根目录创建 `.env.apifox` 文件:
```bash
VITE_APIFOX_TOKEN=aps-your_token_here
VITE_APIFOX_PROJECT_ID=your_project_id_here
```
#### 2.2 填写实际凭证
```bash
# Apifox API Token
VITE_APIFOX_TOKEN=aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Apifox 项目 ID
VITE_APIFOX_PROJECT_ID=12345678
```
⚠️ **重要**: 请勿将 `.env.apifox` 文件提交到 Git!
### 3. 同步 API
#### 方式 1: 使用 pnpm 命令(推荐)
```bash
pnpm api:sync
```
这个命令会:
1. 从 Apifox 获取所有接口数据
2. 转换为 OpenAPI 3.0 格式
3. 保存到 `docs/api-specs/` 目录
4. 自动运行 `api:generate` 生成 API 代码
#### 方式 2: 仅生成 OpenAPI 文档
```bash
node scripts/apifox-to-openapi.js
```
#### 方式 3: 仅从 OpenAPI 生成代码
```bash
pnpm api:generate
```
## 📂 项目结构
```
manulife-weapp/
├── .env.apifox # Apifox 凭证配置(不提交到 Git)
├── docs/
│ └── api-specs/ # OpenAPI 文档目录
│ ├── user/ # 按模块分类
│ │ ├── get-userinfo.md
│ │ └── post-login.md
│ └── product/
│ └── get-list.md
├── src/
│ └── api/ # 生成的 API 代码
│ ├── user.js # 用户模块 API
│ └── product.js # 产品模块 API
└── scripts/
├── apifox-to-openapi.js # Apifox → OpenAPI 转换器
└── generateApiFromOpenAPI.js # OpenAPI → API 代码生成器
```
## 📝 生成的代码示例
### OpenAPI 文档(`docs/api-specs/user/get-userinfo.md`)
```markdown
# 获取用户信息
获取当前登录用户的详细信息
## 接口信息
- **方法**: GET
- **路径**: /srv/?a=get_userinfo
- **标签**: user
## OpenAPI 规范
\`\`\`yaml
openapi: 3.0.0
info:
title: 获取用户信息
description: 获取当前登录用户的详细信息
version: '1.0.0'
paths:
/srv/:
get:
summary: 获取用户信息
description: 获取当前登录用户的详细信息
parameters:
- name: a
in: query
description: 接口动作
required: true
schema:
type: string
example: get_userinfo
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
code:
type: number
description: 状态码
msg:
type: string
description: 消息
data:
type: object
properties:
userId:
type: string
description: 用户 ID
userName:
type: string
description: 用户名
\`\`\`
```
### API 代码(`src/api/user.js`)
```javascript
/**
* @description: 获取用户信息
* @param {Object} params 请求参数
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
* userId: string; // 用户 ID
* userName: string; // 用户名
* };
* }>}
*/
export const getUserinfoAPI = (params) => fn(fetch.get(Api.GetUserinfo, params));
```
## 💡 在组件中使用
### 1. 导入 API
```javascript
import { getUserinfoAPI, postLoginAPI } from '@/api/user'
import { getProductListAPI } from '@/api/product'
```
### 2. 使用 API
```vue
<script setup>
import { ref, onMounted } from 'vue'
import { getUserinfoAPI } from '@/api/user'
const userInfo = ref(null)
const loading = ref(false)
// 获取用户信息
const fetchUserInfo = async () => {
try {
loading.value = true
const res = await getUserinfoAPI({ userId: '123' })
if (res.code === 1) {
userInfo.value = res.data
} else {
console.error('获取用户信息失败:', res.msg)
}
} catch (err) {
console.error('请求失败:', err)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchUserInfo()
})
</script>
<template>
<div v-if="loading">加载中...</div>
<div v-else-if="userInfo">
<p>用户名: {{ userInfo.userName }}</p>
</div>
</template>
```
## 🔧 高级配置
### 1. 自定义输出目录
修改 `scripts/apifox-to-openapi.js` 中的配置:
```javascript
const CONFIG = {
outputDir: path.join(__dirname, '../docs/api-specs'),
autoGenerate: true // 是否自动运行 API 代码生成
};
```
### 2. 按需同步
如果只想同步特定的接口标签,可以修改 `fetchApis()` 函数添加过滤逻辑:
```javascript
// 只获取特定标签的接口
const tagsToSync = ['user', 'product'];
const filteredApis = apis.filter(api =>
api.attributes?.tags?.some(tag => tagsToSync.includes(tag))
);
```
### 3. 禁用自动代码生成
`.env.apifox` 中添加:
```bash
VITE_APIFOX_AUTO_GENERATE=false
```
## 📊 变更检测
系统会自动检测 API 变更并显示差异报告:
```bash
pnpm api:sync
# 输出示例:
🔍 开始检测 API 变更...
📦 新增模块: product
📝 模块: user
✨ 新增接口: post-resetPassword
重置用户密码
✅ 检测到 2 个变更
```
## 🛡️ 安全最佳实践
### 1. 保护敏感信息
- ✅ 将 `.env.apifox` 添加到 `.gitignore`
- ✅ 不要在代码中硬编码 API Token
- ✅ 定期轮换 API Token
### 2. 访问控制
在 Apifox 中:
- 设置合适的权限(只读、编辑、管理)
- 为不同环境创建不同的 Token
- 记录 Token 使用情况
## 🐛 常见问题
### Q1: 提示"未找到 .env.apifox 文件"
**解决方案**: 创建 `.env.apifox` 文件并填写凭证
```bash
touch .env.apifox
```
### Q2: 提示"获取接口失败: HTTP 401"
**原因**: API Token 无效或已过期
**解决方案**:
1. 检查 Token 是否正确
2. 在 Apifox 中重新生成 Token
3. 更新 `.env.apifox` 文件
### Q3: 提示"项目不存在"
**原因**: 项目 ID 错误或无权限访问
**解决方案**:
1. 检查项目 ID 是否正确
2. 确认账号有该项目的访问权限
3. 在 Apifox 中检查项目设置
### Q4: 生成的 API 代码无法导入
**原因**: 路径别名配置问题
**解决方案**:
1. 检查 `config/index.js` 中的路径别名配置
2. 确认 `@/api` 指向正确的目录
3. 重启开发服务器
### Q5: 如何只同步部分接口?
**方案 1**: 在 Apifox 中使用标签分类接口,然后修改脚本过滤特定标签
**方案 2**: 手动管理 `docs/api-specs/` 目录,只保留需要的接口文档
## 📚 参考资料
- [Apifox 官方文档](https://apifox.com/docs/)
- [OpenAPI 3.0 规范](https://swagger.io/specification/)
- [项目 API 管理最佳实践](./api-best-practices.md)
## 🔄 更新日志
### v1.0.0 (2026-01-30)
- ✨ 初始版本
- ✅ 支持 Apifox API Token 认证
- ✅ 自动转换为 OpenAPI 3.0 格式
- ✅ 集成现有代码生成系统
- ✅ 支持变更检测
## 💬 反馈与支持
如有问题或建议,请:
1. 查看本文档的"常见问题"部分
2. 联系项目负责人
3. 在项目 Issues 中提问
---
**最后更新**: 2026-01-30
**维护者**: Manulife WeApp Team
# 🎉 Apifox 集成设置完成
## ✅ 已完成的配置
### 1. 核心文件
| 文件 | 说明 |
|------|------|
| `.env.apifox` | Apifox 凭证配置文件(需要您填写) |
| `.env.apifox.example` | 配置文件模板 |
| `scripts/apifox-to-openapi.js` | Apifox → OpenAPI 转换工具 |
| `scripts/test-apifox-connection.js` | Apifox 连接测试工具 |
| `scripts/apifox-sync.js` | 备用的独立同步工具 |
### 2. 文档文件
| 文件 | 说明 |
|------|------|
| `QUICKSTART_APIFOX.md` | 快速开始指南 |
| `docs/apifox-integration-guide.md` | 完整集成指南 |
### 3. 配置更新
| 文件 | 更新内容 |
|------|----------|
| `package.json` | 添加了 `api:sync``api:test` 命令 |
| `.gitignore` | 添加了 `.env.apifox` 以保护敏感信息 |
## 🚀 开始使用
### 步骤 1:配置 Apifox 凭证
```bash
# 1. 复制示例配置
cp .env.apifox.example .env.apifox
# 2. 编辑 .env.apifox,填写您的 Apifox 信息
# VITE_APIFOX_TOKEN=aps-your_token_here
# VITE_APIFOX_PROJECT_ID=your_project_id_here
```
### 步骤 2:测试连接
```bash
# 测试 Apifox 连接是否正常
pnpm api:test
```
这个命令会:
- ✅ 验证配置文件格式
- ✅ 测试 API Token 是否有效
- ✅ 显示项目基本信息
- ✅ 列出前 10 个接口
### 步骤 3:同步 API
```bash
# 从 Apifox 同步所有接口
pnpm api:sync
```
这个命令会:
- ✅ 从 Apifox 获取所有接口数据
- ✅ 转换为 OpenAPI 3.0 格式
- ✅ 保存到 `docs/api-specs/` 目录
- ✅ 自动生成 API 代码到 `src/api/` 目录
## 📋 可用命令
| 命令 | 说明 |
|------|------|
| `pnpm api:test` | 测试 Apifox 连接 |
| `pnpm api:sync` | 从 Apifox 同步 API(推荐) |
| `pnpm api:generate` | 从 OpenAPI 文档生成代码 |
## 💡 使用示例
### 在组件中使用生成的 API
```javascript
// 1. 导入生成的 API
import { getUserInfoAPI } from '@/api/user'
import { getProductListAPI } from '@/api/product'
// 2. 调用 API
const res = await getUserInfoAPI({ userId: '123' })
// 3. 检查返回值
if (res.code === 1) {
console.log('成功:', res.data)
} else {
console.error('失败:', res.msg)
}
```
## 📂 生成的文件结构
```
docs/api-specs/ # OpenAPI 文档(Markdown)
├── user/ # 用户模块
│ ├── get-userinfo.md
│ └── post-login.md
└── product/ # 产品模块
└── get-list.md
src/api/ # 生成的 API 代码
├── user.js # 用户模块 API
└── product.js # 产品模块 API
```
## 🔍 故障排查
### 问题 1:找不到 .env.apifox 文件
**解决方案**
```bash
cp .env.apifox.example .env.apifox
```
### 问题 2:提示"HTTP 401"
**原因**:API Token 无效
**解决方案**
1. 检查 Token 是否正确
2. 在 Apifox 中重新生成 Token
3. 更新 `.env.apifox` 文件
### 问题 3:提示"项目不存在"
**原因**:项目 ID 错误或无权限
**解决方案**
1. 检查项目 ID 是否正确
2. 确认账号有该项目的访问权限
3. 检查 Apifox 项目设置
## 📚 更多资源
- **快速开始**: [QUICKSTART_APIFOX.md](../QUICKSTART_APIFOX.md)
- **完整指南**: [apifox-integration-guide.md](./apifox-integration-guide.md)
- **Apifox 文档**: https://apifox.com/docs/
## 🛡️ 安全提醒
⚠️ **重要**
- `.env.apifox` 包含敏感信息,已被添加到 `.gitignore`
- 请勿将 `.env.apifox` 提交到 Git
- 定期轮换 API Token
- 在 Apifox 中设置合适的权限
## 🎯 下一步
1. ✅ 配置 `.env.apifox` 文件
2. ✅ 运行 `pnpm api:test` 测试连接
3. ✅ 运行 `pnpm api:sync` 同步 API
4. ✅ 在组件中使用生成的 API
---
**需要帮助?** 查看 [完整集成指南](./apifox-integration-guide.md) 或运行 `pnpm api:test` 诊断问题
**最后更新**: 2026-01-30
......@@ -28,7 +28,9 @@
"dev:quickapp": "NODE_ENV=development taro build --type quickapp --watch",
"postinstall": "weapp-tw patch",
"lint": "eslint --ext .js,.vue src",
"api:generate": "node scripts/generateApiFromOpenAPI.js"
"api:generate": "node scripts/generateApiFromOpenAPI.js",
"api:sync": "node scripts/apifox-to-openapi.js",
"api:test": "node scripts/test-apifox-connection.js"
},
"browserslist": [
"last 3 versions",
......
#!/usr/bin/env node
/**
* Apifox API 同步工具
*
* 功能:
* 1. 从 Apifox 获取所有接口数据
* 2. 生成 TypeScript/JavaScript API 接口代码
* 3. 生成 Mock 数据
* 4. 自动更新 src/api/ 目录
*
* 使用:
* node scripts/apifox-sync.js
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// 配置
const CONFIG = {
token: process.env.VITE_APIFOX_TOKEN,
projectId: process.env.VITE_APIFOX_PROJECT_ID,
baseUrl: 'api.apifox.com',
outputDir: path.join(__dirname, '../src/api'),
mockDir: path.join(__dirname, '../src/mocks')
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
blue: '\x1b[34m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
// 读取 .env.apifox 文件
function loadEnv() {
const envPath = path.join(__dirname, '../.env.apifox');
if (!fs.existsSync(envPath)) {
log('❌ 未找到 .env.apifox 文件', 'red');
log('📝 请先创建 .env.apifox 文件并填写 Apifox 凭证:', 'yellow');
log('');
log('VITE_APIFOX_TOKEN=your_api_token_here', 'blue');
log('VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue');
process.exit(1);
}
const env = fs.readFileSync(envPath, 'utf-8');
env.split('\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
const value = valueParts.join('=');
if (key && !key.startsWith('#') && value) {
process.env[key.trim()] = value.trim();
}
});
if (!process.env.VITE_APIFOX_TOKEN || !process.env.VITE_APIFOX_PROJECT_ID) {
log('❌ .env.apifox 文件中缺少必要的配置', 'red');
log('请确保填写了 VITE_APIFOX_TOKEN 和 VITE_APIFOX_PROJECT_ID', 'yellow');
process.exit(1);
}
CONFIG.token = process.env.VITE_APIFOX_TOKEN;
CONFIG.projectId = process.env.VITE_APIFOX_PROJECT_ID;
log(`✅ 已加载配置,项目 ID: ${CONFIG.projectId}`, 'green');
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(data);
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
}
});
});
req.on('error', reject);
req.end();
});
}
// 获取 Apifox 项目所有接口
async function fetchApis() {
log('\n📡 正在从 Apifox 获取接口数据...', 'blue');
const options = {
hostname: CONFIG.baseUrl,
path: `/api/v1/projects/${CONFIG.projectId}/apis?pageSize=1000`,
method: 'GET',
headers: {
'Authorization': `Bearer ${CONFIG.token}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
const apis = response.data || [];
log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
return apis;
} catch (err) {
log(`❌ 获取接口失败: ${err.message}`, 'red');
throw err;
}
}
// 获取接口详情
async function fetchApiDetail(apiId) {
const options = {
hostname: CONFIG.baseUrl,
path: `/api/v1/projects/${CONFIG.projectId}/apis/${apiId}`,
method: 'GET',
headers: {
'Authorization': `Bearer ${CONFIG.token}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
return response.data;
} catch (err) {
log(`⚠️ 获取接口详情失败 (${apiId}): ${err.message}`, 'yellow');
return null;
}
}
// 生成 API 接口代码
function generateApiCode(apis) {
log('\n📝 正在生成 API 接口代码...', 'blue');
let code = `/**
* @description API 接口定义(从 Apifox 自动生成)
* @generated by scripts/apifox-sync.js
* @lastUpdated ${new Date().toISOString()}
*/
import { fn } from '@/api/fn';
`;
// 按标签分组
const groupedApis = {};
apis.forEach(api => {
const tags = api.attributes?.tags || ['default'];
const tag = tags[0];
if (!groupedApis[tag]) {
groupedApis[tag] = [];
}
groupedApis[tag].push(api);
});
// 生成每个分组的接口
Object.entries(groupedApis).forEach(([tag, items]) => {
code += `\n// ==================== ${tag} ====================\n\n`;
items.forEach(api => {
const apiId = api.id;
const name = api.attributes?.name || apiId;
const method = (api.attributes?.method || 'GET').toLowerCase();
const path = api.attributes?.path || '';
// 生成接口函数名(将路径转换为驼峰命名)
const functionName = pathToFunctionName(method, path);
// 生成注释
code += `/**\n`;
code += ` * @description ${name}\n`;
code += ` * @path ${method.toUpperCase()} ${path}\n`;
if (api.attributes?.description) {
code += ` * @remark ${api.attributes.description}\n`;
}
code += ` */\n`;
// 生成接口函数
code += `export const ${functionName} = (params = {}) => {\n`;
code += ` return fn({\n`;
code += ` method: '${method}',\n`;
code += ` url: '${path}',\n`;
code += ` data: params\n`;
code += ` })\n`;
code += `}\n\n`;
});
});
// 写入文件
const outputPath = path.join(CONFIG.outputDir, 'generated.js');
fs.writeFileSync(outputPath, code, 'utf-8');
log(`✅ 已生成 API 接口代码: ${outputPath}`, 'green');
log(` 共 ${apis.length} 个接口,${Object.keys(groupedApis).length} 个分组`, 'green');
return outputPath;
}
// 生成 Mock 数据
function generateMockData(apis) {
log('\n🎭 正在生成 Mock 数据...', 'blue');
// 确保 mocks 目录存在
if (!fs.existsSync(CONFIG.mockDir)) {
fs.mkdirSync(CONFIG.mockDir, { recursive: true });
}
let mockIndex = `/**
* @description Mock 数据(从 Apifox 自动生成)
* @generated by scripts/apifox-sync.js
*/
`;
// 为每个接口生成 Mock 数据
apis.forEach(api => {
const path = api.attributes?.path || '';
const method = (api.attributes?.method || 'GET').toLowerCase();
const functionName = pathToFunctionName(method, path);
const mockFileName = `${functionName}.mock.js`;
// 从响应示例中提取 Mock 数据
const responses = api.attributes?.responses || [];
const successResponse = responses.find(r => r.code === 200) || responses[0];
if (successResponse) {
// 生成 Mock 文件
const mockCode = `/**
* @description Mock data for ${functionName}
* @path ${method.toUpperCase()} ${path}
*/
export const mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data = ${JSON.stringify(successResponse, null, 2)};
export default mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data;
`;
const mockPath = path.join(CONFIG.mockDir, mockFileName);
fs.writeFileSync(mockPath, mockCode, 'utf-8');
mockIndex += `export { default as mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data } from './${mockFileName}';\n`;
}
});
// 写入索引文件
const indexPath = path.join(CONFIG.mockDir, 'index.js');
fs.writeFileSync(indexPath, mockIndex, 'utf-8');
log(`✅ 已生成 Mock 数据: ${indexPath}`, 'green');
return indexPath;
}
// 将 URL 路径转换为驼峰命名的函数名
function pathToFunctionName(method, path) {
// 移除路径参数和查询参数
let cleanPath = path
.replace(/:\w+/g, '')
.replace(/\?.*$/, '')
.replace(/^\//, '')
.replace(/\/$/, '');
// 转换为驼峰命名
const parts = cleanPath.split('/').filter(Boolean);
let functionName = method;
parts.forEach(part => {
// 首字母大写
const capitalized = part.charAt(0).toUpperCase() + part.slice(1);
functionName += capitalized;
});
// 移除特殊字符
functionName = functionName.replace(/[^a-zA-Z0-9]/g, '');
return functionName;
}
// 主函数
async function main() {
try {
log('\n🚀 Apifox API 同步工具', 'bright');
log('=' .repeat(50), 'bright');
// 1. 加载配置
loadEnv();
// 2. 获取接口列表
const apis = await fetchApis();
// 3. 生成 API 接口代码
generateApiCode(apis);
// 4. 生成 Mock 数据
generateMockData(apis);
// 完成
log('\n✅ 同步完成!', 'green');
log('📦 生成的文件:', 'blue');
log(` - src/api/generated.js (API 接口)`, 'blue');
log(` - src/mocks/ (Mock 数据)`, 'blue');
log('\n💡 提示: 将 src/api/generated.js 中的接口导入到 src/api/index.js 中使用', 'yellow');
} catch (err) {
log(`\n❌ 同步失败: ${err.message}`, 'red');
process.exit(1);
}
}
// 运行
main();
#!/usr/bin/env node
/**
* Apifox 到 OpenAPI 转换工具
*
* 功能:
* 1. 从 Apifox 获取所有接口数据
* 2. 转换为 OpenAPI 3.0 格式
* 3. 保存到 docs/api-specs/ 目录
* 4. 可选择运行 generateApiFromOpenAPI.js 生成 API 代码
*
* 使用:
* node scripts/apifox-to-openapi.js
*
* 环境变量:
* VITE_APIFOX_TOKEN - Apifox API Token
* VITE_APIFOX_PROJECT_ID - Apifox 项目 ID
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const { execSync } = require('child_process');
// 配置
const CONFIG = {
token: null,
projectId: null,
baseUrl: 'api.apifox.com',
outputDir: path.join(__dirname, '../docs/api-specs'),
autoGenerate: true // 是否自动运行 API 代码生成
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
blue: '\x1b[34m',
cyan: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
// 加载 .env.apifox 文件
function loadEnv() {
const envPath = path.join(__dirname, '../.env.apifox');
if (!fs.existsSync(envPath)) {
log('❌ 未找到 .env.apifox 文件', 'red');
log('📝 请先创建 .env.apifox 文件并填写 Apifox 凭证:', 'yellow');
log('');
log('VITE_APIFOX_TOKEN=your_api_token_here', 'blue');
log('VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue');
process.exit(1);
}
const env = fs.readFileSync(envPath, 'utf-8');
env.split('\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
const value = valueParts.join('=');
if (key && !key.startsWith('#') && value) {
process.env[key.trim()] = value.trim();
}
});
if (!process.env.VITE_APIFOX_TOKEN || !process.env.VITE_APIFOX_PROJECT_ID) {
log('❌ .env.apifox 文件中缺少必要的配置', 'red');
log('请确保填写了 VITE_APIFOX_TOKEN 和 VITE_APIFOX_PROJECT_ID', 'yellow');
process.exit(1);
}
CONFIG.token = process.env.VITE_APIFOX_TOKEN;
CONFIG.projectId = process.env.VITE_APIFOX_PROJECT_ID;
log(`✅ 已加载配置,项目 ID: ${CONFIG.projectId}`, 'green');
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(data);
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
}
});
});
req.on('error', reject);
req.end();
});
}
// 获取 Apifox 项目所有接口
async function fetchApis() {
log('\n📡 正在从 Apifox 获取接口数据...', 'blue');
const options = {
hostname: CONFIG.baseUrl,
path: `/api/v1/projects/${CONFIG.projectId}/apis?pageSize=1000`,
method: 'GET',
headers: {
'Authorization': `Bearer ${CONFIG.token}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
const apis = response.data || [];
log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
return apis;
} catch (err) {
log(`❌ 获取接口失败: ${err.message}`, 'red');
throw err;
}
}
// 获取接口详情
async function fetchApiDetail(apiId) {
const options = {
hostname: CONFIG.baseUrl,
path: `/api/v1/projects/${CONFIG.projectId}/apis/${apiId}`,
method: 'GET',
headers: {
'Authorization': `Bearer ${CONFIG.token}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
return response.data;
} catch (err) {
log(`⚠️ 获取接口详情失败 (${apiId}): ${err.message}`, 'yellow');
return null;
}
}
// 将 Apifox 接口转换为 OpenAPI 3.0 格式
function convertToOpenAPI(apiDetail) {
const attributes = apiDetail.attributes || {};
const method = attributes.method ? attributes.method.toLowerCase() : 'get';
const path = attributes.path || '/';
// 构建参数
const parameters = [];
const requestBody = {};
// 处理 query 参数
if (attributes.parameters && attributes.parameters.length > 0) {
attributes.parameters.forEach(param => {
if (param.in === 'query') {
parameters.push({
name: param.name,
in: 'query',
description: param.description || '',
required: param.required || false,
schema: {
type: param.schema?.type || 'string',
example: param.example
}
});
}
});
}
// 处理 body 参数(POST/PUT 请求)
if (['post', 'put', 'patch'].includes(method)) {
const bodyParams = attributes.requestBody?.content?.['application/json']?.schema?.properties || {};
const requiredParams = attributes.requestBody?.content?.['application/json']?.schema?.required || [];
if (Object.keys(bodyParams).length > 0) {
requestBody.content = {
'application/json': {
schema: {
type: 'object',
properties: bodyParams,
required: requiredParams
}
}
};
}
}
// 构建响应
const responses = {
'200': {
description: '成功',
content: {
'application/json': {
schema: extractResponseSchema(attributes.responses)
}
}
}
};
// 构建 OpenAPI 文档
const openapi = {
openapi: '3.0.0',
info: {
title: attributes.name || apiDetail.id,
description: attributes.description || '',
version: '1.0.0'
},
paths: {
[path]: {
[method]: {
summary: attributes.name || apiDetail.id,
description: attributes.description || '',
parameters,
requestBody: Object.keys(requestBody).length > 0 ? requestBody : undefined,
responses
}
}
}
};
return openapi;
}
// 提取响应结构
function extractResponseSchema(responses) {
if (!responses || responses.length === 0) {
return {
type: 'object',
properties: {
code: { type: 'number', description: '状态码' },
msg: { type: 'string', description: '消息' },
data: { type: 'any', description: '数据' }
}
};
}
const successResponse = responses.find(r => r.code === 200) || responses[0];
if (successResponse && successResponse.schema) {
return successResponse.schema;
}
return {
type: 'object',
properties: {
code: { type: 'number', description: '状态码' },
msg: { type: 'string', description: '消息' },
data: { type: 'any', description: '数据' }
}
};
}
// 将 OpenAPI 对象转换为 YAML 字符串
function openAPIToYAML(openapi) {
const yaml = require('js-yaml');
return yaml.dump(openapi, {
indent: 2,
lineWidth: -1,
noRefs: true
});
}
// 生成 Markdown 文件内容
function generateMarkdown(openapi, apiDetail) {
const yaml = openAPIToYAML(openapi);
const attributes = apiDetail.attributes || {};
return `# ${attributes.name || apiDetail.id}
${attributes.description || '暂无描述'}
## 接口信息
- **方法**: ${attributes.method?.toUpperCase() || 'GET'}
- **路径**: ${attributes.path || '/'}
- **标签**: ${(attributes.tags || []).join(', ') || '默认'}
## OpenAPI 规范
\`\`\`yaml
${yaml}
\`\`\`
`;
}
// 将接口按标签分组
function groupApisByTag(apis) {
const grouped = {};
apis.forEach(api => {
const tags = api.attributes?.tags || ['default'];
const tag = tags[0];
if (!grouped[tag]) {
grouped[tag] = [];
}
grouped[tag].push(api);
});
return grouped;
}
// 生成文件名(从接口路径和名称)
function generateFileName(api) {
const attributes = api.attributes || {};
const path = attributes.path || '/';
const method = attributes.method?.toLowerCase() || 'get';
// 从路径提取文件名
const pathParts = path.split('/').filter(Boolean);
const lastPart = pathParts[pathParts.length - 1] || 'index';
// 移除路径参数
const cleanName = lastPart.replace(/^:/, '').replace(/[^a-zA-Z0-9]/g, '-');
return `${method}-${cleanName}`;
}
// 保存 OpenAPI 文档到文件
async function saveOpenAPIDocs(groupedApis) {
log('\n📝 正在生成 OpenAPI 文档...', 'blue');
// 确保输出目录存在
if (!fs.existsSync(CONFIG.outputDir)) {
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
}
let totalFiles = 0;
for (const [tag, apis] of Object.entries(groupedApis)) {
const moduleDir = path.join(CONFIG.outputDir, tag);
// 创建模块目录
if (!fs.existsSync(moduleDir)) {
fs.mkdirSync(moduleDir, { recursive: true });
}
log(`\n处理模块: ${tag}`, 'cyan');
for (const api of apis) {
try {
// 获取接口详情
const apiDetail = await fetchApiDetail(api.id);
if (!apiDetail) continue;
// 转换为 OpenAPI
const openapi = convertToOpenAPI(apiDetail);
// 生成 Markdown 文件
const fileName = generateFileName(api);
const markdown = generateMarkdown(openapi, apiDetail);
const filePath = path.join(moduleDir, `${fileName}.md`);
// 写入文件
fs.writeFileSync(filePath, markdown, 'utf-8');
log(` ✓ ${fileName}.md`, 'green');
totalFiles++;
} catch (err) {
log(` ✗ 处理失败: ${err.message}`, 'red');
}
}
}
log(`\n✅ 共生成 ${totalFiles} 个 OpenAPI 文档`, 'green');
log(`📁 输出目录: ${CONFIG.outputDir}`, 'blue');
return totalFiles;
}
// 运行 API 代码生成
function runAPIGenerator() {
if (!CONFIG.autoGenerate) {
log('\n⏭️ 跳过 API 代码生成', 'yellow');
return;
}
log('\n🔧 正在运行 API 代码生成...', 'blue');
try {
const generatorPath = path.join(__dirname, 'generateApiFromOpenAPI.js');
execSync(`node "${generatorPath}"`, {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});
log('✅ API 代码生成完成', 'green');
} catch (err) {
log(`⚠️ API 代码生成失败: ${err.message}`, 'yellow');
}
}
// 主函数
async function main() {
try {
log('\n🚀 Apifox 到 OpenAPI 转换工具', 'bright');
log('=' .repeat(50), 'bright');
// 1. 加载配置
loadEnv();
// 2. 获取接口列表
const apis = await fetchApis();
// 3. 按标签分组
const groupedApis = groupApisByTag(apis);
log(`\n📦 找到 ${Object.keys(groupedApis).length} 个模块:`, 'cyan');
Object.keys(groupedApis).forEach(tag => {
log(` - ${tag} (${groupedApis[tag].length} 个接口)`, 'cyan');
});
// 4. 保存 OpenAPI 文档
await saveOpenAPIDocs(groupedApis);
// 5. 运行 API 代码生成
runAPIGenerator();
// 完成
log('\n✅ 转换完成!', 'green');
log('\n💡 提示:', 'yellow');
log(' 1. 检查 docs/api-specs/ 目录查看生成的 OpenAPI 文档', 'blue');
log(' 2. 检查 src/api/ 目录查看生成的 API 代码', 'blue');
log(' 3. 在组件中导入并使用生成的 API', 'blue');
} catch (err) {
log(`\n❌ 转换失败: ${err.message}`, 'red');
process.exit(1);
}
}
// 运行
main();
#!/usr/bin/env node
/**
* Apifox 连接测试工具
*
* 功能:
* 1. 验证 .env.apifox 配置是否正确
* 2. 测试 Apifox API 连接
* 3. 显示项目基本信息
* 4. 列出所有接口数量
*
* 使用:
* node scripts/test-apifox-connection.js
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// 颜色输出
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
blue: '\x1b[34m',
cyan: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
// 加载配置
function loadConfig() {
const envPath = path.join(__dirname, '../.env.apifox');
if (!fs.existsSync(envPath)) {
log('❌ 未找到 .env.apifox 文件', 'red');
log('\n📝 请先创建配置文件:', 'yellow');
log(' cp .env.apifox.example .env.apifox', 'blue');
log(' 然后编辑 .env.apifox 填写凭证信息', 'blue');
return null;
}
const env = fs.readFileSync(envPath, 'utf-8');
const config = {};
env.split('\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
const value = valueParts.join('=');
if (key && !key.startsWith('#') && value) {
config[key.trim()] = value.trim();
}
});
if (!config.VITE_APIFOX_TOKEN || !config.VITE_APIFOX_PROJECT_ID) {
log('❌ .env.apifox 文件中缺少必要的配置', 'red');
log('\n请确保填写了以下配置:', 'yellow');
log(' VITE_APIFOX_TOKEN=aps-your_token_here', 'blue');
log(' VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue');
return null;
}
return config;
}
// 验证 Token 格式
function validateToken(token) {
if (!token.startsWith('aps-')) {
log('⚠️ Token 格式不正确', 'yellow');
log(' 正确格式: aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'cyan');
return false;
}
if (token.length < 10) {
log('⚠️ Token 长度似乎不正确', 'yellow');
return false;
}
return true;
}
// 验证项目 ID 格式
function validateProjectId(projectId) {
if (!/^\d+$/.test(projectId)) {
log('⚠️ 项目 ID 格式不正确', 'yellow');
log(' 正确格式: 纯数字(如: 12345678)', 'cyan');
return false;
}
return true;
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
log(`📡 发送请求到: https://${options.hostname}${options.path}`, 'blue');
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(data);
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
}
});
});
req.on('error', reject);
req.end();
});
}
// 测试获取项目信息
async function testProjectInfo(config) {
log('\n📊 获取项目信息...', 'cyan');
const options = {
hostname: 'api.apifox.com',
path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}`,
method: 'GET',
headers: {
'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
const project = response.data;
log('✅ 项目信息获取成功', 'green');
log('');
log('项目详情:', 'bright');
log(` 名称: ${project.name || '未设置'}`, 'reset');
log(` ID: ${project.id}`, 'reset');
log(` 描述: ${project.description || '无'}`, 'reset');
log(` 创建时间: ${project.createdTime || '未知'}`, 'reset');
return true;
} catch (err) {
log(`❌ 获取项目信息失败: ${err.message}`, 'red');
if (err.message.includes('401')) {
log('\n可能的原因:', 'yellow');
log(' 1. API Token 错误或已过期', 'yellow');
log(' 2. 请检查 .env.apifox 中的 VITE_APIFOX_TOKEN', 'yellow');
} else if (err.message.includes('403') || err.message.includes('404')) {
log('\n可能的原因:', 'yellow');
log(' 1. 项目 ID 错误', 'yellow');
log(' 2. 账号无该项目的访问权限', 'yellow');
log(' 3. 请检查 .env.apifox 中的 VITE_APIFOX_PROJECT_ID', 'yellow');
}
return false;
}
}
// 测试获取接口列表
async function testApiList(config) {
log('\n📋 获取接口列表...', 'cyan');
const options = {
hostname: 'api.apifox.com',
path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}/apis?pageSize=10`,
method: 'GET',
headers: {
'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
const apis = response.data || [];
log('✅ 接口列表获取成功', 'green');
log(` 共找到 ${response.total || apis.length} 个接口`, 'blue');
if (apis.length > 0) {
log('\n前 10 个接口:', 'bright');
apis.forEach((api, index) => {
const method = (api.attributes?.method || 'GET').toUpperCase();
const path = api.attributes?.path || '/';
const name = api.attributes?.name || api.id;
log(` ${index + 1}. ${method} ${path}`, 'cyan');
log(` ${name}`, 'reset');
});
if (response.total > 10) {
log(`\n ... 还有 ${response.total - 10} 个接口`, 'yellow');
}
}
return true;
} catch (err) {
log(`❌ 获取接口列表失败: ${err.message}`, 'red');
return false;
}
}
// 主函数
async function main() {
log('\n🧪 Apifox 连接测试工具', 'bright');
log('=' .repeat(50), 'bright');
// 1. 加载配置
log('\n📝 步骤 1/4: 加载配置文件', 'cyan');
const config = loadConfig();
if (!config) {
process.exit(1);
}
log('✅ 配置文件加载成功', 'green');
log(` Token: ${config.VITE_APIFOX_TOKEN.substring(0, 15)}...`, 'blue');
log(` 项目 ID: ${config.VITE_APIFOX_PROJECT_ID}`, 'blue');
// 2. 验证格式
log('\n🔍 步骤 2/4: 验证配置格式', 'cyan');
let valid = true;
if (!validateToken(config.VITE_APIFOX_TOKEN)) {
valid = false;
}
if (!validateProjectId(config.VITE_APIFOX_PROJECT_ID)) {
valid = false;
}
if (!valid) {
log('\n❌ 配置验证失败,请修正后重试', 'red');
process.exit(1);
}
log('✅ 配置格式验证通过', 'green');
// 3. 测试项目信息
log('\n🌐 步骤 3/4: 测试 API 连接', 'cyan');
const projectSuccess = await testProjectInfo(config);
if (!projectSuccess) {
log('\n❌ API 连接测试失败', 'red');
log('\n💡 建议:', 'yellow');
log(' 1. 检查网络连接', 'yellow');
log(' 2. 验证 Token 和项目 ID 是否正确', 'yellow');
log(' 3. 确认账号有该项目的访问权限', 'yellow');
process.exit(1);
}
// 4. 测试接口列表
const apiSuccess = await testApiList(config);
if (!apiSuccess) {
log('\n⚠️ 接口列表获取失败,但项目连接正常', 'yellow');
process.exit(1);
}
// 完成
log('\n' + '='.repeat(50), 'bright');
log('✅ 所有测试通过!', 'green');
log('\n🎉 配置正确,可以开始同步 API 了', 'green');
log('\n运行以下命令同步 API:', 'blue');
log(' pnpm api:sync', 'bright');
log('');
}
// 运行
main().catch(err => {
log(`\n❌ 测试失败: ${err.message}`, 'red');
console.error(err);
process.exit(1);
});
#!/bin/bash
# 测试 Apifox MCP 服务器连接
echo "🔍 测试 Apifox MCP 服务器连接..."
echo ""
# 设置环境变量
export APIFOX_ACCESS_TOKEN="APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc"
# 测试命令
echo "✅ Token 格式: 正确"
echo "✅ Project ID: 6084040"
echo "✅ MCP 配置文件: .claude/settings.json"
echo ""
echo "📝 当前配置内容:"
cat .claude/settings.json
echo ""
echo "📋 检查清单:"
echo " ✓ Token 已更新为: APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc"
echo " ✓ Project ID: 6084040 (mlaj 项目)"
echo ""
echo "💡 下一步:"
echo " 1. ✅ 配置已更新"
echo " 2. ⚠️ 需要重启 Claude Code 才能生效"
echo " 3. 重启后可以在对话中问:'列出 manulife 项目所有的 API'"
echo ""
echo "🔧 手动测试 MCP 服务器(可选):"
echo " 运行以下命令测试服务器是否能启动:"
echo ""
echo " APIFOX_ACCESS_TOKEN=\"APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc\" \\"
echo " npx -y apifox-mcp-server@latest --project-id=6084040"
echo ""