hookehuyr

refactor: 清理 Apifox API 依赖并优化工具链

- 删除 7 个不再工作的 Apifox API 相关脚本
- 删除 .env.apifox 配置文件
- 简化 package.json 命令(移除 api:sync 和 api:test)
- 修复 API 生成工具:新增模块接口信息显示为 undefined
- 新增文档模块接口(初稿):weekHotAPI、fileListAPI
- 创建 API_GUIDE.md 使用指南
- 更新 API 联调日志(v2.3)和 CHANGELOG

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -5,6 +5,73 @@
---
## [2026-02-04] - 清理 Apifox 依赖并优化工具链
### 移除
- Apifox API 相关脚本(不再工作)
- 删除 `scripts/apifox-to-openapi.js` - 无法从 API 获取数据
- 删除 `scripts/apifox-sync.js` - API 同步脚本
- 删除 `scripts/debug-apifox-response.js` - 调试脚本
- 删除 `scripts/test-apifox-connection.js` - 测试脚本
- 删除 `scripts/test-apifox-endpoints.js` - 测试脚本
- 删除 `scripts/test-apifox-export.js` - 测试脚本
- 删除 `.env.apifox` 配置文件
- package.json 命令清理
- 移除 `api:sync` 命令(依赖 Apifox API)
- 移除 `api:test` 命令(依赖已删除的脚本)
- 保留 `api:generate` 命令(从 OpenAPI 文档生成代码)
### 新增
- API 文档维护指南(`scripts/API_GUIDE.md`
- 详细说明手动维护 OpenAPI 文档的工作流程
- 提供完整的文档格式示例和最佳实践
- 包含故障排除指南
- 新增文档模块接口(初稿)
- `weekHotAPI` - 本周热门资料
- `fileListAPI` - 文档列表
- 标记为初稿状态,后端字段未最终确定
### 优化
- API 生成脚本(`scripts/generateApiFromOpenAPI.js`
- 修复新增模块接口信息显示为 undefined 的问题
- 使用 `extractAPIInfo` 提取完整的接口信息(方法、路径、描述)
- 改进接口信息显示逻辑
### 文档
- 更新 API 接口联调日志(`docs/api-integration-log.md`
- 新增文档模块:2个初稿接口(字段未确定)
- 更新总体进度:26个接口(12已完成,9待联调,2初稿,3已废弃)
- 文档版本升级到 v2.3
- 添加移除 Apifox 依赖的变更记录
- 更新快速索引,添加文档模块链接
---
**详细信息**
- **影响文件**:
- `scripts/apifox-to-openapi.js` (已删除)
- `scripts/apifox-sync.js` (已删除)
- `scripts/debug-apifox-response.js` (已删除)
- `scripts/test-apifox-connection.js` (已删除)
- `scripts/test-apifox-endpoints.js` (已删除)
- `scripts/test-apifox-export.js` (已删除)
- `.env.apifox` (已删除)
- `scripts/API_GUIDE.md` (新增)
- `scripts/generateApiFromOpenAPI.js` (修复)
- `package.json` (命令清理)
- `docs/api-integration-log.md` (更新)
- `docs/api-specs/file/week_hot.md` (新增)
- `docs/api-specs/get_file_list/file_list.md` (新增)
- **技术栈**: Node.js, fs 模块, js-yaml
- **测试状态**: ✅ 已测试通过
- **备注**:
- 项目现在完全采用手动维护 OpenAPI 文档的方式
- 工作流:从 Apifox 导出 → 放入 `docs/api-specs/` → 运行 `pnpm api:generate`
- 不再依赖 Apifox API,更加稳定可控
- 文档模块接口标记为初稿,待后端确认字段后更新
---
## [2026-02-04] - 优化 OpenAPI 文档生成工具
### 优化
......
......@@ -4,11 +4,12 @@
## 📊 总体进度
- **总接口数**: 24
- **已完成**: 12 (50.0%)
- **总接口数**: 26
- **已完成**: 12 (46.2%)
- **联调中**: 0 (0%)
- **已废弃**: 3 (12.5%)
- **待联调**: 9 (37.5%)
- **已废弃**: 3 (11.5%)
- **待联调**: 9 (34.6%)
- **初稿(字段未确定)**: 2 (7.7%)
- **有阻塞**: 0
---
......@@ -18,6 +19,15 @@
- 添加 CLAUDE.md 文件过滤,避免将文档文件当作 API 文档处理
- 改进新增模块检测逻辑,显示新增模块包含的所有接口
- 优化变更检测报告,提升可读性
- 修复接口信息显示为 undefined 的问题
- 🗑️ **清理 Apifox API 依赖**
- 删除不再工作的 Apifox API 相关脚本(apifox-to-openapi.js、apifox-sync.js 等)
- 删除 .env.apifox 配置文件
- 更新 package.json,移除 api:sync 和 api:test 命令
- 创建 API_GUIDE.md 使用指南,明确手动维护 OpenAPI 文档的工作流
- 📝 **新增文档模块接口(初稿)**
- weekHotAPI(本周热门资料):字段未最终确定,待联调确认
- fileListAPI(文档列表):字段未最终确定,待联调确认
-**收藏模块后端完成**:3 个收藏接口(addAPI、delAPI、listAPI)后端已开发完成,前端待联调
-**埋点接口后端完成**:埋点接口(addAPI)后端已开发完成,前端待联调
-**产品模块联调完成**:产品列表接口(listAPI)联调成功
......@@ -818,6 +828,121 @@
---
### 文档模块
#### 接口 1: 本周热门资料(初稿)
**接口信息**
- **接口名称**: `weekHotAPI`
- **接口路径**: `/srv/?a=file&t=week_hot`
- **请求方法**: GET
- **负责页面**: 待确认
- **负责人**: 后端团队
**接口文档更新记录**
| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
|------|------|---------|---------|---------|
| 2026-02-04 | v0.1 | 初稿版本 | 后端字段未确定,待联调确认 | [查看](docs/api-specs/file/week_hot.md) |
**页面调试情况**
| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
|------|---------|---------|---------|------|
| 2026-02-04 | - | 后端字段未确定 | 待后端确认后更新 | ⏳ 待联调 |
**接口状态**: ⏳ 初稿 - 后端字段未确定
**备注**:
- ⚠️ **初稿状态**:当前接口文档为初稿,后端字段可能还有调整
- **参数**:
- `page`: 页码,从 0 开始
- `limit`: 每页数量
- **预期返回数据结构**:
```javascript
{
code: 1,
data: {
list: [
{
meta_id: 123, // 文件ID
name: "文件名称", // 文件名称
src: "https://...", // 文件URL
size: "1.2MB", // 文件大小
read_people_count: 100, // 学习人数
read_people_percent: 85.5 // 学习人数比例
}
]
}
}
```
- **字段确认状态**:待后端确认最终字段结构
- 实现位置:`src/api/file.js:weekHotAPI`
---
#### 接口 2: 文档列表(初稿)
**接口信息**
- **接口名称**: `fileListAPI`
- **接口路径**: `/srv/?a=get_file_list&t=file_list`
- **请求方法**: GET
- **负责页面**: 待确认
- **负责人**: 后端团队
**接口文档更新记录**
| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
|------|------|---------|---------|---------|
| 2026-02-04 | v0.1 | 初稿版本 | 后端字段未确定,待联调确认 | [查看](docs/api-specs/get_file_list/file_list.md) |
**页面调试情况**
| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
|------|---------|---------|---------|------|
| 2026-02-04 | - | 后端字段未确定 | 待后端确认后更新 | ⏳ 待联调 |
**接口状态**: ⏳ 初稿 - 后端字段未确定
**备注**:
- ⚠️ **初稿状态**:当前接口文档为初稿,后端字段可能还有调整
- **参数**:
- `client_id`: 主体 ID(可选)
- `cid`: 分类 ID(可选)
- `page`: 页码(从 0 开始)
- `limit`: 每页数量
- **预期返回数据结构**:
```javascript
{
code: 1,
data: {
categories: [
{ id: 2769851, name: "分类名称" }
],
list: [
{
id: 2769856, // 产品id
product_name: "产品名称", // 产品名
recommend: "hot", // 推荐位: normal-普通, hot-热卖
form_sn: "customize_jsj_pnzuky", // 产品编号
created_time: "2026-02-03 10:36:29",
categories: [ // 产品所属分类
{ id: "2769851", name: "分类名称" }
],
tags: [ // 产品标签
{ id: "2769847", name: "标签名", bg_color: "#3e5160", text_color: "#ffffff" }
]
}
],
total: 3 // 产品总数
}
}
```
- **字段确认状态**:待后端确认最终字段结构
- 实现位置:`src/api/get_file_list.js:fileListAPI`
---
### 埋点模块
#### 接口 1: 添加埋点
......@@ -938,6 +1063,7 @@
- [❌ 已废弃](#通用模块) - 3个接口
- [⏳ 待联调](#收藏模块) - 3个接口
- [⏳ 待联调](#埋点模块) - 1个接口
- [⏳ 初稿 - 字段未确定](#文档模块) - 2个接口
- [⏳ 后端开发中](#消息模块) - 2个接口
### 按模块查看
......@@ -947,6 +1073,7 @@
- [产品](#产品模块) - ✅ 1个已完成
- [收藏](#收藏模块) - ⏳ 3个待联调
- [埋点](#埋点模块) - ⏳ 1个待联调
- [文档](#文档模块) - ⏳ 2个初稿
- [消息](#消息模块) - ⏳ 2个后端开发中
- [知识库](#知识库模块) - ⏳ 未开始
- [家办](#家办模块) - ⏳ 未开始
......@@ -984,20 +1111,20 @@
---
**最后更新时间**: 2026-02-04 13:00
**文档版本**: v2.2
**最后更新时间**: 2026-02-04 15:30
**文档版本**: v2.3
**更新内容**:
- 收藏模块:3个接口后端已完成,前端待联调
- addAPI(添加收藏)
- delAPI(取消收藏)
- listAPI(收藏列表)
- 埋点模块:1个接口后端已完成,前端待联调
- addAPI(添加埋点)
- OpenAPI 文档生成工具优化:
- 添加 CLAUDE.md 文件过滤
- 改进新增模块检测逻辑
- 优化变更检测报告
- 更新总体进度:24个接口(12个已完成,9个待联调,3个已废弃)
- 📝 **新增文档模块接口(初稿)**
- weekHotAPI(本周热门资料):字段未最终确定
- fileListAPI(文档列表):字段未最终确定
- 🗑️ **移除 Apifox API 依赖**
- 删除 apifox-to-openapi.js 等不再工作的脚本
- 更新 package.json,简化为 api:generate 单一命令
- 明确手动维护 OpenAPI 文档的工作流
- 🔧 **API 生成工具优化**
- 修复新增模块接口信息显示为 undefined 的问题
- 改进接口信息提取逻辑
- 更新总体进度:26个接口(12个已完成,9个待联调,2个初稿,3个已废弃)
**历史版本**:
- v2.1 (2026-02-03 21:00): 产品模块联调完成
......
# 本周热门资料
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 本周热门资料
deprecated: false
description: ''
tags:
- 文档
parameters:
- name: f
in: query
description: ''
required: true
example: manulife
schema:
type: string
- name: a
in: query
description: ''
required: true
example: file
schema:
type: string
- name: t
in: query
description: ''
required: true
example: week_hot
schema:
type: string
- name: page
in: query
description: 页码,从0开始
required: true
example: '0'
schema:
type: string
- name: limit
in: query
description: 每页数量
required: true
example: '10'
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
data:
type: object
properties:
list:
type: array
items:
type: object
properties:
meta_id:
type: integer
title: 文件ID
name:
type: string
title: 文件名称
src:
type: string
title: 文件URL
size:
type: string
title: 文件大小
read_people_count:
type: integer
title: 学习人数
read_people_percent:
type: number
title: 学习人数比例
x-apifox-orders:
- meta_id
- name
- src
- size
- read_people_count
- read_people_percent
required:
- size
- read_people_count
- read_people_percent
required:
- list
x-apifox-orders:
- list
required:
- code
- msg
- data
x-apifox-orders:
- code
- msg
- data
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 文档
x-apifox-status: developing
x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-415055277-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 文档列表
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 文档列表
deprecated: false
description: ''
tags:
- 文档
parameters:
- name: f
in: query
description: ''
required: true
example: manulife
schema:
type: string
- name: a
in: query
description: ''
required: true
example: get_file_list
schema:
type: string
- name: t
in: query
description: ''
required: false
example: file_list
schema:
type: string
- name: client_id
in: query
description: 主体id
required: false
example: '30901'
schema:
type: string
- name: limit
in: query
description: ''
required: false
example: '10'
schema:
type: string
- name: page
in: query
description: ''
required: false
example: '0'
schema:
type: string
- name: cid
in: query
description: 分类id
required: false
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: integer
data:
type: object
properties:
categories:
type: array
items:
type: object
properties:
id:
type: integer
title: 分类id
name:
type: string
title: 分类名
x-apifox-orders:
- id
- name
title: 分类列表
list:
type: array
items:
type: object
properties:
id:
type: integer
title: 产品id
product_name:
type: string
title: 产品名
recommend:
type: string
title: '推荐位: normal-普通, hot-热卖'
form_sn:
type: string
created_time:
type: string
title: 创建时间
categories:
type: array
items:
type: object
properties:
id:
type: string
title: 分类id
name:
type: string
title: 分类名
required:
- id
- name
x-apifox-orders:
- id
- name
title: 产品所属分类
tags:
type: array
items:
type: object
properties:
id:
type: string
title: 标签id
name:
type: string
title: 标签名
bg_color:
type: string
title: 标签背景色
text_color:
type: string
title: 标签文字色
required:
- id
- name
- bg_color
- text_color
x-apifox-orders:
- id
- name
- bg_color
- text_color
title: 产品标签
required:
- id
- product_name
- recommend
- form_sn
- created_time
- categories
- tags
x-apifox-orders:
- id
- product_name
- recommend
- form_sn
- created_time
- categories
- tags
title: 产品列表
total:
type: integer
title: 产品总数
required:
- categories
- list
- total
x-apifox-orders:
- categories
- list
- total
required:
- code
- msg
- data
x-apifox-orders:
- code
- msg
- data
example:
code: 1
msg: 0
data:
categories:
- id: 2769851
name: '11'
list:
- id: 2769856
product_name: '22'
recommend: hot
form_sn: customize_jsj_pnzuky
created_time: '2026-02-03 10:36:29'
categories:
- id: '2769851'
name: '11'
tags:
- id: '2769847'
name: '111'
bg_color: '#3e5160'
text_color: '#ffffff'
- id: 2769848
product_name: '1111'
recommend: normal
form_sn: customize_jsj_ivleuz
created_time: '2026-02-02 16:33:01'
categories:
- id: '2769851'
name: '11'
tags:
- id: '2769846'
name: 测试1
bg_color: '#1e9fff'
text_color: '#ffffff'
- id: '2769847'
name: '111'
bg_color: '#3e5160'
text_color: '#ffffff'
- id: 2769845
product_name: '1'
recommend: normal
form_sn: customize_jsj_pnzuky
created_time: '2026-02-02 16:22:26'
categories: []
tags: []
total: 3
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 文档
x-apifox-status: developing
x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414702244-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
......@@ -29,8 +29,6 @@
"postinstall": "weapp-tw patch",
"lint": "eslint --ext .js,.vue src",
"api:generate": "node scripts/generateApiFromOpenAPI.js",
"api:sync": "node scripts/apifox-to-openapi.js",
"api:test": "node scripts/test-apifox-connection.js",
"changelog:check": "bash scripts/check-changelog.sh 7",
"changelog:check:30": "bash scripts/check-changelog.sh 30",
"changelog:check:all": "bash scripts/check-changelog.sh 0"
......
# API 文档生成指南
本项目的 API 文档采用手动维护的方式。
## 📝 工作流程
### 1. 维护 OpenAPI 文档
`docs/api-specs/` 目录中维护 OpenAPI 文档。
#### 目录结构
```
docs/api-specs/
├── user/ # 用户模块
│ ├── login.md
│ ├── login_status.md
│ └── ...
├── favorite/ # 收藏模块
│ ├── add.md
│ ├── del.md
│ └── list.md
└── ...
```
#### 文档格式
每个 `.md` 文件包含:
- 接口描述(Markdown 格式)
- OpenAPI 3.0.1 规范(YAML 格式)
**示例**`docs/api-specs/user/login.md`):
\`\`\`markdown
# 登录并绑定 OpenID
## 接口信息
- **方法**: POST
- **路径**: /srv/?a=user&t=login
- **标签**: user
## OpenAPI 规范
\`\`\`yaml
openapi: 3.0.0
info:
title: 登录并绑定 OpenID
description: 使用手机号和验证码登录,并绑定到 OpenID
version: 1.0.0
paths:
/srv/?:
post:
summary: 登录并绑定 OpenID
description: 使用手机号和验证码登录,并绑定到 OpenID
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- f
- a
- t
properties:
f:
type: string
description: 业务模块标识
example: manulife
a:
type: string
description: 模块名(user)
example: user
t:
type: string
description: 接口类型(login)
example: login
phone:
type: string
description: 手机号
example: '13800138000'
code:
type: string
description: 验证码
example: '123456'
openid:
type: string
description: 微信 OpenID
example: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
code:
type: number
description: 状态码(0=失败,1=成功)
msg:
type: string
description: 消息
data:
type: object
description: 用户信息
properties:
id:
type: number
description: 用户 ID
avatar:
type: string
description: 头像 URL
name:
type: string
description: 姓名
\`\`\`
\`\`\`
### 2. 生成 API 代码
运行生成脚本:
```bash
node scripts/generateApiFromOpenAPI.js
```
#### 生成内容
脚本会:
1. 扫描 `docs/api-specs/` 目录
2. 解析每个 `.md` 文件中的 OpenAPI 规范
3. 生成对应的 JavaScript API 文件到 `src/api/` 目录
#### 输出示例
\`\`\`
=== OpenAPI 转 API 文档生成器 ===
输入目录: /Users/huyirui/program/itomix/git/manulife-weapp/docs/api-specs
输出目录: /Users/huyirui/program/itomix/git/manulife-weapp/src/api
💾 备份当前 OpenAPI 文档...
找到 9 个模块: event, favorite, feedback, file, get_file_list, get_product, news, user, wechat
处理模块: user
找到 5 个 API 文档
✓ get_profile: 获取个人信息
✓ login: 登录并绑定openid
✓ login_status: 查询登录状态
✓ logout: 退出登录并解绑openid
✓ update_profile: 更新个人资料
📝 生成文件: /Users/huyirui/program/itomix/git/manulife-weapp/src/api/user.js
✅ API 文档生成完成!
\`\`\`
### 3. 使用生成的 API
在组件中导入并使用:
\`\`\`javascript
import { loginAPI, getUserProfileAPI } from '@/api/user';
// 登录
const result = await loginAPI({
phone: '13800138000',
code: '123456',
openid: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
});
if (result.code === 1) {
console.log('登录成功', result.data);
}
\`\`\`
## 🔧 高级功能
### API 变更检测
脚本会自动检测 API 变更:
-**新增接口** - 检测到新的 API 文档
- ⚠️ **修改接口** - 检测到 API 规范变更
-**删除接口** - 检测到删除的 API 文档
#### 变更报告示例
\`\`\`
🔍 开始检测 API 变更...
📦 新增模块: user
包含 2 个新增接口:
• POST /srv/?a=user&t=login - 登录并绑定 OpenID
• GET /srv/?a=user&t=get_profile - 获取个人信息
📦 对比范围: 9 个旧接口 → 11 个新接口
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 新增接口 (2):
+ login - 登录并绑定 OpenID
+ get_profile - 获取个人信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总计: 2 新增, 0 修改, 0 删除
✅ 未检测到破坏性变更
\`\`\`
## 📚 最佳实践
### 1. 文档命名
- 使用语义化文件名(如 `login.md`, `get_list.md`
- 避免使用通用名称(如 `api.md`, `endpoint.md`
### 2. 参数定义
- **必填参数**:在 `required` 数组中列出
- **可选参数**:不在 `required`
- **描述**:为每个参数添加清晰的 `description`
- **示例**:为每个参数添加 `example`
### 3. 响应结构
- 统一使用 `{ code, msg, data }` 格式
- `code`: 状态码(0=失败,1=成功)
- `msg`: 消息说明
- `data`: 数据内容
### 4. 文档分组
- 按业务模块分组(user, favorite, product 等)
- 每个模块一个目录
- 相关接口放在同一目录
## 🛠️ 故障排除
### 问题:YAML 解析失败
**错误信息**
\`\`\`
✗ login.md: 解析失败 - YAML 代码块格式错误
\`\`\`
**解决方案**
- 检查 YAML 代码块是否正确包裹在 `\`\`\`yaml``\`\`\`` 之间
- 检查 YAML 缩进是否正确(使用空格,不要使用 Tab)
- 使用在线 YAML 验证器验证格式
### 问题:未找到 YAML 代码块
**错误信息**
\`\`\`
⚠️ login.md: 未找到 YAML 代码块
\`\`\`
**解决方案**
- 确保文档中包含 `\`\`\`yaml` 代码块
- 检查代码块格式是否正确
### 问题:生成的 API 代码为空
**可能原因**
1. OpenAPI 文档格式不正确
2. `paths``requestBody` 定义缺失
**解决方案**
- 检查 OpenAPI 文档结构是否完整
- 参考本文档中的示例格式
## 📖 参考资料
- [OpenAPI 3.0 规范](https://swagger.io/specification/)
- [YAML 语法指南](https://yaml.org/spec/1.2/spec.html)
- [项目 API 文档目录](../docs/api-specs/)
#!/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) => {
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8').trim();
if (!raw) {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
return;
}
reject(new Error(`HTTP ${res.statusCode}: Empty response`));
return;
}
try {
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
}
} 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 = Array.isArray(response.data) ? 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();
This diff is collapsed. Click to expand it.
#!/usr/bin/env node
/**
* Apifox API 响应调试工具
*
* 功能:输出完整的 API 响应,帮助诊断问题
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// 加载配置
function loadConfig() {
const envPath = path.join(__dirname, '../.env.apifox');
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();
}
});
return config;
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
console.log(`📡 请求 URL: https://${options.hostname}${options.path}`);
console.log(`📋 请求头:`, JSON.stringify(options.headers, null, 2));
const req = https.request(options, (res) => {
console.log(`\n📊 响应状态码: ${res.statusCode}`);
console.log(`📋 响应头:`, JSON.stringify(res.headers, null, 2));
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8').trim();
if (!raw) {
console.log('⚠️ 响应体为空');
resolve({ raw: null, statusCode: res.statusCode });
return;
}
console.log(`\n📦 响应体长度: ${raw.length} 字符`);
console.log(`📄 响应体内容:`);
console.log('---开始---');
console.log(raw);
console.log('---结束---\n');
try {
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
resolve(json);
} catch (err) {
console.error(`❌ JSON 解析失败: ${err.message}`);
resolve({ raw, parseError: err.message });
}
});
});
req.on('error', (err) => {
console.error(`❌ 请求失败: ${err.message}`);
reject(err);
});
req.end();
});
}
// 主函数
async function main() {
console.log('🔍 Apifox API 响应调试工具\n');
const config = loadConfig();
// 测试获取接口列表
console.log('========================================');
console.log('测试: 获取接口列表');
console.log('========================================\n');
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);
console.log('========================================');
console.log('解析结果');
console.log('========================================\n');
if (response.parseError) {
console.log(`❌ 无法解析 JSON`);
console.log(`原始响应:`, response.raw);
} else {
console.log(`✅ JSON 解析成功`);
console.log(`响应类型: ${typeof response}`);
console.log(`响应键:`, Object.keys(response));
console.log(`\ndata 类型: ${typeof response.data}`);
console.log(`data 是数组: ${Array.isArray(response.data)}`);
console.log(`data 长度: ${response.data?.length || 0}`);
console.log(`total: ${response.total}`);
if (response.data && response.data.length > 0) {
console.log(`\n第一个接口示例:`);
console.log(JSON.stringify(response.data[0], null, 2));
}
}
} catch (err) {
console.error(`\n❌ 请求失败: ${err.message}`);
}
}
main();
......@@ -709,9 +709,11 @@ function compareAPIChanges(openAPIDir) {
try {
const newDocs = parseOpenAPIPath(moduleDir);
if (newDocs && newDocs.length > 0) {
console.log(` 包含 ${newDocs.length} 个新增接口:`);
newDocs.forEach(doc => {
console.log(` • ${doc.method} ${doc.path} - ${doc.summary}`);
// 使用 extractAPIInfo 提取 API 信息
const apiInfos = newDocs.map(doc => extractAPIInfo(doc));
console.log(` 包含 ${apiInfos.length} 个新增接口:`);
apiInfos.forEach(api => {
console.log(` • ${api.method} ${api.path} - ${api.summary || api.name}`);
});
}
} catch (error) {
......
#!/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) {
// Apifox Token 格式:APS-xxxxxxxx (大写)
if (!token.toUpperCase().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) => {
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8').trim();
if (!raw) {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
return;
}
reject(new Error(`HTTP ${res.statusCode}: Empty response`));
return;
}
try {
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
}
} 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);
if (!response || !response.data) {
throw new Error('响应为空或缺少 data 字段');
}
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 = Array.isArray(response.data) ? 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');
}
} else if (response.__empty) {
log('⚠️ 接口列表响应为空,已按空列表处理', '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);
});
#!/usr/bin/env node
/**
* 测试不同的 Apifox API 端点
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// 加载配置
function loadConfig() {
const envPath = path.join(__dirname, '../.env.apifox');
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();
}
});
return config;
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8').trim();
if (!raw) {
resolve({ statusCode: res.statusCode, data: null, headers: res.headers });
return;
}
try {
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
resolve({ statusCode: res.statusCode, data: json, headers: res.headers });
} catch (err) {
resolve({ statusCode: res.statusCode, raw, parseError: err.message, headers: res.headers });
}
});
});
req.on('error', reject);
req.end();
});
}
// 测试端点
async function testEndpoint(config, endpointPath, description) {
console.log(`\n测试: ${description}`);
console.log(`端点: ${endpointPath}`);
const options = {
hostname: 'api.apifox.com',
path: endpointPath,
method: 'GET',
headers: {
'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
'Content-Type': 'application/json'
}
};
try {
const response = await httpsRequest(options);
console.log(`状态码: ${response.statusCode}`);
console.log(`Content-Type: ${response.headers['content-type']}`);
console.log(`Content-Length: ${response.headers['content-length']}`);
if (response.parseError) {
console.log(`❌ JSON 解析失败: ${response.parseError}`);
console.log(`原始响应 (前 200 字符): ${response.raw?.substring(0, 200)}`);
} else if (response.data) {
console.log(` 成功`);
// 显示响应结构
if (Array.isArray(response.data)) {
console.log(` - data 是数组,长度: ${response.data.length}`);
} else if (typeof response.data === 'object') {
console.log(` - data 是对象,键: ${Object.keys(response.data).join(', ')}`);
}
// 如果有接口数据,显示第一个
if (Array.isArray(response.data) && response.data.length > 0) {
console.log(` - 第一个接口:`, JSON.stringify(response.data[0]).substring(0, 150));
}
} else {
console.log(`⚠️ 响应为空`);
}
return response.statusCode === 200 && response.data && (Array.isArray(response.data) ? response.data.length > 0 : true);
} catch (err) {
console.log(`❌ 请求失败: ${err.message}`);
return false;
}
}
// 主函数
async function main() {
const config = loadConfig();
const projectId = config.VITE_APIFOX_PROJECT_ID;
console.log('='.repeat(60));
console.log('测试不同的 Apifox API 端点');
console.log('='.repeat(60));
// 尝试不同的端点
const endpoints = [
{ path: `/api/v1/projects/${projectId}/apis?pageSize=10`, desc: '当前使用的端点 (/apis)' },
{ path: `/api/v1/projects/${projectId}/interfaces?pageSize=10`, desc: '尝试 /interfaces' },
{ path: `/v1/projects/${projectId}/apis?pageSize=10`, desc: '不带 /api 前缀' },
{ path: `/api/v1/projects/${projectId}/api-lists?pageSize=10`, desc: '尝试 /api-lists' },
{ path: `/api/v1/projects/${projectId}/endpoints?pageSize=10`, desc: '尝试 /endpoints' },
];
let successCount = 0;
for (const endpoint of endpoints) {
const success = await testEndpoint(config, endpoint.path, endpoint.desc);
if (success) successCount++;
}
console.log('\n' + '='.repeat(60));
console.log(`测试完成!成功: ${successCount}/${endpoints.length}`);
console.log('='.repeat(60));
}
main();
#!/usr/bin/env node
/**
* 测试 Apifox 导出 API
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// 加载配置
function loadConfig() {
const envPath = path.join(__dirname, '../.env.apifox');
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();
}
});
return config;
}
// 发送 HTTPS 请求
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8').trim();
if (!raw) {
resolve({ statusCode: res.statusCode, data: null, headers: res.headers });
return;
}
try {
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
resolve({ statusCode: res.statusCode, data: json, headers: res.headers });
} catch (err) {
resolve({ statusCode: res.statusCode, raw, headers: res.headers });
}
});
});
req.on('error', reject);
req.end();
});
}
// 主函数
async function main() {
const config = loadConfig();
const projectId = config.VITE_APIFOX_PROJECT_ID;
console.log('='.repeat(60));
console.log('测试 Apifox 导出 API');
console.log('='.repeat(60));
// 尝试导出 OpenAPI 格式
const exportEndpoints = [
{ path: `/api/v1/projects/${projectId}/export-openapi`, desc: '导出 OpenAPI' },
{ path: `/api/v1/projects/${projectId}/export-openapi/3.0.0`, desc: '导出 OpenAPI 3.0' },
{ path: `/api/v1/projects/${projectId}/export-openapi/3.0.0/json`, desc: '导出 OpenAPI 3.0 JSON' },
];
for (const endpoint of exportEndpoints) {
console.log(`\n测试: ${endpoint.desc}`);
console.log(`端点: ${endpoint.path}`);
const options = {
hostname: 'api.apifox.com',
path: endpoint.path,
method: 'GET',
headers: {
'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
'Content-Type': 'application/json',
'X-Apifox-Version': '2024-06-14' // 尝试添加版本头
}
};
try {
const response = await httpsRequest(options);
console.log(`状态码: ${response.statusCode}`);
console.log(`Content-Type: ${response.headers['content-type']}`);
console.log(`Content-Length: ${response.headers['content-length']}`);
if (response.statusCode === 200 && response.raw) {
console.log(`✅ 成功获取数据 (${response.raw.length} 字符)`);
// 尝试解析为 JSON
try {
const json = JSON.parse(response.raw);
console.log(` - JSON 解析成功`);
console.log(` - 顶层键: ${Object.keys(json).join(', ')}`);
// 检查是否有接口数据
if (json.paths) {
const pathCount = Object.keys(json.paths).length;
console.log(` - OpenAPI paths 数量: ${pathCount}`);
if (pathCount > 0) {
console.log(` ✓ 找到接口数据!`);
// 保存到文件
const outputPath = path.join(__dirname, `../test-openapi-${Date.now()}.json`);
fs.writeFileSync(outputPath, JSON.stringify(json, null, 2), 'utf-8');
console.log(` - 已保存到: ${outputPath}`);
return; // 找到数据就退出
}
}
} catch (err) {
console.log(` - JSON 解析失败,可能是其他格式`);
}
} else if (response.data) {
console.log(`✅ 响应包含 data 字段`);
console.log(` - data 键: ${Object.keys(response.data).join(', ')}`);
} else {
console.log(`⚠️ 响应为空`);
}
} catch (err) {
console.log(`❌ 请求失败: ${err.message}`);
}
}
console.log('\n' + '='.repeat(60));
}
main();
import { fn, fetch } from '@/api/fn';
const Api = {
WeekHot: '/srv/?a=file&t=week_hot',
}
/**
* @description 本周热门资料
* @remark
* @param {Object} params 请求参数
* @param {string} params.page 页码,从0开始
* @param {string} params.limit 每页数量
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
* list: Array<{
* meta_id: integer; // 文件ID
* name: string; // 文件名称
* src: string; // 文件URL
* size: string; // 文件大小
* read_people_count: integer; // 学习人数
* read_people_percent: number; // 学习人数比例
* }>;
* };
* }>}
*/
export const weekHotAPI = (params) => fn(fetch.get(Api.WeekHot, params));
import { fn, fetch } from '@/api/fn';
const Api = {
FileList: '/srv/?a=get_file_list&t=file_list',
}
/**
* @description 文档列表
* @remark
* @param {Object} params 请求参数
* @param {string} params.client_id (可选) 主体id
* @param {string} params.limit (可选)
* @param {string} params.page (可选)
* @param {string} params.cid (可选) 分类id
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
* categories: Array<{
* id: integer; // 分类id
* name: string; // 分类名
* }>;
* list: Array<{
* id: integer; // 产品id
* product_name: string; // 产品名
* recommend: string; // 推荐位: normal-普通, hot-热卖
* form_sn: string; //
* created_time: string; // 创建时间
* categories: array; // 产品所属分类
* tags: array; // 产品标签
* }>;
* total: integer; // 产品总数
* };
* }>}
*/
export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));