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,保护敏感配置文件不被提交
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
...@@ -10,6 +10,7 @@ yarn-error.log* ...@@ -10,6 +10,7 @@ yarn-error.log*
10 pnpm-debug.log* 10 pnpm-debug.log*
11 .env.local 11 .env.local
12 .env.*.local 12 .env.*.local
13 +.env.apifox
13 unpackage/ 14 unpackage/
14 .history/ 15 .history/
15 .tmp/ 16 .tmp/
......
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 +**需要帮助?** 查看完整文档或联系项目组
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
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",
......
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();
This diff is collapsed. Click to expand it.
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 +});
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 ""