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,保护敏感配置文件不被提交
Showing
10 changed files
with
1304 additions
and
1 deletions
.env.apifox.example
0 → 100644
| 1 | +# Apifox API 配置示例 | ||
| 2 | +# 复制此文件为 .env.apifox 并填写实际的凭证信息 | ||
| 3 | + | ||
| 4 | +# Apifox API Token(在 Apifox → 个人设置 → API Token 中创建) | ||
| 5 | +# 格式: aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||
| 6 | +VITE_APIFOX_TOKEN=aps-your_token_here | ||
| 7 | + | ||
| 8 | +# Apifox 项目 ID(从 URL 中获取) | ||
| 9 | +# 示例: https://app.apifox.com/project/12345678/api-12345 | ||
| 10 | +# 项目 ID: 12345678 | ||
| 11 | +VITE_APIFOX_PROJECT_ID=your_project_id_here | ||
| 12 | + | ||
| 13 | +# API 数据缓存时间(秒),默认 1 小时 | ||
| 14 | +VITE_APIFOX_CACHE_TIME=3600 |
QUICKSTART_APIFOX.md
0 → 100644
| 1 | +# 🚀 Apifox 集成快速开始 | ||
| 2 | + | ||
| 3 | +## 第一步:获取 Apifox 凭证 | ||
| 4 | + | ||
| 5 | +### 1.1 获取 API Token | ||
| 6 | + | ||
| 7 | +1. 访问 [Apifox](https://app.apifox.com) 并登录 | ||
| 8 | +2. 点击右上角头像 → **个人设置** | ||
| 9 | +3. 选择 **API Token** → **新建 Token** | ||
| 10 | +4. 输入名称(如"Manulife WeApp") | ||
| 11 | +5. 复制生成的 Token(格式:`aps-xxxxx`) | ||
| 12 | + | ||
| 13 | +### 1.2 获取项目 ID | ||
| 14 | + | ||
| 15 | +在 Apifox 中打开您的项目,URL 格式为: | ||
| 16 | + | ||
| 17 | +``` | ||
| 18 | +https://app.apifox.com/project/{PROJECT_ID}/... | ||
| 19 | +``` | ||
| 20 | + | ||
| 21 | +复制 `{PROJECT_ID}` 部分。 | ||
| 22 | + | ||
| 23 | +## 第二步:配置项目 | ||
| 24 | + | ||
| 25 | +### 2.1 创建配置文件 | ||
| 26 | + | ||
| 27 | +在项目根目录执行: | ||
| 28 | + | ||
| 29 | +```bash | ||
| 30 | +cp .env.apifox.example .env.apifox | ||
| 31 | +``` | ||
| 32 | + | ||
| 33 | +### 2.2 填写凭证信息 | ||
| 34 | + | ||
| 35 | +编辑 `.env.apifox` 文件: | ||
| 36 | + | ||
| 37 | +```bash | ||
| 38 | +# 替换为你的实际值 | ||
| 39 | +VITE_APIFOX_TOKEN=aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||
| 40 | +VITE_APIFOX_PROJECT_ID=12345678 | ||
| 41 | +``` | ||
| 42 | + | ||
| 43 | +## 第三步:同步 API | ||
| 44 | + | ||
| 45 | +运行同步命令: | ||
| 46 | + | ||
| 47 | +```bash | ||
| 48 | +pnpm api:sync | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +这个命令会: | ||
| 52 | +1. ✅ 从 Apifox 获取所有接口 | ||
| 53 | +2. ✅ 生成 OpenAPI 文档到 `docs/api-specs/` | ||
| 54 | +3. ✅ 生成 API 代码到 `src/api/` | ||
| 55 | + | ||
| 56 | +## 第四步:使用 API | ||
| 57 | + | ||
| 58 | +在组件中导入并使用: | ||
| 59 | + | ||
| 60 | +```javascript | ||
| 61 | +import { getUserInfoAPI } from '@/api/user' | ||
| 62 | + | ||
| 63 | +// 调用 API | ||
| 64 | +const res = await getUserInfoAPI({ userId: '123' }) | ||
| 65 | +if (res.code === 1) { | ||
| 66 | + console.log('用户信息:', res.data) | ||
| 67 | +} | ||
| 68 | +``` | ||
| 69 | + | ||
| 70 | +## 📚 更多信息 | ||
| 71 | + | ||
| 72 | +详细文档请查看:[Apifox 集成完整指南](./docs/apifox-integration-guide.md) | ||
| 73 | + | ||
| 74 | +## ❓ 常见问题 | ||
| 75 | + | ||
| 76 | +**Q: 提示"未找到 .env.apifox 文件"** | ||
| 77 | +```bash | ||
| 78 | +# 解决方案:创建配置文件 | ||
| 79 | +cp .env.apifox.example .env.apifox | ||
| 80 | +``` | ||
| 81 | + | ||
| 82 | +**Q: 提示"HTTP 401"** | ||
| 83 | +- 检查 API Token 是否正确 | ||
| 84 | +- 确认 Token 未过期 | ||
| 85 | + | ||
| 86 | +**Q: 提示"项目不存在"** | ||
| 87 | +- 检查项目 ID 是否正确 | ||
| 88 | +- 确认账号有项目访问权限 | ||
| 89 | + | ||
| 90 | +--- | ||
| 91 | + | ||
| 92 | +**需要帮助?** 查看完整文档或联系项目组 |
docs/apifox-integration-guide.md
0 → 100644
| 1 | +# Apifox API 集成指南 | ||
| 2 | + | ||
| 3 | +## 📖 概述 | ||
| 4 | + | ||
| 5 | +本项目集成了 Apifox API 管理系统,可以自动从 Apifox 获取接口数据并生成 API 代码。 | ||
| 6 | + | ||
| 7 | +## 🎯 功能特性 | ||
| 8 | + | ||
| 9 | +- ✅ **自动同步**: 从 Apifox 获取所有接口数据 | ||
| 10 | +- ✅ **OpenAPI 转换**: 将 Apifox 数据转换为 OpenAPI 3.0 格式 | ||
| 11 | +- ✅ **代码生成**: 自动生成 JavaScript API 接口代码 | ||
| 12 | +- ✅ **类型安全**: 生成完整的 JSDoc 类型注释 | ||
| 13 | +- ✅ **变更检测**: 自动检测 API 变更并显示差异 | ||
| 14 | +- ✅ **Mock 数据**: 支持生成 Mock 数据(可选) | ||
| 15 | + | ||
| 16 | +## 🚀 快速开始 | ||
| 17 | + | ||
| 18 | +### 1. 获取 Apifox 凭证 | ||
| 19 | + | ||
| 20 | +#### 1.1 获取 API Token | ||
| 21 | + | ||
| 22 | +1. 登录 [Apifox](https://app.apifox.com) | ||
| 23 | +2. 点击右上角头像 → **个人设置** | ||
| 24 | +3. 选择 **API Token** → **新建 Token** | ||
| 25 | +4. 输入 Token 名称(如"Manulife WeApp") | ||
| 26 | +5. 复制生成的 Token(格式:`aps-xxxxxxxxxxxxx`) | ||
| 27 | + | ||
| 28 | +#### 1.2 获取项目 ID | ||
| 29 | + | ||
| 30 | +项目 ID 是 Apifox 项目 URL 中的一部分: | ||
| 31 | + | ||
| 32 | +``` | ||
| 33 | +https://app.apifox.com/project/{PROJECT_ID}/... | ||
| 34 | +``` | ||
| 35 | + | ||
| 36 | +例如:`https://app.apifox.com/project/12345678/api-12345` 的项目 ID 是 `12345678` | ||
| 37 | + | ||
| 38 | +### 2. 配置项目 | ||
| 39 | + | ||
| 40 | +#### 2.1 创建 `.env.apifox` 文件 | ||
| 41 | + | ||
| 42 | +在项目根目录创建 `.env.apifox` 文件: | ||
| 43 | + | ||
| 44 | +```bash | ||
| 45 | +VITE_APIFOX_TOKEN=aps-your_token_here | ||
| 46 | +VITE_APIFOX_PROJECT_ID=your_project_id_here | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +#### 2.2 填写实际凭证 | ||
| 50 | + | ||
| 51 | +```bash | ||
| 52 | +# Apifox API Token | ||
| 53 | +VITE_APIFOX_TOKEN=aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||
| 54 | + | ||
| 55 | +# Apifox 项目 ID | ||
| 56 | +VITE_APIFOX_PROJECT_ID=12345678 | ||
| 57 | +``` | ||
| 58 | + | ||
| 59 | +⚠️ **重要**: 请勿将 `.env.apifox` 文件提交到 Git! | ||
| 60 | + | ||
| 61 | +### 3. 同步 API | ||
| 62 | + | ||
| 63 | +#### 方式 1: 使用 pnpm 命令(推荐) | ||
| 64 | + | ||
| 65 | +```bash | ||
| 66 | +pnpm api:sync | ||
| 67 | +``` | ||
| 68 | + | ||
| 69 | +这个命令会: | ||
| 70 | +1. 从 Apifox 获取所有接口数据 | ||
| 71 | +2. 转换为 OpenAPI 3.0 格式 | ||
| 72 | +3. 保存到 `docs/api-specs/` 目录 | ||
| 73 | +4. 自动运行 `api:generate` 生成 API 代码 | ||
| 74 | + | ||
| 75 | +#### 方式 2: 仅生成 OpenAPI 文档 | ||
| 76 | + | ||
| 77 | +```bash | ||
| 78 | +node scripts/apifox-to-openapi.js | ||
| 79 | +``` | ||
| 80 | + | ||
| 81 | +#### 方式 3: 仅从 OpenAPI 生成代码 | ||
| 82 | + | ||
| 83 | +```bash | ||
| 84 | +pnpm api:generate | ||
| 85 | +``` | ||
| 86 | + | ||
| 87 | +## 📂 项目结构 | ||
| 88 | + | ||
| 89 | +``` | ||
| 90 | +manulife-weapp/ | ||
| 91 | +├── .env.apifox # Apifox 凭证配置(不提交到 Git) | ||
| 92 | +├── docs/ | ||
| 93 | +│ └── api-specs/ # OpenAPI 文档目录 | ||
| 94 | +│ ├── user/ # 按模块分类 | ||
| 95 | +│ │ ├── get-userinfo.md | ||
| 96 | +│ │ └── post-login.md | ||
| 97 | +│ └── product/ | ||
| 98 | +│ └── get-list.md | ||
| 99 | +├── src/ | ||
| 100 | +│ └── api/ # 生成的 API 代码 | ||
| 101 | +│ ├── user.js # 用户模块 API | ||
| 102 | +│ └── product.js # 产品模块 API | ||
| 103 | +└── scripts/ | ||
| 104 | + ├── apifox-to-openapi.js # Apifox → OpenAPI 转换器 | ||
| 105 | + └── generateApiFromOpenAPI.js # OpenAPI → API 代码生成器 | ||
| 106 | +``` | ||
| 107 | + | ||
| 108 | +## 📝 生成的代码示例 | ||
| 109 | + | ||
| 110 | +### OpenAPI 文档(`docs/api-specs/user/get-userinfo.md`) | ||
| 111 | + | ||
| 112 | +```markdown | ||
| 113 | +# 获取用户信息 | ||
| 114 | + | ||
| 115 | +获取当前登录用户的详细信息 | ||
| 116 | + | ||
| 117 | +## 接口信息 | ||
| 118 | + | ||
| 119 | +- **方法**: GET | ||
| 120 | +- **路径**: /srv/?a=get_userinfo | ||
| 121 | +- **标签**: user | ||
| 122 | + | ||
| 123 | +## OpenAPI 规范 | ||
| 124 | + | ||
| 125 | +\`\`\`yaml | ||
| 126 | +openapi: 3.0.0 | ||
| 127 | +info: | ||
| 128 | + title: 获取用户信息 | ||
| 129 | + description: 获取当前登录用户的详细信息 | ||
| 130 | + version: '1.0.0' | ||
| 131 | +paths: | ||
| 132 | + /srv/: | ||
| 133 | + get: | ||
| 134 | + summary: 获取用户信息 | ||
| 135 | + description: 获取当前登录用户的详细信息 | ||
| 136 | + parameters: | ||
| 137 | + - name: a | ||
| 138 | + in: query | ||
| 139 | + description: 接口动作 | ||
| 140 | + required: true | ||
| 141 | + schema: | ||
| 142 | + type: string | ||
| 143 | + example: get_userinfo | ||
| 144 | + responses: | ||
| 145 | + '200': | ||
| 146 | + description: 成功 | ||
| 147 | + content: | ||
| 148 | + application/json: | ||
| 149 | + schema: | ||
| 150 | + type: object | ||
| 151 | + properties: | ||
| 152 | + code: | ||
| 153 | + type: number | ||
| 154 | + description: 状态码 | ||
| 155 | + msg: | ||
| 156 | + type: string | ||
| 157 | + description: 消息 | ||
| 158 | + data: | ||
| 159 | + type: object | ||
| 160 | + properties: | ||
| 161 | + userId: | ||
| 162 | + type: string | ||
| 163 | + description: 用户 ID | ||
| 164 | + userName: | ||
| 165 | + type: string | ||
| 166 | + description: 用户名 | ||
| 167 | +\`\`\` | ||
| 168 | +``` | ||
| 169 | + | ||
| 170 | +### API 代码(`src/api/user.js`) | ||
| 171 | + | ||
| 172 | +```javascript | ||
| 173 | +/** | ||
| 174 | + * @description: 获取用户信息 | ||
| 175 | + * @param {Object} params 请求参数 | ||
| 176 | + * @returns {Promise<{ | ||
| 177 | + * code: number; // 状态码 | ||
| 178 | + * msg: string; // 消息 | ||
| 179 | + * data: { | ||
| 180 | + * userId: string; // 用户 ID | ||
| 181 | + * userName: string; // 用户名 | ||
| 182 | + * }; | ||
| 183 | + * }>} | ||
| 184 | + */ | ||
| 185 | +export const getUserinfoAPI = (params) => fn(fetch.get(Api.GetUserinfo, params)); | ||
| 186 | +``` | ||
| 187 | + | ||
| 188 | +## 💡 在组件中使用 | ||
| 189 | + | ||
| 190 | +### 1. 导入 API | ||
| 191 | + | ||
| 192 | +```javascript | ||
| 193 | +import { getUserinfoAPI, postLoginAPI } from '@/api/user' | ||
| 194 | +import { getProductListAPI } from '@/api/product' | ||
| 195 | +``` | ||
| 196 | + | ||
| 197 | +### 2. 使用 API | ||
| 198 | + | ||
| 199 | +```vue | ||
| 200 | +<script setup> | ||
| 201 | +import { ref, onMounted } from 'vue' | ||
| 202 | +import { getUserinfoAPI } from '@/api/user' | ||
| 203 | + | ||
| 204 | +const userInfo = ref(null) | ||
| 205 | +const loading = ref(false) | ||
| 206 | + | ||
| 207 | +// 获取用户信息 | ||
| 208 | +const fetchUserInfo = async () => { | ||
| 209 | + try { | ||
| 210 | + loading.value = true | ||
| 211 | + const res = await getUserinfoAPI({ userId: '123' }) | ||
| 212 | + | ||
| 213 | + if (res.code === 1) { | ||
| 214 | + userInfo.value = res.data | ||
| 215 | + } else { | ||
| 216 | + console.error('获取用户信息失败:', res.msg) | ||
| 217 | + } | ||
| 218 | + } catch (err) { | ||
| 219 | + console.error('请求失败:', err) | ||
| 220 | + } finally { | ||
| 221 | + loading.value = false | ||
| 222 | + } | ||
| 223 | +} | ||
| 224 | + | ||
| 225 | +onMounted(() => { | ||
| 226 | + fetchUserInfo() | ||
| 227 | +}) | ||
| 228 | +</script> | ||
| 229 | + | ||
| 230 | +<template> | ||
| 231 | + <div v-if="loading">加载中...</div> | ||
| 232 | + <div v-else-if="userInfo"> | ||
| 233 | + <p>用户名: {{ userInfo.userName }}</p> | ||
| 234 | + </div> | ||
| 235 | +</template> | ||
| 236 | +``` | ||
| 237 | + | ||
| 238 | +## 🔧 高级配置 | ||
| 239 | + | ||
| 240 | +### 1. 自定义输出目录 | ||
| 241 | + | ||
| 242 | +修改 `scripts/apifox-to-openapi.js` 中的配置: | ||
| 243 | + | ||
| 244 | +```javascript | ||
| 245 | +const CONFIG = { | ||
| 246 | + outputDir: path.join(__dirname, '../docs/api-specs'), | ||
| 247 | + autoGenerate: true // 是否自动运行 API 代码生成 | ||
| 248 | +}; | ||
| 249 | +``` | ||
| 250 | + | ||
| 251 | +### 2. 按需同步 | ||
| 252 | + | ||
| 253 | +如果只想同步特定的接口标签,可以修改 `fetchApis()` 函数添加过滤逻辑: | ||
| 254 | + | ||
| 255 | +```javascript | ||
| 256 | +// 只获取特定标签的接口 | ||
| 257 | +const tagsToSync = ['user', 'product']; | ||
| 258 | +const filteredApis = apis.filter(api => | ||
| 259 | + api.attributes?.tags?.some(tag => tagsToSync.includes(tag)) | ||
| 260 | +); | ||
| 261 | +``` | ||
| 262 | + | ||
| 263 | +### 3. 禁用自动代码生成 | ||
| 264 | + | ||
| 265 | +在 `.env.apifox` 中添加: | ||
| 266 | + | ||
| 267 | +```bash | ||
| 268 | +VITE_APIFOX_AUTO_GENERATE=false | ||
| 269 | +``` | ||
| 270 | + | ||
| 271 | +## 📊 变更检测 | ||
| 272 | + | ||
| 273 | +系统会自动检测 API 变更并显示差异报告: | ||
| 274 | + | ||
| 275 | +```bash | ||
| 276 | +pnpm api:sync | ||
| 277 | + | ||
| 278 | +# 输出示例: | ||
| 279 | +🔍 开始检测 API 变更... | ||
| 280 | + | ||
| 281 | +📦 新增模块: product | ||
| 282 | + | ||
| 283 | +📝 模块: user | ||
| 284 | + ✨ 新增接口: post-resetPassword | ||
| 285 | + 重置用户密码 | ||
| 286 | + | ||
| 287 | +✅ 检测到 2 个变更 | ||
| 288 | +``` | ||
| 289 | + | ||
| 290 | +## 🛡️ 安全最佳实践 | ||
| 291 | + | ||
| 292 | +### 1. 保护敏感信息 | ||
| 293 | + | ||
| 294 | +- ✅ 将 `.env.apifox` 添加到 `.gitignore` | ||
| 295 | +- ✅ 不要在代码中硬编码 API Token | ||
| 296 | +- ✅ 定期轮换 API Token | ||
| 297 | + | ||
| 298 | +### 2. 访问控制 | ||
| 299 | + | ||
| 300 | +在 Apifox 中: | ||
| 301 | +- 设置合适的权限(只读、编辑、管理) | ||
| 302 | +- 为不同环境创建不同的 Token | ||
| 303 | +- 记录 Token 使用情况 | ||
| 304 | + | ||
| 305 | +## 🐛 常见问题 | ||
| 306 | + | ||
| 307 | +### Q1: 提示"未找到 .env.apifox 文件" | ||
| 308 | + | ||
| 309 | +**解决方案**: 创建 `.env.apifox` 文件并填写凭证 | ||
| 310 | + | ||
| 311 | +```bash | ||
| 312 | +touch .env.apifox | ||
| 313 | +``` | ||
| 314 | + | ||
| 315 | +### Q2: 提示"获取接口失败: HTTP 401" | ||
| 316 | + | ||
| 317 | +**原因**: API Token 无效或已过期 | ||
| 318 | + | ||
| 319 | +**解决方案**: | ||
| 320 | +1. 检查 Token 是否正确 | ||
| 321 | +2. 在 Apifox 中重新生成 Token | ||
| 322 | +3. 更新 `.env.apifox` 文件 | ||
| 323 | + | ||
| 324 | +### Q3: 提示"项目不存在" | ||
| 325 | + | ||
| 326 | +**原因**: 项目 ID 错误或无权限访问 | ||
| 327 | + | ||
| 328 | +**解决方案**: | ||
| 329 | +1. 检查项目 ID 是否正确 | ||
| 330 | +2. 确认账号有该项目的访问权限 | ||
| 331 | +3. 在 Apifox 中检查项目设置 | ||
| 332 | + | ||
| 333 | +### Q4: 生成的 API 代码无法导入 | ||
| 334 | + | ||
| 335 | +**原因**: 路径别名配置问题 | ||
| 336 | + | ||
| 337 | +**解决方案**: | ||
| 338 | +1. 检查 `config/index.js` 中的路径别名配置 | ||
| 339 | +2. 确认 `@/api` 指向正确的目录 | ||
| 340 | +3. 重启开发服务器 | ||
| 341 | + | ||
| 342 | +### Q5: 如何只同步部分接口? | ||
| 343 | + | ||
| 344 | +**方案 1**: 在 Apifox 中使用标签分类接口,然后修改脚本过滤特定标签 | ||
| 345 | + | ||
| 346 | +**方案 2**: 手动管理 `docs/api-specs/` 目录,只保留需要的接口文档 | ||
| 347 | + | ||
| 348 | +## 📚 参考资料 | ||
| 349 | + | ||
| 350 | +- [Apifox 官方文档](https://apifox.com/docs/) | ||
| 351 | +- [OpenAPI 3.0 规范](https://swagger.io/specification/) | ||
| 352 | +- [项目 API 管理最佳实践](./api-best-practices.md) | ||
| 353 | + | ||
| 354 | +## 🔄 更新日志 | ||
| 355 | + | ||
| 356 | +### v1.0.0 (2026-01-30) | ||
| 357 | + | ||
| 358 | +- ✨ 初始版本 | ||
| 359 | +- ✅ 支持 Apifox API Token 认证 | ||
| 360 | +- ✅ 自动转换为 OpenAPI 3.0 格式 | ||
| 361 | +- ✅ 集成现有代码生成系统 | ||
| 362 | +- ✅ 支持变更检测 | ||
| 363 | + | ||
| 364 | +## 💬 反馈与支持 | ||
| 365 | + | ||
| 366 | +如有问题或建议,请: | ||
| 367 | +1. 查看本文档的"常见问题"部分 | ||
| 368 | +2. 联系项目负责人 | ||
| 369 | +3. 在项目 Issues 中提问 | ||
| 370 | + | ||
| 371 | +--- | ||
| 372 | + | ||
| 373 | +**最后更新**: 2026-01-30 | ||
| 374 | +**维护者**: Manulife WeApp Team |
docs/apifox-setup-summary.md
0 → 100644
| 1 | +# 🎉 Apifox 集成设置完成 | ||
| 2 | + | ||
| 3 | +## ✅ 已完成的配置 | ||
| 4 | + | ||
| 5 | +### 1. 核心文件 | ||
| 6 | + | ||
| 7 | +| 文件 | 说明 | | ||
| 8 | +|------|------| | ||
| 9 | +| `.env.apifox` | Apifox 凭证配置文件(需要您填写) | | ||
| 10 | +| `.env.apifox.example` | 配置文件模板 | | ||
| 11 | +| `scripts/apifox-to-openapi.js` | Apifox → OpenAPI 转换工具 | | ||
| 12 | +| `scripts/test-apifox-connection.js` | Apifox 连接测试工具 | | ||
| 13 | +| `scripts/apifox-sync.js` | 备用的独立同步工具 | | ||
| 14 | + | ||
| 15 | +### 2. 文档文件 | ||
| 16 | + | ||
| 17 | +| 文件 | 说明 | | ||
| 18 | +|------|------| | ||
| 19 | +| `QUICKSTART_APIFOX.md` | 快速开始指南 | | ||
| 20 | +| `docs/apifox-integration-guide.md` | 完整集成指南 | | ||
| 21 | + | ||
| 22 | +### 3. 配置更新 | ||
| 23 | + | ||
| 24 | +| 文件 | 更新内容 | | ||
| 25 | +|------|----------| | ||
| 26 | +| `package.json` | 添加了 `api:sync` 和 `api:test` 命令 | | ||
| 27 | +| `.gitignore` | 添加了 `.env.apifox` 以保护敏感信息 | | ||
| 28 | + | ||
| 29 | +## 🚀 开始使用 | ||
| 30 | + | ||
| 31 | +### 步骤 1:配置 Apifox 凭证 | ||
| 32 | + | ||
| 33 | +```bash | ||
| 34 | +# 1. 复制示例配置 | ||
| 35 | +cp .env.apifox.example .env.apifox | ||
| 36 | + | ||
| 37 | +# 2. 编辑 .env.apifox,填写您的 Apifox 信息 | ||
| 38 | +# VITE_APIFOX_TOKEN=aps-your_token_here | ||
| 39 | +# VITE_APIFOX_PROJECT_ID=your_project_id_here | ||
| 40 | +``` | ||
| 41 | + | ||
| 42 | +### 步骤 2:测试连接 | ||
| 43 | + | ||
| 44 | +```bash | ||
| 45 | +# 测试 Apifox 连接是否正常 | ||
| 46 | +pnpm api:test | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +这个命令会: | ||
| 50 | +- ✅ 验证配置文件格式 | ||
| 51 | +- ✅ 测试 API Token 是否有效 | ||
| 52 | +- ✅ 显示项目基本信息 | ||
| 53 | +- ✅ 列出前 10 个接口 | ||
| 54 | + | ||
| 55 | +### 步骤 3:同步 API | ||
| 56 | + | ||
| 57 | +```bash | ||
| 58 | +# 从 Apifox 同步所有接口 | ||
| 59 | +pnpm api:sync | ||
| 60 | +``` | ||
| 61 | + | ||
| 62 | +这个命令会: | ||
| 63 | +- ✅ 从 Apifox 获取所有接口数据 | ||
| 64 | +- ✅ 转换为 OpenAPI 3.0 格式 | ||
| 65 | +- ✅ 保存到 `docs/api-specs/` 目录 | ||
| 66 | +- ✅ 自动生成 API 代码到 `src/api/` 目录 | ||
| 67 | + | ||
| 68 | +## 📋 可用命令 | ||
| 69 | + | ||
| 70 | +| 命令 | 说明 | | ||
| 71 | +|------|------| | ||
| 72 | +| `pnpm api:test` | 测试 Apifox 连接 | | ||
| 73 | +| `pnpm api:sync` | 从 Apifox 同步 API(推荐) | | ||
| 74 | +| `pnpm api:generate` | 从 OpenAPI 文档生成代码 | | ||
| 75 | + | ||
| 76 | +## 💡 使用示例 | ||
| 77 | + | ||
| 78 | +### 在组件中使用生成的 API | ||
| 79 | + | ||
| 80 | +```javascript | ||
| 81 | +// 1. 导入生成的 API | ||
| 82 | +import { getUserInfoAPI } from '@/api/user' | ||
| 83 | +import { getProductListAPI } from '@/api/product' | ||
| 84 | + | ||
| 85 | +// 2. 调用 API | ||
| 86 | +const res = await getUserInfoAPI({ userId: '123' }) | ||
| 87 | + | ||
| 88 | +// 3. 检查返回值 | ||
| 89 | +if (res.code === 1) { | ||
| 90 | + console.log('成功:', res.data) | ||
| 91 | +} else { | ||
| 92 | + console.error('失败:', res.msg) | ||
| 93 | +} | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +## 📂 生成的文件结构 | ||
| 97 | + | ||
| 98 | +``` | ||
| 99 | +docs/api-specs/ # OpenAPI 文档(Markdown) | ||
| 100 | +├── user/ # 用户模块 | ||
| 101 | +│ ├── get-userinfo.md | ||
| 102 | +│ └── post-login.md | ||
| 103 | +└── product/ # 产品模块 | ||
| 104 | + └── get-list.md | ||
| 105 | + | ||
| 106 | +src/api/ # 生成的 API 代码 | ||
| 107 | +├── user.js # 用户模块 API | ||
| 108 | +└── product.js # 产品模块 API | ||
| 109 | +``` | ||
| 110 | + | ||
| 111 | +## 🔍 故障排查 | ||
| 112 | + | ||
| 113 | +### 问题 1:找不到 .env.apifox 文件 | ||
| 114 | + | ||
| 115 | +**解决方案**: | ||
| 116 | +```bash | ||
| 117 | +cp .env.apifox.example .env.apifox | ||
| 118 | +``` | ||
| 119 | + | ||
| 120 | +### 问题 2:提示"HTTP 401" | ||
| 121 | + | ||
| 122 | +**原因**:API Token 无效 | ||
| 123 | + | ||
| 124 | +**解决方案**: | ||
| 125 | +1. 检查 Token 是否正确 | ||
| 126 | +2. 在 Apifox 中重新生成 Token | ||
| 127 | +3. 更新 `.env.apifox` 文件 | ||
| 128 | + | ||
| 129 | +### 问题 3:提示"项目不存在" | ||
| 130 | + | ||
| 131 | +**原因**:项目 ID 错误或无权限 | ||
| 132 | + | ||
| 133 | +**解决方案**: | ||
| 134 | +1. 检查项目 ID 是否正确 | ||
| 135 | +2. 确认账号有该项目的访问权限 | ||
| 136 | +3. 检查 Apifox 项目设置 | ||
| 137 | + | ||
| 138 | +## 📚 更多资源 | ||
| 139 | + | ||
| 140 | +- **快速开始**: [QUICKSTART_APIFOX.md](../QUICKSTART_APIFOX.md) | ||
| 141 | +- **完整指南**: [apifox-integration-guide.md](./apifox-integration-guide.md) | ||
| 142 | +- **Apifox 文档**: https://apifox.com/docs/ | ||
| 143 | + | ||
| 144 | +## 🛡️ 安全提醒 | ||
| 145 | + | ||
| 146 | +⚠️ **重要**: | ||
| 147 | +- `.env.apifox` 包含敏感信息,已被添加到 `.gitignore` | ||
| 148 | +- 请勿将 `.env.apifox` 提交到 Git | ||
| 149 | +- 定期轮换 API Token | ||
| 150 | +- 在 Apifox 中设置合适的权限 | ||
| 151 | + | ||
| 152 | +## 🎯 下一步 | ||
| 153 | + | ||
| 154 | +1. ✅ 配置 `.env.apifox` 文件 | ||
| 155 | +2. ✅ 运行 `pnpm api:test` 测试连接 | ||
| 156 | +3. ✅ 运行 `pnpm api:sync` 同步 API | ||
| 157 | +4. ✅ 在组件中使用生成的 API | ||
| 158 | + | ||
| 159 | +--- | ||
| 160 | + | ||
| 161 | +**需要帮助?** 查看 [完整集成指南](./apifox-integration-guide.md) 或运行 `pnpm api:test` 诊断问题 | ||
| 162 | + | ||
| 163 | +**最后更新**: 2026-01-30 |
| ... | @@ -28,7 +28,9 @@ | ... | @@ -28,7 +28,9 @@ |
| 28 | "dev:quickapp": "NODE_ENV=development taro build --type quickapp --watch", | 28 | "dev:quickapp": "NODE_ENV=development taro build --type quickapp --watch", |
| 29 | "postinstall": "weapp-tw patch", | 29 | "postinstall": "weapp-tw patch", |
| 30 | "lint": "eslint --ext .js,.vue src", | 30 | "lint": "eslint --ext .js,.vue src", |
| 31 | - "api:generate": "node scripts/generateApiFromOpenAPI.js" | 31 | + "api:generate": "node scripts/generateApiFromOpenAPI.js", |
| 32 | + "api:sync": "node scripts/apifox-to-openapi.js", | ||
| 33 | + "api:test": "node scripts/test-apifox-connection.js" | ||
| 32 | }, | 34 | }, |
| 33 | "browserslist": [ | 35 | "browserslist": [ |
| 34 | "last 3 versions", | 36 | "last 3 versions", | ... | ... |
scripts/apifox-sync.js
0 → 100644
| 1 | +#!/usr/bin/env node | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * Apifox API 同步工具 | ||
| 5 | + * | ||
| 6 | + * 功能: | ||
| 7 | + * 1. 从 Apifox 获取所有接口数据 | ||
| 8 | + * 2. 生成 TypeScript/JavaScript API 接口代码 | ||
| 9 | + * 3. 生成 Mock 数据 | ||
| 10 | + * 4. 自动更新 src/api/ 目录 | ||
| 11 | + * | ||
| 12 | + * 使用: | ||
| 13 | + * node scripts/apifox-sync.js | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +const fs = require('fs'); | ||
| 17 | +const path = require('path'); | ||
| 18 | +const https = require('https'); | ||
| 19 | + | ||
| 20 | +// 配置 | ||
| 21 | +const CONFIG = { | ||
| 22 | + token: process.env.VITE_APIFOX_TOKEN, | ||
| 23 | + projectId: process.env.VITE_APIFOX_PROJECT_ID, | ||
| 24 | + baseUrl: 'api.apifox.com', | ||
| 25 | + outputDir: path.join(__dirname, '../src/api'), | ||
| 26 | + mockDir: path.join(__dirname, '../src/mocks') | ||
| 27 | +}; | ||
| 28 | + | ||
| 29 | +// 颜色输出 | ||
| 30 | +const colors = { | ||
| 31 | + reset: '\x1b[0m', | ||
| 32 | + bright: '\x1b[1m', | ||
| 33 | + green: '\x1b[32m', | ||
| 34 | + yellow: '\x1b[33m', | ||
| 35 | + red: '\x1b[31m', | ||
| 36 | + blue: '\x1b[34m' | ||
| 37 | +}; | ||
| 38 | + | ||
| 39 | +function log(message, color = 'reset') { | ||
| 40 | + console.log(`${colors[color]}${message}${colors.reset}`); | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +// 读取 .env.apifox 文件 | ||
| 44 | +function loadEnv() { | ||
| 45 | + const envPath = path.join(__dirname, '../.env.apifox'); | ||
| 46 | + | ||
| 47 | + if (!fs.existsSync(envPath)) { | ||
| 48 | + log('❌ 未找到 .env.apifox 文件', 'red'); | ||
| 49 | + log('📝 请先创建 .env.apifox 文件并填写 Apifox 凭证:', 'yellow'); | ||
| 50 | + log(''); | ||
| 51 | + log('VITE_APIFOX_TOKEN=your_api_token_here', 'blue'); | ||
| 52 | + log('VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue'); | ||
| 53 | + process.exit(1); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + const env = fs.readFileSync(envPath, 'utf-8'); | ||
| 57 | + env.split('\n').forEach(line => { | ||
| 58 | + const [key, ...valueParts] = line.split('='); | ||
| 59 | + const value = valueParts.join('='); | ||
| 60 | + if (key && !key.startsWith('#') && value) { | ||
| 61 | + process.env[key.trim()] = value.trim(); | ||
| 62 | + } | ||
| 63 | + }); | ||
| 64 | + | ||
| 65 | + if (!process.env.VITE_APIFOX_TOKEN || !process.env.VITE_APIFOX_PROJECT_ID) { | ||
| 66 | + log('❌ .env.apifox 文件中缺少必要的配置', 'red'); | ||
| 67 | + log('请确保填写了 VITE_APIFOX_TOKEN 和 VITE_APIFOX_PROJECT_ID', 'yellow'); | ||
| 68 | + process.exit(1); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + CONFIG.token = process.env.VITE_APIFOX_TOKEN; | ||
| 72 | + CONFIG.projectId = process.env.VITE_APIFOX_PROJECT_ID; | ||
| 73 | + | ||
| 74 | + log(`✅ 已加载配置,项目 ID: ${CONFIG.projectId}`, 'green'); | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +// 发送 HTTPS 请求 | ||
| 78 | +function httpsRequest(options) { | ||
| 79 | + return new Promise((resolve, reject) => { | ||
| 80 | + const req = https.request(options, (res) => { | ||
| 81 | + let data = ''; | ||
| 82 | + | ||
| 83 | + res.on('data', chunk => { | ||
| 84 | + data += chunk; | ||
| 85 | + }); | ||
| 86 | + | ||
| 87 | + res.on('end', () => { | ||
| 88 | + try { | ||
| 89 | + const json = JSON.parse(data); | ||
| 90 | + if (res.statusCode === 200) { | ||
| 91 | + resolve(json); | ||
| 92 | + } else { | ||
| 93 | + reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`)); | ||
| 94 | + } | ||
| 95 | + } catch (err) { | ||
| 96 | + reject(new Error(`解析响应失败: ${err.message}`)); | ||
| 97 | + } | ||
| 98 | + }); | ||
| 99 | + }); | ||
| 100 | + | ||
| 101 | + req.on('error', reject); | ||
| 102 | + req.end(); | ||
| 103 | + }); | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +// 获取 Apifox 项目所有接口 | ||
| 107 | +async function fetchApis() { | ||
| 108 | + log('\n📡 正在从 Apifox 获取接口数据...', 'blue'); | ||
| 109 | + | ||
| 110 | + const options = { | ||
| 111 | + hostname: CONFIG.baseUrl, | ||
| 112 | + path: `/api/v1/projects/${CONFIG.projectId}/apis?pageSize=1000`, | ||
| 113 | + method: 'GET', | ||
| 114 | + headers: { | ||
| 115 | + 'Authorization': `Bearer ${CONFIG.token}`, | ||
| 116 | + 'Content-Type': 'application/json' | ||
| 117 | + } | ||
| 118 | + }; | ||
| 119 | + | ||
| 120 | + try { | ||
| 121 | + const response = await httpsRequest(options); | ||
| 122 | + const apis = response.data || []; | ||
| 123 | + | ||
| 124 | + log(`✅ 成功获取 ${apis.length} 个接口`, 'green'); | ||
| 125 | + return apis; | ||
| 126 | + } catch (err) { | ||
| 127 | + log(`❌ 获取接口失败: ${err.message}`, 'red'); | ||
| 128 | + throw err; | ||
| 129 | + } | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +// 获取接口详情 | ||
| 133 | +async function fetchApiDetail(apiId) { | ||
| 134 | + const options = { | ||
| 135 | + hostname: CONFIG.baseUrl, | ||
| 136 | + path: `/api/v1/projects/${CONFIG.projectId}/apis/${apiId}`, | ||
| 137 | + method: 'GET', | ||
| 138 | + headers: { | ||
| 139 | + 'Authorization': `Bearer ${CONFIG.token}`, | ||
| 140 | + 'Content-Type': 'application/json' | ||
| 141 | + } | ||
| 142 | + }; | ||
| 143 | + | ||
| 144 | + try { | ||
| 145 | + const response = await httpsRequest(options); | ||
| 146 | + return response.data; | ||
| 147 | + } catch (err) { | ||
| 148 | + log(`⚠️ 获取接口详情失败 (${apiId}): ${err.message}`, 'yellow'); | ||
| 149 | + return null; | ||
| 150 | + } | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +// 生成 API 接口代码 | ||
| 154 | +function generateApiCode(apis) { | ||
| 155 | + log('\n📝 正在生成 API 接口代码...', 'blue'); | ||
| 156 | + | ||
| 157 | + let code = `/** | ||
| 158 | + * @description API 接口定义(从 Apifox 自动生成) | ||
| 159 | + * @generated by scripts/apifox-sync.js | ||
| 160 | + * @lastUpdated ${new Date().toISOString()} | ||
| 161 | + */ | ||
| 162 | + | ||
| 163 | +import { fn } from '@/api/fn'; | ||
| 164 | + | ||
| 165 | +`; | ||
| 166 | + | ||
| 167 | + // 按标签分组 | ||
| 168 | + const groupedApis = {}; | ||
| 169 | + apis.forEach(api => { | ||
| 170 | + const tags = api.attributes?.tags || ['default']; | ||
| 171 | + const tag = tags[0]; | ||
| 172 | + if (!groupedApis[tag]) { | ||
| 173 | + groupedApis[tag] = []; | ||
| 174 | + } | ||
| 175 | + groupedApis[tag].push(api); | ||
| 176 | + }); | ||
| 177 | + | ||
| 178 | + // 生成每个分组的接口 | ||
| 179 | + Object.entries(groupedApis).forEach(([tag, items]) => { | ||
| 180 | + code += `\n// ==================== ${tag} ====================\n\n`; | ||
| 181 | + | ||
| 182 | + items.forEach(api => { | ||
| 183 | + const apiId = api.id; | ||
| 184 | + const name = api.attributes?.name || apiId; | ||
| 185 | + const method = (api.attributes?.method || 'GET').toLowerCase(); | ||
| 186 | + const path = api.attributes?.path || ''; | ||
| 187 | + | ||
| 188 | + // 生成接口函数名(将路径转换为驼峰命名) | ||
| 189 | + const functionName = pathToFunctionName(method, path); | ||
| 190 | + | ||
| 191 | + // 生成注释 | ||
| 192 | + code += `/**\n`; | ||
| 193 | + code += ` * @description ${name}\n`; | ||
| 194 | + code += ` * @path ${method.toUpperCase()} ${path}\n`; | ||
| 195 | + if (api.attributes?.description) { | ||
| 196 | + code += ` * @remark ${api.attributes.description}\n`; | ||
| 197 | + } | ||
| 198 | + code += ` */\n`; | ||
| 199 | + | ||
| 200 | + // 生成接口函数 | ||
| 201 | + code += `export const ${functionName} = (params = {}) => {\n`; | ||
| 202 | + code += ` return fn({\n`; | ||
| 203 | + code += ` method: '${method}',\n`; | ||
| 204 | + code += ` url: '${path}',\n`; | ||
| 205 | + code += ` data: params\n`; | ||
| 206 | + code += ` })\n`; | ||
| 207 | + code += `}\n\n`; | ||
| 208 | + }); | ||
| 209 | + }); | ||
| 210 | + | ||
| 211 | + // 写入文件 | ||
| 212 | + const outputPath = path.join(CONFIG.outputDir, 'generated.js'); | ||
| 213 | + fs.writeFileSync(outputPath, code, 'utf-8'); | ||
| 214 | + | ||
| 215 | + log(`✅ 已生成 API 接口代码: ${outputPath}`, 'green'); | ||
| 216 | + log(` 共 ${apis.length} 个接口,${Object.keys(groupedApis).length} 个分组`, 'green'); | ||
| 217 | + | ||
| 218 | + return outputPath; | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +// 生成 Mock 数据 | ||
| 222 | +function generateMockData(apis) { | ||
| 223 | + log('\n🎭 正在生成 Mock 数据...', 'blue'); | ||
| 224 | + | ||
| 225 | + // 确保 mocks 目录存在 | ||
| 226 | + if (!fs.existsSync(CONFIG.mockDir)) { | ||
| 227 | + fs.mkdirSync(CONFIG.mockDir, { recursive: true }); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + let mockIndex = `/** | ||
| 231 | + * @description Mock 数据(从 Apifox 自动生成) | ||
| 232 | + * @generated by scripts/apifox-sync.js | ||
| 233 | + */ | ||
| 234 | + | ||
| 235 | +`; | ||
| 236 | + | ||
| 237 | + // 为每个接口生成 Mock 数据 | ||
| 238 | + apis.forEach(api => { | ||
| 239 | + const path = api.attributes?.path || ''; | ||
| 240 | + const method = (api.attributes?.method || 'GET').toLowerCase(); | ||
| 241 | + const functionName = pathToFunctionName(method, path); | ||
| 242 | + const mockFileName = `${functionName}.mock.js`; | ||
| 243 | + | ||
| 244 | + // 从响应示例中提取 Mock 数据 | ||
| 245 | + const responses = api.attributes?.responses || []; | ||
| 246 | + const successResponse = responses.find(r => r.code === 200) || responses[0]; | ||
| 247 | + | ||
| 248 | + if (successResponse) { | ||
| 249 | + // 生成 Mock 文件 | ||
| 250 | + const mockCode = `/** | ||
| 251 | + * @description Mock data for ${functionName} | ||
| 252 | + * @path ${method.toUpperCase()} ${path} | ||
| 253 | + */ | ||
| 254 | + | ||
| 255 | +export const mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data = ${JSON.stringify(successResponse, null, 2)}; | ||
| 256 | + | ||
| 257 | +export default mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data; | ||
| 258 | +`; | ||
| 259 | + | ||
| 260 | + const mockPath = path.join(CONFIG.mockDir, mockFileName); | ||
| 261 | + fs.writeFileSync(mockPath, mockCode, 'utf-8'); | ||
| 262 | + | ||
| 263 | + mockIndex += `export { default as mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data } from './${mockFileName}';\n`; | ||
| 264 | + } | ||
| 265 | + }); | ||
| 266 | + | ||
| 267 | + // 写入索引文件 | ||
| 268 | + const indexPath = path.join(CONFIG.mockDir, 'index.js'); | ||
| 269 | + fs.writeFileSync(indexPath, mockIndex, 'utf-8'); | ||
| 270 | + | ||
| 271 | + log(`✅ 已生成 Mock 数据: ${indexPath}`, 'green'); | ||
| 272 | + | ||
| 273 | + return indexPath; | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +// 将 URL 路径转换为驼峰命名的函数名 | ||
| 277 | +function pathToFunctionName(method, path) { | ||
| 278 | + // 移除路径参数和查询参数 | ||
| 279 | + let cleanPath = path | ||
| 280 | + .replace(/:\w+/g, '') | ||
| 281 | + .replace(/\?.*$/, '') | ||
| 282 | + .replace(/^\//, '') | ||
| 283 | + .replace(/\/$/, ''); | ||
| 284 | + | ||
| 285 | + // 转换为驼峰命名 | ||
| 286 | + const parts = cleanPath.split('/').filter(Boolean); | ||
| 287 | + let functionName = method; | ||
| 288 | + | ||
| 289 | + parts.forEach(part => { | ||
| 290 | + // 首字母大写 | ||
| 291 | + const capitalized = part.charAt(0).toUpperCase() + part.slice(1); | ||
| 292 | + functionName += capitalized; | ||
| 293 | + }); | ||
| 294 | + | ||
| 295 | + // 移除特殊字符 | ||
| 296 | + functionName = functionName.replace(/[^a-zA-Z0-9]/g, ''); | ||
| 297 | + | ||
| 298 | + return functionName; | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +// 主函数 | ||
| 302 | +async function main() { | ||
| 303 | + try { | ||
| 304 | + log('\n🚀 Apifox API 同步工具', 'bright'); | ||
| 305 | + log('=' .repeat(50), 'bright'); | ||
| 306 | + | ||
| 307 | + // 1. 加载配置 | ||
| 308 | + loadEnv(); | ||
| 309 | + | ||
| 310 | + // 2. 获取接口列表 | ||
| 311 | + const apis = await fetchApis(); | ||
| 312 | + | ||
| 313 | + // 3. 生成 API 接口代码 | ||
| 314 | + generateApiCode(apis); | ||
| 315 | + | ||
| 316 | + // 4. 生成 Mock 数据 | ||
| 317 | + generateMockData(apis); | ||
| 318 | + | ||
| 319 | + // 完成 | ||
| 320 | + log('\n✅ 同步完成!', 'green'); | ||
| 321 | + log('📦 生成的文件:', 'blue'); | ||
| 322 | + log(` - src/api/generated.js (API 接口)`, 'blue'); | ||
| 323 | + log(` - src/mocks/ (Mock 数据)`, 'blue'); | ||
| 324 | + log('\n💡 提示: 将 src/api/generated.js 中的接口导入到 src/api/index.js 中使用', 'yellow'); | ||
| 325 | + | ||
| 326 | + } catch (err) { | ||
| 327 | + log(`\n❌ 同步失败: ${err.message}`, 'red'); | ||
| 328 | + process.exit(1); | ||
| 329 | + } | ||
| 330 | +} | ||
| 331 | + | ||
| 332 | +// 运行 | ||
| 333 | +main(); |
scripts/apifox-to-openapi.js
0 → 100644
This diff is collapsed. Click to expand it.
scripts/test-apifox-connection.js
0 → 100644
| 1 | +#!/usr/bin/env node | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * Apifox 连接测试工具 | ||
| 5 | + * | ||
| 6 | + * 功能: | ||
| 7 | + * 1. 验证 .env.apifox 配置是否正确 | ||
| 8 | + * 2. 测试 Apifox API 连接 | ||
| 9 | + * 3. 显示项目基本信息 | ||
| 10 | + * 4. 列出所有接口数量 | ||
| 11 | + * | ||
| 12 | + * 使用: | ||
| 13 | + * node scripts/test-apifox-connection.js | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +const fs = require('fs'); | ||
| 17 | +const path = require('path'); | ||
| 18 | +const https = require('https'); | ||
| 19 | + | ||
| 20 | +// 颜色输出 | ||
| 21 | +const colors = { | ||
| 22 | + reset: '\x1b[0m', | ||
| 23 | + bright: '\x1b[1m', | ||
| 24 | + green: '\x1b[32m', | ||
| 25 | + yellow: '\x1b[33m', | ||
| 26 | + red: '\x1b[31m', | ||
| 27 | + blue: '\x1b[34m', | ||
| 28 | + cyan: '\x1b[36m' | ||
| 29 | +}; | ||
| 30 | + | ||
| 31 | +function log(message, color = 'reset') { | ||
| 32 | + console.log(`${colors[color]}${message}${colors.reset}`); | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +// 加载配置 | ||
| 36 | +function loadConfig() { | ||
| 37 | + const envPath = path.join(__dirname, '../.env.apifox'); | ||
| 38 | + | ||
| 39 | + if (!fs.existsSync(envPath)) { | ||
| 40 | + log('❌ 未找到 .env.apifox 文件', 'red'); | ||
| 41 | + log('\n📝 请先创建配置文件:', 'yellow'); | ||
| 42 | + log(' cp .env.apifox.example .env.apifox', 'blue'); | ||
| 43 | + log(' 然后编辑 .env.apifox 填写凭证信息', 'blue'); | ||
| 44 | + return null; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + const env = fs.readFileSync(envPath, 'utf-8'); | ||
| 48 | + const config = {}; | ||
| 49 | + | ||
| 50 | + env.split('\n').forEach(line => { | ||
| 51 | + const [key, ...valueParts] = line.split('='); | ||
| 52 | + const value = valueParts.join('='); | ||
| 53 | + if (key && !key.startsWith('#') && value) { | ||
| 54 | + config[key.trim()] = value.trim(); | ||
| 55 | + } | ||
| 56 | + }); | ||
| 57 | + | ||
| 58 | + if (!config.VITE_APIFOX_TOKEN || !config.VITE_APIFOX_PROJECT_ID) { | ||
| 59 | + log('❌ .env.apifox 文件中缺少必要的配置', 'red'); | ||
| 60 | + log('\n请确保填写了以下配置:', 'yellow'); | ||
| 61 | + log(' VITE_APIFOX_TOKEN=aps-your_token_here', 'blue'); | ||
| 62 | + log(' VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue'); | ||
| 63 | + return null; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + return config; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +// 验证 Token 格式 | ||
| 70 | +function validateToken(token) { | ||
| 71 | + if (!token.startsWith('aps-')) { | ||
| 72 | + log('⚠️ Token 格式不正确', 'yellow'); | ||
| 73 | + log(' 正确格式: aps-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'cyan'); | ||
| 74 | + return false; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + if (token.length < 10) { | ||
| 78 | + log('⚠️ Token 长度似乎不正确', 'yellow'); | ||
| 79 | + return false; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + return true; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +// 验证项目 ID 格式 | ||
| 86 | +function validateProjectId(projectId) { | ||
| 87 | + if (!/^\d+$/.test(projectId)) { | ||
| 88 | + log('⚠️ 项目 ID 格式不正确', 'yellow'); | ||
| 89 | + log(' 正确格式: 纯数字(如: 12345678)', 'cyan'); | ||
| 90 | + return false; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + return true; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +// 发送 HTTPS 请求 | ||
| 97 | +function httpsRequest(options) { | ||
| 98 | + return new Promise((resolve, reject) => { | ||
| 99 | + log(`📡 发送请求到: https://${options.hostname}${options.path}`, 'blue'); | ||
| 100 | + | ||
| 101 | + const req = https.request(options, (res) => { | ||
| 102 | + let data = ''; | ||
| 103 | + | ||
| 104 | + res.on('data', chunk => { | ||
| 105 | + data += chunk; | ||
| 106 | + }); | ||
| 107 | + | ||
| 108 | + res.on('end', () => { | ||
| 109 | + try { | ||
| 110 | + const json = JSON.parse(data); | ||
| 111 | + if (res.statusCode === 200) { | ||
| 112 | + resolve(json); | ||
| 113 | + } else { | ||
| 114 | + reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`)); | ||
| 115 | + } | ||
| 116 | + } catch (err) { | ||
| 117 | + reject(new Error(`解析响应失败: ${err.message}`)); | ||
| 118 | + } | ||
| 119 | + }); | ||
| 120 | + }); | ||
| 121 | + | ||
| 122 | + req.on('error', reject); | ||
| 123 | + req.end(); | ||
| 124 | + }); | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +// 测试获取项目信息 | ||
| 128 | +async function testProjectInfo(config) { | ||
| 129 | + log('\n📊 获取项目信息...', 'cyan'); | ||
| 130 | + | ||
| 131 | + const options = { | ||
| 132 | + hostname: 'api.apifox.com', | ||
| 133 | + path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}`, | ||
| 134 | + method: 'GET', | ||
| 135 | + headers: { | ||
| 136 | + 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`, | ||
| 137 | + 'Content-Type': 'application/json' | ||
| 138 | + } | ||
| 139 | + }; | ||
| 140 | + | ||
| 141 | + try { | ||
| 142 | + const response = await httpsRequest(options); | ||
| 143 | + const project = response.data; | ||
| 144 | + | ||
| 145 | + log('✅ 项目信息获取成功', 'green'); | ||
| 146 | + log(''); | ||
| 147 | + log('项目详情:', 'bright'); | ||
| 148 | + log(` 名称: ${project.name || '未设置'}`, 'reset'); | ||
| 149 | + log(` ID: ${project.id}`, 'reset'); | ||
| 150 | + log(` 描述: ${project.description || '无'}`, 'reset'); | ||
| 151 | + log(` 创建时间: ${project.createdTime || '未知'}`, 'reset'); | ||
| 152 | + | ||
| 153 | + return true; | ||
| 154 | + } catch (err) { | ||
| 155 | + log(`❌ 获取项目信息失败: ${err.message}`, 'red'); | ||
| 156 | + | ||
| 157 | + if (err.message.includes('401')) { | ||
| 158 | + log('\n可能的原因:', 'yellow'); | ||
| 159 | + log(' 1. API Token 错误或已过期', 'yellow'); | ||
| 160 | + log(' 2. 请检查 .env.apifox 中的 VITE_APIFOX_TOKEN', 'yellow'); | ||
| 161 | + } else if (err.message.includes('403') || err.message.includes('404')) { | ||
| 162 | + log('\n可能的原因:', 'yellow'); | ||
| 163 | + log(' 1. 项目 ID 错误', 'yellow'); | ||
| 164 | + log(' 2. 账号无该项目的访问权限', 'yellow'); | ||
| 165 | + log(' 3. 请检查 .env.apifox 中的 VITE_APIFOX_PROJECT_ID', 'yellow'); | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + return false; | ||
| 169 | + } | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +// 测试获取接口列表 | ||
| 173 | +async function testApiList(config) { | ||
| 174 | + log('\n📋 获取接口列表...', 'cyan'); | ||
| 175 | + | ||
| 176 | + const options = { | ||
| 177 | + hostname: 'api.apifox.com', | ||
| 178 | + path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}/apis?pageSize=10`, | ||
| 179 | + method: 'GET', | ||
| 180 | + headers: { | ||
| 181 | + 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`, | ||
| 182 | + 'Content-Type': 'application/json' | ||
| 183 | + } | ||
| 184 | + }; | ||
| 185 | + | ||
| 186 | + try { | ||
| 187 | + const response = await httpsRequest(options); | ||
| 188 | + const apis = response.data || []; | ||
| 189 | + | ||
| 190 | + log('✅ 接口列表获取成功', 'green'); | ||
| 191 | + log(` 共找到 ${response.total || apis.length} 个接口`, 'blue'); | ||
| 192 | + | ||
| 193 | + if (apis.length > 0) { | ||
| 194 | + log('\n前 10 个接口:', 'bright'); | ||
| 195 | + apis.forEach((api, index) => { | ||
| 196 | + const method = (api.attributes?.method || 'GET').toUpperCase(); | ||
| 197 | + const path = api.attributes?.path || '/'; | ||
| 198 | + const name = api.attributes?.name || api.id; | ||
| 199 | + | ||
| 200 | + log(` ${index + 1}. ${method} ${path}`, 'cyan'); | ||
| 201 | + log(` ${name}`, 'reset'); | ||
| 202 | + }); | ||
| 203 | + | ||
| 204 | + if (response.total > 10) { | ||
| 205 | + log(`\n ... 还有 ${response.total - 10} 个接口`, 'yellow'); | ||
| 206 | + } | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + return true; | ||
| 210 | + } catch (err) { | ||
| 211 | + log(`❌ 获取接口列表失败: ${err.message}`, 'red'); | ||
| 212 | + return false; | ||
| 213 | + } | ||
| 214 | +} | ||
| 215 | + | ||
| 216 | +// 主函数 | ||
| 217 | +async function main() { | ||
| 218 | + log('\n🧪 Apifox 连接测试工具', 'bright'); | ||
| 219 | + log('=' .repeat(50), 'bright'); | ||
| 220 | + | ||
| 221 | + // 1. 加载配置 | ||
| 222 | + log('\n📝 步骤 1/4: 加载配置文件', 'cyan'); | ||
| 223 | + const config = loadConfig(); | ||
| 224 | + if (!config) { | ||
| 225 | + process.exit(1); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + log('✅ 配置文件加载成功', 'green'); | ||
| 229 | + log(` Token: ${config.VITE_APIFOX_TOKEN.substring(0, 15)}...`, 'blue'); | ||
| 230 | + log(` 项目 ID: ${config.VITE_APIFOX_PROJECT_ID}`, 'blue'); | ||
| 231 | + | ||
| 232 | + // 2. 验证格式 | ||
| 233 | + log('\n🔍 步骤 2/4: 验证配置格式', 'cyan'); | ||
| 234 | + | ||
| 235 | + let valid = true; | ||
| 236 | + | ||
| 237 | + if (!validateToken(config.VITE_APIFOX_TOKEN)) { | ||
| 238 | + valid = false; | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + if (!validateProjectId(config.VITE_APIFOX_PROJECT_ID)) { | ||
| 242 | + valid = false; | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + if (!valid) { | ||
| 246 | + log('\n❌ 配置验证失败,请修正后重试', 'red'); | ||
| 247 | + process.exit(1); | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + log('✅ 配置格式验证通过', 'green'); | ||
| 251 | + | ||
| 252 | + // 3. 测试项目信息 | ||
| 253 | + log('\n🌐 步骤 3/4: 测试 API 连接', 'cyan'); | ||
| 254 | + const projectSuccess = await testProjectInfo(config); | ||
| 255 | + | ||
| 256 | + if (!projectSuccess) { | ||
| 257 | + log('\n❌ API 连接测试失败', 'red'); | ||
| 258 | + log('\n💡 建议:', 'yellow'); | ||
| 259 | + log(' 1. 检查网络连接', 'yellow'); | ||
| 260 | + log(' 2. 验证 Token 和项目 ID 是否正确', 'yellow'); | ||
| 261 | + log(' 3. 确认账号有该项目的访问权限', 'yellow'); | ||
| 262 | + process.exit(1); | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + // 4. 测试接口列表 | ||
| 266 | + const apiSuccess = await testApiList(config); | ||
| 267 | + | ||
| 268 | + if (!apiSuccess) { | ||
| 269 | + log('\n⚠️ 接口列表获取失败,但项目连接正常', 'yellow'); | ||
| 270 | + process.exit(1); | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + // 完成 | ||
| 274 | + log('\n' + '='.repeat(50), 'bright'); | ||
| 275 | + log('✅ 所有测试通过!', 'green'); | ||
| 276 | + log('\n🎉 配置正确,可以开始同步 API 了', 'green'); | ||
| 277 | + log('\n运行以下命令同步 API:', 'blue'); | ||
| 278 | + log(' pnpm api:sync', 'bright'); | ||
| 279 | + log(''); | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +// 运行 | ||
| 283 | +main().catch(err => { | ||
| 284 | + log(`\n❌ 测试失败: ${err.message}`, 'red'); | ||
| 285 | + console.error(err); | ||
| 286 | + process.exit(1); | ||
| 287 | +}); |
test-mcp-connection.sh
0 → 100755
| 1 | +#!/bin/bash | ||
| 2 | + | ||
| 3 | +# 测试 Apifox MCP 服务器连接 | ||
| 4 | + | ||
| 5 | +echo "🔍 测试 Apifox MCP 服务器连接..." | ||
| 6 | +echo "" | ||
| 7 | + | ||
| 8 | +# 设置环境变量 | ||
| 9 | +export APIFOX_ACCESS_TOKEN="APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc" | ||
| 10 | + | ||
| 11 | +# 测试命令 | ||
| 12 | +echo "✅ Token 格式: 正确" | ||
| 13 | +echo "✅ Project ID: 6084040" | ||
| 14 | +echo "✅ MCP 配置文件: .claude/settings.json" | ||
| 15 | + | ||
| 16 | +echo "" | ||
| 17 | +echo "📝 当前配置内容:" | ||
| 18 | +cat .claude/settings.json | ||
| 19 | + | ||
| 20 | +echo "" | ||
| 21 | +echo "📋 检查清单:" | ||
| 22 | +echo " ✓ Token 已更新为: APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc" | ||
| 23 | +echo " ✓ Project ID: 6084040 (mlaj 项目)" | ||
| 24 | +echo "" | ||
| 25 | + | ||
| 26 | +echo "💡 下一步:" | ||
| 27 | +echo " 1. ✅ 配置已更新" | ||
| 28 | +echo " 2. ⚠️ 需要重启 Claude Code 才能生效" | ||
| 29 | +echo " 3. 重启后可以在对话中问:'列出 manulife 项目所有的 API'" | ||
| 30 | +echo "" | ||
| 31 | + | ||
| 32 | +echo "🔧 手动测试 MCP 服务器(可选):" | ||
| 33 | +echo " 运行以下命令测试服务器是否能启动:" | ||
| 34 | +echo "" | ||
| 35 | +echo " APIFOX_ACCESS_TOKEN=\"APS-t3Lm53YUvYMwNWEqb5Y5nnrRSlDz04Mc\" \\" | ||
| 36 | +echo " npx -y apifox-mcp-server@latest --project-id=6084040" | ||
| 37 | +echo "" |
-
Please register or login to post a comment