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 @@ ...@@ -5,6 +5,73 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-04] - 清理 Apifox 依赖并优化工具链
9 +
10 +### 移除
11 +- Apifox API 相关脚本(不再工作)
12 + - 删除 `scripts/apifox-to-openapi.js` - 无法从 API 获取数据
13 + - 删除 `scripts/apifox-sync.js` - API 同步脚本
14 + - 删除 `scripts/debug-apifox-response.js` - 调试脚本
15 + - 删除 `scripts/test-apifox-connection.js` - 测试脚本
16 + - 删除 `scripts/test-apifox-endpoints.js` - 测试脚本
17 + - 删除 `scripts/test-apifox-export.js` - 测试脚本
18 + - 删除 `.env.apifox` 配置文件
19 +- package.json 命令清理
20 + - 移除 `api:sync` 命令(依赖 Apifox API)
21 + - 移除 `api:test` 命令(依赖已删除的脚本)
22 + - 保留 `api:generate` 命令(从 OpenAPI 文档生成代码)
23 +
24 +### 新增
25 +- API 文档维护指南(`scripts/API_GUIDE.md`
26 + - 详细说明手动维护 OpenAPI 文档的工作流程
27 + - 提供完整的文档格式示例和最佳实践
28 + - 包含故障排除指南
29 +- 新增文档模块接口(初稿)
30 + - `weekHotAPI` - 本周热门资料
31 + - `fileListAPI` - 文档列表
32 + - 标记为初稿状态,后端字段未最终确定
33 +
34 +### 优化
35 +- API 生成脚本(`scripts/generateApiFromOpenAPI.js`
36 + - 修复新增模块接口信息显示为 undefined 的问题
37 + - 使用 `extractAPIInfo` 提取完整的接口信息(方法、路径、描述)
38 + - 改进接口信息显示逻辑
39 +
40 +### 文档
41 +- 更新 API 接口联调日志(`docs/api-integration-log.md`
42 + - 新增文档模块:2个初稿接口(字段未确定)
43 + - 更新总体进度:26个接口(12已完成,9待联调,2初稿,3已废弃)
44 + - 文档版本升级到 v2.3
45 + - 添加移除 Apifox 依赖的变更记录
46 + - 更新快速索引,添加文档模块链接
47 +
48 +---
49 +
50 +**详细信息**
51 +- **影响文件**:
52 + - `scripts/apifox-to-openapi.js` (已删除)
53 + - `scripts/apifox-sync.js` (已删除)
54 + - `scripts/debug-apifox-response.js` (已删除)
55 + - `scripts/test-apifox-connection.js` (已删除)
56 + - `scripts/test-apifox-endpoints.js` (已删除)
57 + - `scripts/test-apifox-export.js` (已删除)
58 + - `.env.apifox` (已删除)
59 + - `scripts/API_GUIDE.md` (新增)
60 + - `scripts/generateApiFromOpenAPI.js` (修复)
61 + - `package.json` (命令清理)
62 + - `docs/api-integration-log.md` (更新)
63 + - `docs/api-specs/file/week_hot.md` (新增)
64 + - `docs/api-specs/get_file_list/file_list.md` (新增)
65 +- **技术栈**: Node.js, fs 模块, js-yaml
66 +- **测试状态**: ✅ 已测试通过
67 +- **备注**:
68 + - 项目现在完全采用手动维护 OpenAPI 文档的方式
69 + - 工作流:从 Apifox 导出 → 放入 `docs/api-specs/` → 运行 `pnpm api:generate`
70 + - 不再依赖 Apifox API,更加稳定可控
71 + - 文档模块接口标记为初稿,待后端确认字段后更新
72 +
73 +---
74 +
8 ## [2026-02-04] - 优化 OpenAPI 文档生成工具 75 ## [2026-02-04] - 优化 OpenAPI 文档生成工具
9 76
10 ### 优化 77 ### 优化
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
4 4
5 ## 📊 总体进度 5 ## 📊 总体进度
6 6
7 -- **总接口数**: 24 7 +- **总接口数**: 26
8 -- **已完成**: 12 (50.0%) 8 +- **已完成**: 12 (46.2%)
9 - **联调中**: 0 (0%) 9 - **联调中**: 0 (0%)
10 -- **已废弃**: 3 (12.5%) 10 +- **已废弃**: 3 (11.5%)
11 -- **待联调**: 9 (37.5%) 11 +- **待联调**: 9 (34.6%)
12 +- **初稿(字段未确定)**: 2 (7.7%)
12 - **有阻塞**: 0 13 - **有阻塞**: 0
13 14
14 --- 15 ---
...@@ -18,6 +19,15 @@ ...@@ -18,6 +19,15 @@
18 - 添加 CLAUDE.md 文件过滤,避免将文档文件当作 API 文档处理 19 - 添加 CLAUDE.md 文件过滤,避免将文档文件当作 API 文档处理
19 - 改进新增模块检测逻辑,显示新增模块包含的所有接口 20 - 改进新增模块检测逻辑,显示新增模块包含的所有接口
20 - 优化变更检测报告,提升可读性 21 - 优化变更检测报告,提升可读性
22 + - 修复接口信息显示为 undefined 的问题
23 +- 🗑️ **清理 Apifox API 依赖**
24 + - 删除不再工作的 Apifox API 相关脚本(apifox-to-openapi.js、apifox-sync.js 等)
25 + - 删除 .env.apifox 配置文件
26 + - 更新 package.json,移除 api:sync 和 api:test 命令
27 + - 创建 API_GUIDE.md 使用指南,明确手动维护 OpenAPI 文档的工作流
28 +- 📝 **新增文档模块接口(初稿)**
29 + - weekHotAPI(本周热门资料):字段未最终确定,待联调确认
30 + - fileListAPI(文档列表):字段未最终确定,待联调确认
21 -**收藏模块后端完成**:3 个收藏接口(addAPI、delAPI、listAPI)后端已开发完成,前端待联调 31 -**收藏模块后端完成**:3 个收藏接口(addAPI、delAPI、listAPI)后端已开发完成,前端待联调
22 -**埋点接口后端完成**:埋点接口(addAPI)后端已开发完成,前端待联调 32 -**埋点接口后端完成**:埋点接口(addAPI)后端已开发完成,前端待联调
23 -**产品模块联调完成**:产品列表接口(listAPI)联调成功 33 -**产品模块联调完成**:产品列表接口(listAPI)联调成功
...@@ -818,6 +828,121 @@ ...@@ -818,6 +828,121 @@
818 828
819 --- 829 ---
820 830
831 +### 文档模块
832 +
833 +#### 接口 1: 本周热门资料(初稿)
834 +
835 +**接口信息**
836 +- **接口名称**: `weekHotAPI`
837 +- **接口路径**: `/srv/?a=file&t=week_hot`
838 +- **请求方法**: GET
839 +- **负责页面**: 待确认
840 +- **负责人**: 后端团队
841 +
842 +**接口文档更新记录**
843 +
844 +| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
845 +|------|------|---------|---------|---------|
846 +| 2026-02-04 | v0.1 | 初稿版本 | 后端字段未确定,待联调确认 | [查看](docs/api-specs/file/week_hot.md) |
847 +
848 +**页面调试情况**
849 +
850 +| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
851 +|------|---------|---------|---------|------|
852 +| 2026-02-04 | - | 后端字段未确定 | 待后端确认后更新 | ⏳ 待联调 |
853 +
854 +**接口状态**: ⏳ 初稿 - 后端字段未确定
855 +
856 +**备注**:
857 +- ⚠️ **初稿状态**:当前接口文档为初稿,后端字段可能还有调整
858 +- **参数**:
859 + - `page`: 页码,从 0 开始
860 + - `limit`: 每页数量
861 +- **预期返回数据结构**:
862 + ```javascript
863 + {
864 + code: 1,
865 + data: {
866 + list: [
867 + {
868 + meta_id: 123, // 文件ID
869 + name: "文件名称", // 文件名称
870 + src: "https://...", // 文件URL
871 + size: "1.2MB", // 文件大小
872 + read_people_count: 100, // 学习人数
873 + read_people_percent: 85.5 // 学习人数比例
874 + }
875 + ]
876 + }
877 + }
878 + ```
879 +- **字段确认状态**:待后端确认最终字段结构
880 +- 实现位置:`src/api/file.js:weekHotAPI`
881 +
882 +---
883 +
884 +#### 接口 2: 文档列表(初稿)
885 +
886 +**接口信息**
887 +- **接口名称**: `fileListAPI`
888 +- **接口路径**: `/srv/?a=get_file_list&t=file_list`
889 +- **请求方法**: GET
890 +- **负责页面**: 待确认
891 +- **负责人**: 后端团队
892 +
893 +**接口文档更新记录**
894 +
895 +| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
896 +|------|------|---------|---------|---------|
897 +| 2026-02-04 | v0.1 | 初稿版本 | 后端字段未确定,待联调确认 | [查看](docs/api-specs/get_file_list/file_list.md) |
898 +
899 +**页面调试情况**
900 +
901 +| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
902 +|------|---------|---------|---------|------|
903 +| 2026-02-04 | - | 后端字段未确定 | 待后端确认后更新 | ⏳ 待联调 |
904 +
905 +**接口状态**: ⏳ 初稿 - 后端字段未确定
906 +
907 +**备注**:
908 +- ⚠️ **初稿状态**:当前接口文档为初稿,后端字段可能还有调整
909 +- **参数**:
910 + - `client_id`: 主体 ID(可选)
911 + - `cid`: 分类 ID(可选)
912 + - `page`: 页码(从 0 开始)
913 + - `limit`: 每页数量
914 +- **预期返回数据结构**:
915 + ```javascript
916 + {
917 + code: 1,
918 + data: {
919 + categories: [
920 + { id: 2769851, name: "分类名称" }
921 + ],
922 + list: [
923 + {
924 + id: 2769856, // 产品id
925 + product_name: "产品名称", // 产品名
926 + recommend: "hot", // 推荐位: normal-普通, hot-热卖
927 + form_sn: "customize_jsj_pnzuky", // 产品编号
928 + created_time: "2026-02-03 10:36:29",
929 + categories: [ // 产品所属分类
930 + { id: "2769851", name: "分类名称" }
931 + ],
932 + tags: [ // 产品标签
933 + { id: "2769847", name: "标签名", bg_color: "#3e5160", text_color: "#ffffff" }
934 + ]
935 + }
936 + ],
937 + total: 3 // 产品总数
938 + }
939 + }
940 + ```
941 +- **字段确认状态**:待后端确认最终字段结构
942 +- 实现位置:`src/api/get_file_list.js:fileListAPI`
943 +
944 +---
945 +
821 ### 埋点模块 946 ### 埋点模块
822 947
823 #### 接口 1: 添加埋点 948 #### 接口 1: 添加埋点
...@@ -938,6 +1063,7 @@ ...@@ -938,6 +1063,7 @@
938 - [❌ 已废弃](#通用模块) - 3个接口 1063 - [❌ 已废弃](#通用模块) - 3个接口
939 - [⏳ 待联调](#收藏模块) - 3个接口 1064 - [⏳ 待联调](#收藏模块) - 3个接口
940 - [⏳ 待联调](#埋点模块) - 1个接口 1065 - [⏳ 待联调](#埋点模块) - 1个接口
1066 +- [⏳ 初稿 - 字段未确定](#文档模块) - 2个接口
941 - [⏳ 后端开发中](#消息模块) - 2个接口 1067 - [⏳ 后端开发中](#消息模块) - 2个接口
942 1068
943 ### 按模块查看 1069 ### 按模块查看
...@@ -947,6 +1073,7 @@ ...@@ -947,6 +1073,7 @@
947 - [产品](#产品模块) - ✅ 1个已完成 1073 - [产品](#产品模块) - ✅ 1个已完成
948 - [收藏](#收藏模块) - ⏳ 3个待联调 1074 - [收藏](#收藏模块) - ⏳ 3个待联调
949 - [埋点](#埋点模块) - ⏳ 1个待联调 1075 - [埋点](#埋点模块) - ⏳ 1个待联调
1076 +- [文档](#文档模块) - ⏳ 2个初稿
950 - [消息](#消息模块) - ⏳ 2个后端开发中 1077 - [消息](#消息模块) - ⏳ 2个后端开发中
951 - [知识库](#知识库模块) - ⏳ 未开始 1078 - [知识库](#知识库模块) - ⏳ 未开始
952 - [家办](#家办模块) - ⏳ 未开始 1079 - [家办](#家办模块) - ⏳ 未开始
...@@ -984,20 +1111,20 @@ ...@@ -984,20 +1111,20 @@
984 1111
985 --- 1112 ---
986 1113
987 -**最后更新时间**: 2026-02-04 13:00 1114 +**最后更新时间**: 2026-02-04 15:30
988 -**文档版本**: v2.2 1115 +**文档版本**: v2.3
989 **更新内容**: 1116 **更新内容**:
990 -- 收藏模块:3个接口后端已完成,前端待联调 1117 +- 📝 **新增文档模块接口(初稿)**
991 - - addAPI(添加收藏) 1118 + - weekHotAPI(本周热门资料):字段未最终确定
992 - - delAPI(取消收藏) 1119 + - fileListAPI(文档列表):字段未最终确定
993 - - listAPI(收藏列表) 1120 +- 🗑️ **移除 Apifox API 依赖**
994 -- 埋点模块:1个接口后端已完成,前端待联调 1121 + - 删除 apifox-to-openapi.js 等不再工作的脚本
995 - - addAPI(添加埋点) 1122 + - 更新 package.json,简化为 api:generate 单一命令
996 -- OpenAPI 文档生成工具优化: 1123 + - 明确手动维护 OpenAPI 文档的工作流
997 - - 添加 CLAUDE.md 文件过滤 1124 +- 🔧 **API 生成工具优化**
998 - - 改进新增模块检测逻辑 1125 + - 修复新增模块接口信息显示为 undefined 的问题
999 - - 优化变更检测报告 1126 + - 改进接口信息提取逻辑
1000 -- 更新总体进度:24个接口(12个已完成,9个待联调,3个已废弃) 1127 +- 更新总体进度:26个接口(12个已完成,9个待联调,2个初稿,3个已废弃)
1001 1128
1002 **历史版本**: 1129 **历史版本**:
1003 - v2.1 (2026-02-03 21:00): 产品模块联调完成 1130 - v2.1 (2026-02-03 21:00): 产品模块联调完成
......
1 +# 本周热门资料
2 +
3 +## OpenAPI Specification
4 +
5 +```yaml
6 +openapi: 3.0.1
7 +info:
8 + title: ''
9 + version: 1.0.0
10 +paths:
11 + /srv/:
12 + get:
13 + summary: 本周热门资料
14 + deprecated: false
15 + description: ''
16 + tags:
17 + - 文档
18 + parameters:
19 + - name: f
20 + in: query
21 + description: ''
22 + required: true
23 + example: manulife
24 + schema:
25 + type: string
26 + - name: a
27 + in: query
28 + description: ''
29 + required: true
30 + example: file
31 + schema:
32 + type: string
33 + - name: t
34 + in: query
35 + description: ''
36 + required: true
37 + example: week_hot
38 + schema:
39 + type: string
40 + - name: page
41 + in: query
42 + description: 页码,从0开始
43 + required: true
44 + example: '0'
45 + schema:
46 + type: string
47 + - name: limit
48 + in: query
49 + description: 每页数量
50 + required: true
51 + example: '10'
52 + schema:
53 + type: string
54 + responses:
55 + '200':
56 + description: ''
57 + content:
58 + application/json:
59 + schema:
60 + type: object
61 + properties:
62 + code:
63 + type: integer
64 + msg:
65 + type: string
66 + data:
67 + type: object
68 + properties:
69 + list:
70 + type: array
71 + items:
72 + type: object
73 + properties:
74 + meta_id:
75 + type: integer
76 + title: 文件ID
77 + name:
78 + type: string
79 + title: 文件名称
80 + src:
81 + type: string
82 + title: 文件URL
83 + size:
84 + type: string
85 + title: 文件大小
86 + read_people_count:
87 + type: integer
88 + title: 学习人数
89 + read_people_percent:
90 + type: number
91 + title: 学习人数比例
92 + x-apifox-orders:
93 + - meta_id
94 + - name
95 + - src
96 + - size
97 + - read_people_count
98 + - read_people_percent
99 + required:
100 + - size
101 + - read_people_count
102 + - read_people_percent
103 + required:
104 + - list
105 + x-apifox-orders:
106 + - list
107 + required:
108 + - code
109 + - msg
110 + - data
111 + x-apifox-orders:
112 + - code
113 + - msg
114 + - data
115 + headers: {}
116 + x-apifox-name: 成功
117 + x-apifox-ordering: 0
118 + security: []
119 + x-apifox-folder: 文档
120 + x-apifox-status: developing
121 + x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-415055277-run
122 +components:
123 + schemas: {}
124 + responses: {}
125 + securitySchemes: {}
126 +servers: []
127 +security: []
128 +
129 +```
1 +# 文档列表
2 +
3 +## OpenAPI Specification
4 +
5 +```yaml
6 +openapi: 3.0.1
7 +info:
8 + title: ''
9 + version: 1.0.0
10 +paths:
11 + /srv/:
12 + get:
13 + summary: 文档列表
14 + deprecated: false
15 + description: ''
16 + tags:
17 + - 文档
18 + parameters:
19 + - name: f
20 + in: query
21 + description: ''
22 + required: true
23 + example: manulife
24 + schema:
25 + type: string
26 + - name: a
27 + in: query
28 + description: ''
29 + required: true
30 + example: get_file_list
31 + schema:
32 + type: string
33 + - name: t
34 + in: query
35 + description: ''
36 + required: false
37 + example: file_list
38 + schema:
39 + type: string
40 + - name: client_id
41 + in: query
42 + description: 主体id
43 + required: false
44 + example: '30901'
45 + schema:
46 + type: string
47 + - name: limit
48 + in: query
49 + description: ''
50 + required: false
51 + example: '10'
52 + schema:
53 + type: string
54 + - name: page
55 + in: query
56 + description: ''
57 + required: false
58 + example: '0'
59 + schema:
60 + type: string
61 + - name: cid
62 + in: query
63 + description: 分类id
64 + required: false
65 + schema:
66 + type: string
67 + responses:
68 + '200':
69 + description: ''
70 + content:
71 + application/json:
72 + schema:
73 + type: object
74 + properties:
75 + code:
76 + type: integer
77 + msg:
78 + type: integer
79 + data:
80 + type: object
81 + properties:
82 + categories:
83 + type: array
84 + items:
85 + type: object
86 + properties:
87 + id:
88 + type: integer
89 + title: 分类id
90 + name:
91 + type: string
92 + title: 分类名
93 + x-apifox-orders:
94 + - id
95 + - name
96 + title: 分类列表
97 + list:
98 + type: array
99 + items:
100 + type: object
101 + properties:
102 + id:
103 + type: integer
104 + title: 产品id
105 + product_name:
106 + type: string
107 + title: 产品名
108 + recommend:
109 + type: string
110 + title: '推荐位: normal-普通, hot-热卖'
111 + form_sn:
112 + type: string
113 + created_time:
114 + type: string
115 + title: 创建时间
116 + categories:
117 + type: array
118 + items:
119 + type: object
120 + properties:
121 + id:
122 + type: string
123 + title: 分类id
124 + name:
125 + type: string
126 + title: 分类名
127 + required:
128 + - id
129 + - name
130 + x-apifox-orders:
131 + - id
132 + - name
133 + title: 产品所属分类
134 + tags:
135 + type: array
136 + items:
137 + type: object
138 + properties:
139 + id:
140 + type: string
141 + title: 标签id
142 + name:
143 + type: string
144 + title: 标签名
145 + bg_color:
146 + type: string
147 + title: 标签背景色
148 + text_color:
149 + type: string
150 + title: 标签文字色
151 + required:
152 + - id
153 + - name
154 + - bg_color
155 + - text_color
156 + x-apifox-orders:
157 + - id
158 + - name
159 + - bg_color
160 + - text_color
161 + title: 产品标签
162 + required:
163 + - id
164 + - product_name
165 + - recommend
166 + - form_sn
167 + - created_time
168 + - categories
169 + - tags
170 + x-apifox-orders:
171 + - id
172 + - product_name
173 + - recommend
174 + - form_sn
175 + - created_time
176 + - categories
177 + - tags
178 + title: 产品列表
179 + total:
180 + type: integer
181 + title: 产品总数
182 + required:
183 + - categories
184 + - list
185 + - total
186 + x-apifox-orders:
187 + - categories
188 + - list
189 + - total
190 + required:
191 + - code
192 + - msg
193 + - data
194 + x-apifox-orders:
195 + - code
196 + - msg
197 + - data
198 + example:
199 + code: 1
200 + msg: 0
201 + data:
202 + categories:
203 + - id: 2769851
204 + name: '11'
205 + list:
206 + - id: 2769856
207 + product_name: '22'
208 + recommend: hot
209 + form_sn: customize_jsj_pnzuky
210 + created_time: '2026-02-03 10:36:29'
211 + categories:
212 + - id: '2769851'
213 + name: '11'
214 + tags:
215 + - id: '2769847'
216 + name: '111'
217 + bg_color: '#3e5160'
218 + text_color: '#ffffff'
219 + - id: 2769848
220 + product_name: '1111'
221 + recommend: normal
222 + form_sn: customize_jsj_ivleuz
223 + created_time: '2026-02-02 16:33:01'
224 + categories:
225 + - id: '2769851'
226 + name: '11'
227 + tags:
228 + - id: '2769846'
229 + name: 测试1
230 + bg_color: '#1e9fff'
231 + text_color: '#ffffff'
232 + - id: '2769847'
233 + name: '111'
234 + bg_color: '#3e5160'
235 + text_color: '#ffffff'
236 + - id: 2769845
237 + product_name: '1'
238 + recommend: normal
239 + form_sn: customize_jsj_pnzuky
240 + created_time: '2026-02-02 16:22:26'
241 + categories: []
242 + tags: []
243 + total: 3
244 + headers: {}
245 + x-apifox-name: 成功
246 + x-apifox-ordering: 0
247 + security: []
248 + x-apifox-folder: 文档
249 + x-apifox-status: developing
250 + x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414702244-run
251 +components:
252 + schemas: {}
253 + responses: {}
254 + securitySchemes: {}
255 +servers: []
256 +security: []
257 +
258 +```
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
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",
34 "changelog:check": "bash scripts/check-changelog.sh 7", 32 "changelog:check": "bash scripts/check-changelog.sh 7",
35 "changelog:check:30": "bash scripts/check-changelog.sh 30", 33 "changelog:check:30": "bash scripts/check-changelog.sh 30",
36 "changelog:check:all": "bash scripts/check-changelog.sh 0" 34 "changelog:check:all": "bash scripts/check-changelog.sh 0"
......
1 +# API 文档生成指南
2 +
3 +本项目的 API 文档采用手动维护的方式。
4 +
5 +## 📝 工作流程
6 +
7 +### 1. 维护 OpenAPI 文档
8 +
9 +`docs/api-specs/` 目录中维护 OpenAPI 文档。
10 +
11 +#### 目录结构
12 +
13 +```
14 +docs/api-specs/
15 +├── user/ # 用户模块
16 +│ ├── login.md
17 +│ ├── login_status.md
18 +│ └── ...
19 +├── favorite/ # 收藏模块
20 +│ ├── add.md
21 +│ ├── del.md
22 +│ └── list.md
23 +└── ...
24 +```
25 +
26 +#### 文档格式
27 +
28 +每个 `.md` 文件包含:
29 +- 接口描述(Markdown 格式)
30 +- OpenAPI 3.0.1 规范(YAML 格式)
31 +
32 +**示例**`docs/api-specs/user/login.md`):
33 +
34 +\`\`\`markdown
35 +# 登录并绑定 OpenID
36 +
37 +## 接口信息
38 +
39 +- **方法**: POST
40 +- **路径**: /srv/?a=user&t=login
41 +- **标签**: user
42 +
43 +## OpenAPI 规范
44 +
45 +\`\`\`yaml
46 +openapi: 3.0.0
47 +info:
48 + title: 登录并绑定 OpenID
49 + description: 使用手机号和验证码登录,并绑定到 OpenID
50 + version: 1.0.0
51 +paths:
52 + /srv/?:
53 + post:
54 + summary: 登录并绑定 OpenID
55 + description: 使用手机号和验证码登录,并绑定到 OpenID
56 + requestBody:
57 + content:
58 + application/x-www-form-urlencoded:
59 + schema:
60 + type: object
61 + required:
62 + - f
63 + - a
64 + - t
65 + properties:
66 + f:
67 + type: string
68 + description: 业务模块标识
69 + example: manulife
70 + a:
71 + type: string
72 + description: 模块名(user)
73 + example: user
74 + t:
75 + type: string
76 + description: 接口类型(login)
77 + example: login
78 + phone:
79 + type: string
80 + description: 手机号
81 + example: '13800138000'
82 + code:
83 + type: string
84 + description: 验证码
85 + example: '123456'
86 + openid:
87 + type: string
88 + description: 微信 OpenID
89 + example: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
90 + responses:
91 + '200':
92 + description: 成功
93 + content:
94 + application/json:
95 + schema:
96 + type: object
97 + properties:
98 + code:
99 + type: number
100 + description: 状态码(0=失败,1=成功)
101 + msg:
102 + type: string
103 + description: 消息
104 + data:
105 + type: object
106 + description: 用户信息
107 + properties:
108 + id:
109 + type: number
110 + description: 用户 ID
111 + avatar:
112 + type: string
113 + description: 头像 URL
114 + name:
115 + type: string
116 + description: 姓名
117 +\`\`\`
118 +\`\`\`
119 +
120 +### 2. 生成 API 代码
121 +
122 +运行生成脚本:
123 +
124 +```bash
125 +node scripts/generateApiFromOpenAPI.js
126 +```
127 +
128 +#### 生成内容
129 +
130 +脚本会:
131 +1. 扫描 `docs/api-specs/` 目录
132 +2. 解析每个 `.md` 文件中的 OpenAPI 规范
133 +3. 生成对应的 JavaScript API 文件到 `src/api/` 目录
134 +
135 +#### 输出示例
136 +
137 +\`\`\`
138 +=== OpenAPI 转 API 文档生成器 ===
139 +
140 +输入目录: /Users/huyirui/program/itomix/git/manulife-weapp/docs/api-specs
141 +输出目录: /Users/huyirui/program/itomix/git/manulife-weapp/src/api
142 +
143 +💾 备份当前 OpenAPI 文档...
144 +
145 +找到 9 个模块: event, favorite, feedback, file, get_file_list, get_product, news, user, wechat
146 +
147 +处理模块: user
148 +找到 5 个 API 文档
149 + ✓ get_profile: 获取个人信息
150 + ✓ login: 登录并绑定openid
151 + ✓ login_status: 查询登录状态
152 + ✓ logout: 退出登录并解绑openid
153 + ✓ update_profile: 更新个人资料
154 + 📝 生成文件: /Users/huyirui/program/itomix/git/manulife-weapp/src/api/user.js
155 +
156 +✅ API 文档生成完成!
157 +\`\`\`
158 +
159 +### 3. 使用生成的 API
160 +
161 +在组件中导入并使用:
162 +
163 +\`\`\`javascript
164 +import { loginAPI, getUserProfileAPI } from '@/api/user';
165 +
166 +// 登录
167 +const result = await loginAPI({
168 + phone: '13800138000',
169 + code: '123456',
170 + openid: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
171 +});
172 +
173 +if (result.code === 1) {
174 + console.log('登录成功', result.data);
175 +}
176 +\`\`\`
177 +
178 +## 🔧 高级功能
179 +
180 +### API 变更检测
181 +
182 +脚本会自动检测 API 变更:
183 +
184 +-**新增接口** - 检测到新的 API 文档
185 +- ⚠️ **修改接口** - 检测到 API 规范变更
186 +-**删除接口** - 检测到删除的 API 文档
187 +
188 +#### 变更报告示例
189 +
190 +\`\`\`
191 +🔍 开始检测 API 变更...
192 +
193 +📦 新增模块: user
194 + 包含 2 个新增接口:
195 + • POST /srv/?a=user&t=login - 登录并绑定 OpenID
196 + • GET /srv/?a=user&t=get_profile - 获取个人信息
197 +
198 +📦 对比范围: 9 个旧接口 → 11 个新接口
199 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
200 +
201 +✅ 新增接口 (2):
202 + + login - 登录并绑定 OpenID
203 + + get_profile - 获取个人信息
204 +
205 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
206 +总计: 2 新增, 0 修改, 0 删除
207 +✅ 未检测到破坏性变更
208 +\`\`\`
209 +
210 +## 📚 最佳实践
211 +
212 +### 1. 文档命名
213 +
214 +- 使用语义化文件名(如 `login.md`, `get_list.md`
215 +- 避免使用通用名称(如 `api.md`, `endpoint.md`
216 +
217 +### 2. 参数定义
218 +
219 +- **必填参数**:在 `required` 数组中列出
220 +- **可选参数**:不在 `required`
221 +- **描述**:为每个参数添加清晰的 `description`
222 +- **示例**:为每个参数添加 `example`
223 +
224 +### 3. 响应结构
225 +
226 +- 统一使用 `{ code, msg, data }` 格式
227 +- `code`: 状态码(0=失败,1=成功)
228 +- `msg`: 消息说明
229 +- `data`: 数据内容
230 +
231 +### 4. 文档分组
232 +
233 +- 按业务模块分组(user, favorite, product 等)
234 +- 每个模块一个目录
235 +- 相关接口放在同一目录
236 +
237 +## 🛠️ 故障排除
238 +
239 +### 问题:YAML 解析失败
240 +
241 +**错误信息**
242 +\`\`\`
243 +✗ login.md: 解析失败 - YAML 代码块格式错误
244 +\`\`\`
245 +
246 +**解决方案**
247 +- 检查 YAML 代码块是否正确包裹在 `\`\`\`yaml``\`\`\`` 之间
248 +- 检查 YAML 缩进是否正确(使用空格,不要使用 Tab)
249 +- 使用在线 YAML 验证器验证格式
250 +
251 +### 问题:未找到 YAML 代码块
252 +
253 +**错误信息**
254 +\`\`\`
255 +⚠️ login.md: 未找到 YAML 代码块
256 +\`\`\`
257 +
258 +**解决方案**
259 +- 确保文档中包含 `\`\`\`yaml` 代码块
260 +- 检查代码块格式是否正确
261 +
262 +### 问题:生成的 API 代码为空
263 +
264 +**可能原因**
265 +1. OpenAPI 文档格式不正确
266 +2. `paths``requestBody` 定义缺失
267 +
268 +**解决方案**
269 +- 检查 OpenAPI 文档结构是否完整
270 +- 参考本文档中的示例格式
271 +
272 +## 📖 参考资料
273 +
274 +- [OpenAPI 3.0 规范](https://swagger.io/specification/)
275 +- [YAML 语法指南](https://yaml.org/spec/1.2/spec.html)
276 +- [项目 API 文档目录](../docs/api-specs/)
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 - const chunks = [];
82 -
83 - res.on('data', chunk => {
84 - chunks.push(chunk);
85 - });
86 -
87 - res.on('end', () => {
88 - const raw = Buffer.concat(chunks).toString('utf8').trim();
89 -
90 - if (!raw) {
91 - if (res.statusCode >= 200 && res.statusCode < 300) {
92 - resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
93 - return;
94 - }
95 -
96 - reject(new Error(`HTTP ${res.statusCode}: Empty response`));
97 - return;
98 - }
99 -
100 - try {
101 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
102 - if (res.statusCode === 200) {
103 - resolve(json);
104 - } else {
105 - reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
106 - }
107 - } catch (err) {
108 - reject(new Error(`解析响应失败: ${err.message}`));
109 - }
110 - });
111 - });
112 -
113 - req.on('error', reject);
114 - req.end();
115 - });
116 -}
117 -
118 -// 获取 Apifox 项目所有接口
119 -async function fetchApis() {
120 - log('\n📡 正在从 Apifox 获取接口数据...', 'blue');
121 -
122 - const options = {
123 - hostname: CONFIG.baseUrl,
124 - path: `/api/v1/projects/${CONFIG.projectId}/apis?pageSize=1000`,
125 - method: 'GET',
126 - headers: {
127 - 'Authorization': `Bearer ${CONFIG.token}`,
128 - 'Content-Type': 'application/json'
129 - }
130 - };
131 -
132 - try {
133 - const response = await httpsRequest(options);
134 - const apis = Array.isArray(response.data) ? response.data : [];
135 -
136 - log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
137 - return apis;
138 - } catch (err) {
139 - log(`❌ 获取接口失败: ${err.message}`, 'red');
140 - throw err;
141 - }
142 -}
143 -
144 -// 获取接口详情
145 -async function fetchApiDetail(apiId) {
146 - const options = {
147 - hostname: CONFIG.baseUrl,
148 - path: `/api/v1/projects/${CONFIG.projectId}/apis/${apiId}`,
149 - method: 'GET',
150 - headers: {
151 - 'Authorization': `Bearer ${CONFIG.token}`,
152 - 'Content-Type': 'application/json'
153 - }
154 - };
155 -
156 - try {
157 - const response = await httpsRequest(options);
158 - return response.data;
159 - } catch (err) {
160 - log(`⚠️ 获取接口详情失败 (${apiId}): ${err.message}`, 'yellow');
161 - return null;
162 - }
163 -}
164 -
165 -// 生成 API 接口代码
166 -function generateApiCode(apis) {
167 - log('\n📝 正在生成 API 接口代码...', 'blue');
168 -
169 - let code = `/**
170 - * @description API 接口定义(从 Apifox 自动生成)
171 - * @generated by scripts/apifox-sync.js
172 - * @lastUpdated ${new Date().toISOString()}
173 - */
174 -
175 -import { fn } from '@/api/fn';
176 -
177 -`;
178 -
179 - // 按标签分组
180 - const groupedApis = {};
181 - apis.forEach(api => {
182 - const tags = api.attributes?.tags || ['default'];
183 - const tag = tags[0];
184 - if (!groupedApis[tag]) {
185 - groupedApis[tag] = [];
186 - }
187 - groupedApis[tag].push(api);
188 - });
189 -
190 - // 生成每个分组的接口
191 - Object.entries(groupedApis).forEach(([tag, items]) => {
192 - code += `\n// ==================== ${tag} ====================\n\n`;
193 -
194 - items.forEach(api => {
195 - const apiId = api.id;
196 - const name = api.attributes?.name || apiId;
197 - const method = (api.attributes?.method || 'GET').toLowerCase();
198 - const path = api.attributes?.path || '';
199 -
200 - // 生成接口函数名(将路径转换为驼峰命名)
201 - const functionName = pathToFunctionName(method, path);
202 -
203 - // 生成注释
204 - code += `/**\n`;
205 - code += ` * @description ${name}\n`;
206 - code += ` * @path ${method.toUpperCase()} ${path}\n`;
207 - if (api.attributes?.description) {
208 - code += ` * @remark ${api.attributes.description}\n`;
209 - }
210 - code += ` */\n`;
211 -
212 - // 生成接口函数
213 - code += `export const ${functionName} = (params = {}) => {\n`;
214 - code += ` return fn({\n`;
215 - code += ` method: '${method}',\n`;
216 - code += ` url: '${path}',\n`;
217 - code += ` data: params\n`;
218 - code += ` })\n`;
219 - code += `}\n\n`;
220 - });
221 - });
222 -
223 - // 写入文件
224 - const outputPath = path.join(CONFIG.outputDir, 'generated.js');
225 - fs.writeFileSync(outputPath, code, 'utf-8');
226 -
227 - log(`✅ 已生成 API 接口代码: ${outputPath}`, 'green');
228 - log(` 共 ${apis.length} 个接口,${Object.keys(groupedApis).length} 个分组`, 'green');
229 -
230 - return outputPath;
231 -}
232 -
233 -// 生成 Mock 数据
234 -function generateMockData(apis) {
235 - log('\n🎭 正在生成 Mock 数据...', 'blue');
236 -
237 - // 确保 mocks 目录存在
238 - if (!fs.existsSync(CONFIG.mockDir)) {
239 - fs.mkdirSync(CONFIG.mockDir, { recursive: true });
240 - }
241 -
242 - let mockIndex = `/**
243 - * @description Mock 数据(从 Apifox 自动生成)
244 - * @generated by scripts/apifox-sync.js
245 - */
246 -
247 -`;
248 -
249 - // 为每个接口生成 Mock 数据
250 - apis.forEach(api => {
251 - const path = api.attributes?.path || '';
252 - const method = (api.attributes?.method || 'GET').toLowerCase();
253 - const functionName = pathToFunctionName(method, path);
254 - const mockFileName = `${functionName}.mock.js`;
255 -
256 - // 从响应示例中提取 Mock 数据
257 - const responses = api.attributes?.responses || [];
258 - const successResponse = responses.find(r => r.code === 200) || responses[0];
259 -
260 - if (successResponse) {
261 - // 生成 Mock 文件
262 - const mockCode = `/**
263 - * @description Mock data for ${functionName}
264 - * @path ${method.toUpperCase()} ${path}
265 - */
266 -
267 -export const mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data = ${JSON.stringify(successResponse, null, 2)};
268 -
269 -export default mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data;
270 -`;
271 -
272 - const mockPath = path.join(CONFIG.mockDir, mockFileName);
273 - fs.writeFileSync(mockPath, mockCode, 'utf-8');
274 -
275 - mockIndex += `export { default as mock${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data } from './${mockFileName}';\n`;
276 - }
277 - });
278 -
279 - // 写入索引文件
280 - const indexPath = path.join(CONFIG.mockDir, 'index.js');
281 - fs.writeFileSync(indexPath, mockIndex, 'utf-8');
282 -
283 - log(`✅ 已生成 Mock 数据: ${indexPath}`, 'green');
284 -
285 - return indexPath;
286 -}
287 -
288 -// 将 URL 路径转换为驼峰命名的函数名
289 -function pathToFunctionName(method, path) {
290 - // 移除路径参数和查询参数
291 - let cleanPath = path
292 - .replace(/:\w+/g, '')
293 - .replace(/\?.*$/, '')
294 - .replace(/^\//, '')
295 - .replace(/\/$/, '');
296 -
297 - // 转换为驼峰命名
298 - const parts = cleanPath.split('/').filter(Boolean);
299 - let functionName = method;
300 -
301 - parts.forEach(part => {
302 - // 首字母大写
303 - const capitalized = part.charAt(0).toUpperCase() + part.slice(1);
304 - functionName += capitalized;
305 - });
306 -
307 - // 移除特殊字符
308 - functionName = functionName.replace(/[^a-zA-Z0-9]/g, '');
309 -
310 - return functionName;
311 -}
312 -
313 -// 主函数
314 -async function main() {
315 - try {
316 - log('\n🚀 Apifox API 同步工具', 'bright');
317 - log('=' .repeat(50), 'bright');
318 -
319 - // 1. 加载配置
320 - loadEnv();
321 -
322 - // 2. 获取接口列表
323 - const apis = await fetchApis();
324 -
325 - // 3. 生成 API 接口代码
326 - generateApiCode(apis);
327 -
328 - // 4. 生成 Mock 数据
329 - generateMockData(apis);
330 -
331 - // 完成
332 - log('\n✅ 同步完成!', 'green');
333 - log('📦 生成的文件:', 'blue');
334 - log(` - src/api/generated.js (API 接口)`, 'blue');
335 - log(` - src/mocks/ (Mock 数据)`, 'blue');
336 - log('\n💡 提示: 将 src/api/generated.js 中的接口导入到 src/api/index.js 中使用', 'yellow');
337 -
338 - } catch (err) {
339 - log(`\n❌ 同步失败: ${err.message}`, 'red');
340 - process.exit(1);
341 - }
342 -}
343 -
344 -// 运行
345 -main();
1 -#!/usr/bin/env node
2 -
3 -/**
4 - * Apifox 到 OpenAPI 转换工具
5 - *
6 - * 功能:
7 - * 1. 从 Apifox 获取所有接口数据
8 - * 2. 转换为 OpenAPI 3.0 格式
9 - * 3. 保存到 docs/api-specs/ 目录
10 - * 4. 可选择运行 generateApiFromOpenAPI.js 生成 API 代码
11 - *
12 - * 使用:
13 - * node scripts/apifox-to-openapi.js
14 - *
15 - * 环境变量:
16 - * VITE_APIFOX_TOKEN - Apifox API Token
17 - * VITE_APIFOX_PROJECT_ID - Apifox 项目 ID
18 - */
19 -
20 -const fs = require('fs');
21 -const path = require('path');
22 -const https = require('https');
23 -const { execSync } = require('child_process');
24 -
25 -// 配置
26 -const CONFIG = {
27 - token: null,
28 - projectId: null,
29 - baseUrl: 'api.apifox.com',
30 - outputDir: path.join(__dirname, '../docs/api-specs'),
31 - autoGenerate: true // 是否自动运行 API 代码生成
32 -};
33 -
34 -// 颜色输出
35 -const colors = {
36 - reset: '\x1b[0m',
37 - bright: '\x1b[1m',
38 - green: '\x1b[32m',
39 - yellow: '\x1b[33m',
40 - red: '\x1b[31m',
41 - blue: '\x1b[34m',
42 - cyan: '\x1b[36m'
43 -};
44 -
45 -function log(message, color = 'reset') {
46 - console.log(`${colors[color]}${message}${colors.reset}`);
47 -}
48 -
49 -// 加载 .env.apifox 文件
50 -function loadEnv() {
51 - const envPath = path.join(__dirname, '../.env.apifox');
52 -
53 - if (!fs.existsSync(envPath)) {
54 - log('❌ 未找到 .env.apifox 文件', 'red');
55 - log('📝 请先创建 .env.apifox 文件并填写 Apifox 凭证:', 'yellow');
56 - log('');
57 - log('VITE_APIFOX_TOKEN=your_api_token_here', 'blue');
58 - log('VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue');
59 - process.exit(1);
60 - }
61 -
62 - const env = fs.readFileSync(envPath, 'utf-8');
63 - env.split('\n').forEach(line => {
64 - const [key, ...valueParts] = line.split('=');
65 - const value = valueParts.join('=');
66 - if (key && !key.startsWith('#') && value) {
67 - process.env[key.trim()] = value.trim();
68 - }
69 - });
70 -
71 - if (!process.env.VITE_APIFOX_TOKEN || !process.env.VITE_APIFOX_PROJECT_ID) {
72 - log('❌ .env.apifox 文件中缺少必要的配置', 'red');
73 - log('请确保填写了 VITE_APIFOX_TOKEN 和 VITE_APIFOX_PROJECT_ID', 'yellow');
74 - process.exit(1);
75 - }
76 -
77 - CONFIG.token = process.env.VITE_APIFOX_TOKEN;
78 - CONFIG.projectId = process.env.VITE_APIFOX_PROJECT_ID;
79 -
80 - log(`✅ 已加载配置,项目 ID: ${CONFIG.projectId}`, 'green');
81 -}
82 -
83 -// 发送 HTTPS 请求
84 -function httpsRequest(options) {
85 - return new Promise((resolve, reject) => {
86 - const req = https.request(options, (res) => {
87 - const chunks = [];
88 -
89 - res.on('data', chunk => {
90 - chunks.push(chunk);
91 - });
92 -
93 - res.on('end', () => {
94 - const raw = Buffer.concat(chunks).toString('utf8').trim();
95 -
96 - if (!raw) {
97 - if (res.statusCode >= 200 && res.statusCode < 300) {
98 - resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
99 - return;
100 - }
101 -
102 - reject(new Error(`HTTP ${res.statusCode}: Empty response`));
103 - return;
104 - }
105 -
106 - try {
107 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
108 - if (res.statusCode === 200) {
109 - resolve(json);
110 - } else {
111 - reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
112 - }
113 - } catch (err) {
114 - reject(new Error(`解析响应失败: ${err.message}`));
115 - }
116 - });
117 - });
118 -
119 - req.on('error', reject);
120 - req.end();
121 - });
122 -}
123 -
124 -// 获取 Apifox 项目所有接口
125 -async function fetchApis() {
126 - log('\n📡 正在从 Apifox 获取接口数据...', 'blue');
127 -
128 - const options = {
129 - hostname: CONFIG.baseUrl,
130 - path: `/api/v1/projects/${CONFIG.projectId}/apis?pageSize=1000`,
131 - method: 'GET',
132 - headers: {
133 - 'Authorization': `Bearer ${CONFIG.token}`,
134 - 'Content-Type': 'application/json'
135 - }
136 - };
137 -
138 - try {
139 - const response = await httpsRequest(options);
140 - const apis = Array.isArray(response.data) ? response.data : [];
141 -
142 - log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
143 - return apis;
144 - } catch (err) {
145 - log(`❌ 获取接口失败: ${err.message}`, 'red');
146 - throw err;
147 - }
148 -}
149 -
150 -// 获取接口详情
151 -async function fetchApiDetail(apiId) {
152 - const options = {
153 - hostname: CONFIG.baseUrl,
154 - path: `/api/v1/projects/${CONFIG.projectId}/apis/${apiId}`,
155 - method: 'GET',
156 - headers: {
157 - 'Authorization': `Bearer ${CONFIG.token}`,
158 - 'Content-Type': 'application/json'
159 - }
160 - };
161 -
162 - try {
163 - const response = await httpsRequest(options);
164 - return response.data;
165 - } catch (err) {
166 - log(`⚠️ 获取接口详情失败 (${apiId}): ${err.message}`, 'yellow');
167 - return null;
168 - }
169 -}
170 -
171 -// 将 Apifox 接口转换为 OpenAPI 3.0 格式
172 -function convertToOpenAPI(apiDetail) {
173 - const attributes = apiDetail.attributes || {};
174 - const method = attributes.method ? attributes.method.toLowerCase() : 'get';
175 - const path = attributes.path || '/';
176 -
177 - // 构建参数
178 - const parameters = [];
179 - const requestBody = {};
180 -
181 - // 处理 query 参数
182 - if (attributes.parameters && attributes.parameters.length > 0) {
183 - attributes.parameters.forEach(param => {
184 - if (param.in === 'query') {
185 - parameters.push({
186 - name: param.name,
187 - in: 'query',
188 - description: param.description || '',
189 - required: param.required || false,
190 - schema: {
191 - type: param.schema?.type || 'string',
192 - example: param.example
193 - }
194 - });
195 - }
196 - });
197 - }
198 -
199 - // 处理 body 参数(POST/PUT 请求)
200 - if (['post', 'put', 'patch'].includes(method)) {
201 - const bodyParams = attributes.requestBody?.content?.['application/json']?.schema?.properties || {};
202 - const requiredParams = attributes.requestBody?.content?.['application/json']?.schema?.required || [];
203 -
204 - if (Object.keys(bodyParams).length > 0) {
205 - requestBody.content = {
206 - 'application/json': {
207 - schema: {
208 - type: 'object',
209 - properties: bodyParams,
210 - required: requiredParams
211 - }
212 - }
213 - };
214 - }
215 - }
216 -
217 - // 构建响应
218 - const responses = {
219 - '200': {
220 - description: '成功',
221 - content: {
222 - 'application/json': {
223 - schema: extractResponseSchema(attributes.responses)
224 - }
225 - }
226 - }
227 - };
228 -
229 - // 构建 OpenAPI 文档
230 - const openapi = {
231 - openapi: '3.0.0',
232 - info: {
233 - title: attributes.name || apiDetail.id,
234 - description: attributes.description || '',
235 - version: '1.0.0'
236 - },
237 - paths: {
238 - [path]: {
239 - [method]: {
240 - summary: attributes.name || apiDetail.id,
241 - description: attributes.description || '',
242 - parameters,
243 - requestBody: Object.keys(requestBody).length > 0 ? requestBody : undefined,
244 - responses
245 - }
246 - }
247 - }
248 - };
249 -
250 - return openapi;
251 -}
252 -
253 -// 提取响应结构
254 -function extractResponseSchema(responses) {
255 - if (!responses || responses.length === 0) {
256 - return {
257 - type: 'object',
258 - properties: {
259 - code: { type: 'number', description: '状态码' },
260 - msg: { type: 'string', description: '消息' },
261 - data: { type: 'any', description: '数据' }
262 - }
263 - };
264 - }
265 -
266 - const successResponse = responses.find(r => r.code === 200) || responses[0];
267 - if (successResponse && successResponse.schema) {
268 - return successResponse.schema;
269 - }
270 -
271 - return {
272 - type: 'object',
273 - properties: {
274 - code: { type: 'number', description: '状态码' },
275 - msg: { type: 'string', description: '消息' },
276 - data: { type: 'any', description: '数据' }
277 - }
278 - };
279 -}
280 -
281 -// 将 OpenAPI 对象转换为 YAML 字符串
282 -function openAPIToYAML(openapi) {
283 - const yaml = require('js-yaml');
284 - return yaml.dump(openapi, {
285 - indent: 2,
286 - lineWidth: -1,
287 - noRefs: true
288 - });
289 -}
290 -
291 -// 生成 Markdown 文件内容
292 -function generateMarkdown(openapi, apiDetail) {
293 - const yaml = openAPIToYAML(openapi);
294 - const attributes = apiDetail.attributes || {};
295 -
296 - return `# ${attributes.name || apiDetail.id}
297 -
298 -${attributes.description || '暂无描述'}
299 -
300 -## 接口信息
301 -
302 -- **方法**: ${attributes.method?.toUpperCase() || 'GET'}
303 -- **路径**: ${attributes.path || '/'}
304 -- **标签**: ${(attributes.tags || []).join(', ') || '默认'}
305 -
306 -## OpenAPI 规范
307 -
308 -\`\`\`yaml
309 -${yaml}
310 -\`\`\`
311 -`;
312 -}
313 -
314 -// 将接口按标签分组
315 -function groupApisByTag(apis) {
316 - const grouped = {};
317 -
318 - apis.forEach(api => {
319 - const tags = api.attributes?.tags || ['default'];
320 - const tag = tags[0];
321 -
322 - if (!grouped[tag]) {
323 - grouped[tag] = [];
324 - }
325 -
326 - grouped[tag].push(api);
327 - });
328 -
329 - return grouped;
330 -}
331 -
332 -// 生成文件名(从接口路径和名称)
333 -function generateFileName(api) {
334 - const attributes = api.attributes || {};
335 - const path = attributes.path || '/';
336 - const method = attributes.method?.toLowerCase() || 'get';
337 -
338 - // 从路径提取文件名
339 - const pathParts = path.split('/').filter(Boolean);
340 - const lastPart = pathParts[pathParts.length - 1] || 'index';
341 -
342 - // 移除路径参数
343 - const cleanName = lastPart.replace(/^:/, '').replace(/[^a-zA-Z0-9]/g, '-');
344 -
345 - return `${method}-${cleanName}`;
346 -}
347 -
348 -// 保存 OpenAPI 文档到文件
349 -async function saveOpenAPIDocs(groupedApis) {
350 - log('\n📝 正在生成 OpenAPI 文档...', 'blue');
351 -
352 - // 确保输出目录存在
353 - if (!fs.existsSync(CONFIG.outputDir)) {
354 - fs.mkdirSync(CONFIG.outputDir, { recursive: true });
355 - }
356 -
357 - let totalFiles = 0;
358 -
359 - for (const [tag, apis] of Object.entries(groupedApis)) {
360 - const moduleDir = path.join(CONFIG.outputDir, tag);
361 -
362 - // 创建模块目录
363 - if (!fs.existsSync(moduleDir)) {
364 - fs.mkdirSync(moduleDir, { recursive: true });
365 - }
366 -
367 - log(`\n处理模块: ${tag}`, 'cyan');
368 -
369 - for (const api of apis) {
370 - try {
371 - // 获取接口详情
372 - const apiDetail = await fetchApiDetail(api.id);
373 - if (!apiDetail) continue;
374 -
375 - // 转换为 OpenAPI
376 - const openapi = convertToOpenAPI(apiDetail);
377 -
378 - // 生成 Markdown 文件
379 - const fileName = generateFileName(api);
380 - const markdown = generateMarkdown(openapi, apiDetail);
381 - const filePath = path.join(moduleDir, `${fileName}.md`);
382 -
383 - // 写入文件
384 - fs.writeFileSync(filePath, markdown, 'utf-8');
385 -
386 - log(` ✓ ${fileName}.md`, 'green');
387 - totalFiles++;
388 - } catch (err) {
389 - log(` ✗ 处理失败: ${err.message}`, 'red');
390 - }
391 - }
392 - }
393 -
394 - log(`\n✅ 共生成 ${totalFiles} 个 OpenAPI 文档`, 'green');
395 - log(`📁 输出目录: ${CONFIG.outputDir}`, 'blue');
396 -
397 - return totalFiles;
398 -}
399 -
400 -// 运行 API 代码生成
401 -function runAPIGenerator() {
402 - if (!CONFIG.autoGenerate) {
403 - log('\n⏭️ 跳过 API 代码生成', 'yellow');
404 - return;
405 - }
406 -
407 - log('\n🔧 正在运行 API 代码生成...', 'blue');
408 -
409 - try {
410 - const generatorPath = path.join(__dirname, 'generateApiFromOpenAPI.js');
411 - execSync(`node "${generatorPath}"`, {
412 - stdio: 'inherit',
413 - cwd: path.join(__dirname, '..')
414 - });
415 - log('✅ API 代码生成完成', 'green');
416 - } catch (err) {
417 - log(`⚠️ API 代码生成失败: ${err.message}`, 'yellow');
418 - }
419 -}
420 -
421 -// 主函数
422 -async function main() {
423 - try {
424 - log('\n🚀 Apifox 到 OpenAPI 转换工具', 'bright');
425 - log('=' .repeat(50), 'bright');
426 -
427 - // 1. 加载配置
428 - loadEnv();
429 -
430 - // 2. 获取接口列表
431 - const apis = await fetchApis();
432 -
433 - // 3. 按标签分组
434 - const groupedApis = groupApisByTag(apis);
435 - log(`\n📦 找到 ${Object.keys(groupedApis).length} 个模块:`, 'cyan');
436 - Object.keys(groupedApis).forEach(tag => {
437 - log(` - ${tag} (${groupedApis[tag].length} 个接口)`, 'cyan');
438 - });
439 -
440 - // 4. 保存 OpenAPI 文档
441 - await saveOpenAPIDocs(groupedApis);
442 -
443 - // 5. 运行 API 代码生成
444 - runAPIGenerator();
445 -
446 - // 完成
447 - log('\n✅ 转换完成!', 'green');
448 - log('\n💡 提示:', 'yellow');
449 - log(' 1. 检查 docs/api-specs/ 目录查看生成的 OpenAPI 文档', 'blue');
450 - log(' 2. 检查 src/api/ 目录查看生成的 API 代码', 'blue');
451 - log(' 3. 在组件中导入并使用生成的 API', 'blue');
452 -
453 - } catch (err) {
454 - log(`\n❌ 转换失败: ${err.message}`, 'red');
455 - process.exit(1);
456 - }
457 -}
458 -
459 -// 运行
460 -main();
1 -#!/usr/bin/env node
2 -
3 -/**
4 - * Apifox API 响应调试工具
5 - *
6 - * 功能:输出完整的 API 响应,帮助诊断问题
7 - */
8 -
9 -const fs = require('fs');
10 -const path = require('path');
11 -const https = require('https');
12 -
13 -// 加载配置
14 -function loadConfig() {
15 - const envPath = path.join(__dirname, '../.env.apifox');
16 - const env = fs.readFileSync(envPath, 'utf-8');
17 - const config = {};
18 -
19 - env.split('\n').forEach(line => {
20 - const [key, ...valueParts] = line.split('=');
21 - const value = valueParts.join('=');
22 - if (key && !key.startsWith('#') && value) {
23 - config[key.trim()] = value.trim();
24 - }
25 - });
26 -
27 - return config;
28 -}
29 -
30 -// 发送 HTTPS 请求
31 -function httpsRequest(options) {
32 - return new Promise((resolve, reject) => {
33 - console.log(`📡 请求 URL: https://${options.hostname}${options.path}`);
34 - console.log(`📋 请求头:`, JSON.stringify(options.headers, null, 2));
35 -
36 - const req = https.request(options, (res) => {
37 - console.log(`\n📊 响应状态码: ${res.statusCode}`);
38 - console.log(`📋 响应头:`, JSON.stringify(res.headers, null, 2));
39 -
40 - const chunks = [];
41 -
42 - res.on('data', chunk => {
43 - chunks.push(chunk);
44 - });
45 -
46 - res.on('end', () => {
47 - const raw = Buffer.concat(chunks).toString('utf8').trim();
48 -
49 - if (!raw) {
50 - console.log('⚠️ 响应体为空');
51 - resolve({ raw: null, statusCode: res.statusCode });
52 - return;
53 - }
54 -
55 - console.log(`\n📦 响应体长度: ${raw.length} 字符`);
56 - console.log(`📄 响应体内容:`);
57 - console.log('---开始---');
58 - console.log(raw);
59 - console.log('---结束---\n');
60 -
61 - try {
62 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
63 - resolve(json);
64 - } catch (err) {
65 - console.error(`❌ JSON 解析失败: ${err.message}`);
66 - resolve({ raw, parseError: err.message });
67 - }
68 - });
69 - });
70 -
71 - req.on('error', (err) => {
72 - console.error(`❌ 请求失败: ${err.message}`);
73 - reject(err);
74 - });
75 -
76 - req.end();
77 - });
78 -}
79 -
80 -// 主函数
81 -async function main() {
82 - console.log('🔍 Apifox API 响应调试工具\n');
83 -
84 - const config = loadConfig();
85 -
86 - // 测试获取接口列表
87 - console.log('========================================');
88 - console.log('测试: 获取接口列表');
89 - console.log('========================================\n');
90 -
91 - const options = {
92 - hostname: 'api.apifox.com',
93 - path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}/apis?pageSize=10`,
94 - method: 'GET',
95 - headers: {
96 - 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
97 - 'Content-Type': 'application/json'
98 - }
99 - };
100 -
101 - try {
102 - const response = await httpsRequest(options);
103 -
104 - console.log('========================================');
105 - console.log('解析结果');
106 - console.log('========================================\n');
107 -
108 - if (response.parseError) {
109 - console.log(`❌ 无法解析 JSON`);
110 - console.log(`原始响应:`, response.raw);
111 - } else {
112 - console.log(`✅ JSON 解析成功`);
113 - console.log(`响应类型: ${typeof response}`);
114 - console.log(`响应键:`, Object.keys(response));
115 - console.log(`\ndata 类型: ${typeof response.data}`);
116 - console.log(`data 是数组: ${Array.isArray(response.data)}`);
117 - console.log(`data 长度: ${response.data?.length || 0}`);
118 - console.log(`total: ${response.total}`);
119 -
120 - if (response.data && response.data.length > 0) {
121 - console.log(`\n第一个接口示例:`);
122 - console.log(JSON.stringify(response.data[0], null, 2));
123 - }
124 - }
125 -
126 - } catch (err) {
127 - console.error(`\n❌ 请求失败: ${err.message}`);
128 - }
129 -}
130 -
131 -main();
...@@ -709,9 +709,11 @@ function compareAPIChanges(openAPIDir) { ...@@ -709,9 +709,11 @@ function compareAPIChanges(openAPIDir) {
709 try { 709 try {
710 const newDocs = parseOpenAPIPath(moduleDir); 710 const newDocs = parseOpenAPIPath(moduleDir);
711 if (newDocs && newDocs.length > 0) { 711 if (newDocs && newDocs.length > 0) {
712 - console.log(` 包含 ${newDocs.length} 个新增接口:`); 712 + // 使用 extractAPIInfo 提取 API 信息
713 - newDocs.forEach(doc => { 713 + const apiInfos = newDocs.map(doc => extractAPIInfo(doc));
714 - console.log(` • ${doc.method} ${doc.path} - ${doc.summary}`); 714 + console.log(` 包含 ${apiInfos.length} 个新增接口:`);
715 + apiInfos.forEach(api => {
716 + console.log(` • ${api.method} ${api.path} - ${api.summary || api.name}`);
715 }); 717 });
716 } 718 }
717 } catch (error) { 719 } catch (error) {
......
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 - // Apifox Token 格式:APS-xxxxxxxx (大写)
72 - if (!token.toUpperCase().startsWith('APS-')) {
73 - log('⚠️ Token 格式不正确', 'yellow');
74 - log(' 正确格式: APS-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'cyan');
75 - return false;
76 - }
77 -
78 - if (token.length < 10) {
79 - log('⚠️ Token 长度似乎不正确', 'yellow');
80 - return false;
81 - }
82 -
83 - return true;
84 -}
85 -
86 -// 验证项目 ID 格式
87 -function validateProjectId(projectId) {
88 - if (!/^\d+$/.test(projectId)) {
89 - log('⚠️ 项目 ID 格式不正确', 'yellow');
90 - log(' 正确格式: 纯数字(如: 12345678)', 'cyan');
91 - return false;
92 - }
93 -
94 - return true;
95 -}
96 -
97 -// 发送 HTTPS 请求
98 -function httpsRequest(options) {
99 - return new Promise((resolve, reject) => {
100 - log(`📡 发送请求到: https://${options.hostname}${options.path}`, 'blue');
101 -
102 - const req = https.request(options, (res) => {
103 - const chunks = [];
104 -
105 - res.on('data', chunk => {
106 - chunks.push(chunk);
107 - });
108 -
109 - res.on('end', () => {
110 - const raw = Buffer.concat(chunks).toString('utf8').trim();
111 -
112 - if (!raw) {
113 - if (res.statusCode >= 200 && res.statusCode < 300) {
114 - resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
115 - return;
116 - }
117 -
118 - reject(new Error(`HTTP ${res.statusCode}: Empty response`));
119 - return;
120 - }
121 -
122 - try {
123 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
124 - if (res.statusCode === 200) {
125 - resolve(json);
126 - } else {
127 - reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
128 - }
129 - } catch (err) {
130 - reject(new Error(`解析响应失败: ${err.message}`));
131 - }
132 - });
133 - });
134 -
135 - req.on('error', reject);
136 - req.end();
137 - });
138 -}
139 -
140 -// 测试获取项目信息
141 -async function testProjectInfo(config) {
142 - log('\n📊 获取项目信息...', 'cyan');
143 -
144 - const options = {
145 - hostname: 'api.apifox.com',
146 - path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}`,
147 - method: 'GET',
148 - headers: {
149 - 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
150 - 'Content-Type': 'application/json'
151 - }
152 - };
153 -
154 - try {
155 - const response = await httpsRequest(options);
156 - if (!response || !response.data) {
157 - throw new Error('响应为空或缺少 data 字段');
158 - }
159 -
160 - const project = response.data;
161 -
162 - log('✅ 项目信息获取成功', 'green');
163 - log('');
164 - log('项目详情:', 'bright');
165 - log(` 名称: ${project.name || '未设置'}`, 'reset');
166 - log(` ID: ${project.id}`, 'reset');
167 - log(` 描述: ${project.description || '无'}`, 'reset');
168 - log(` 创建时间: ${project.createdTime || '未知'}`, 'reset');
169 -
170 - return true;
171 - } catch (err) {
172 - log(`❌ 获取项目信息失败: ${err.message}`, 'red');
173 -
174 - if (err.message.includes('401')) {
175 - log('\n可能的原因:', 'yellow');
176 - log(' 1. API Token 错误或已过期', 'yellow');
177 - log(' 2. 请检查 .env.apifox 中的 VITE_APIFOX_TOKEN', 'yellow');
178 - } else if (err.message.includes('403') || err.message.includes('404')) {
179 - log('\n可能的原因:', 'yellow');
180 - log(' 1. 项目 ID 错误', 'yellow');
181 - log(' 2. 账号无该项目的访问权限', 'yellow');
182 - log(' 3. 请检查 .env.apifox 中的 VITE_APIFOX_PROJECT_ID', 'yellow');
183 - }
184 -
185 - return false;
186 - }
187 -}
188 -
189 -// 测试获取接口列表
190 -async function testApiList(config) {
191 - log('\n📋 获取接口列表...', 'cyan');
192 -
193 - const options = {
194 - hostname: 'api.apifox.com',
195 - path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}/apis?pageSize=10`,
196 - method: 'GET',
197 - headers: {
198 - 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
199 - 'Content-Type': 'application/json'
200 - }
201 - };
202 -
203 - try {
204 - const response = await httpsRequest(options);
205 - const apis = Array.isArray(response.data) ? response.data : [];
206 -
207 - log('✅ 接口列表获取成功', 'green');
208 - log(` 共找到 ${response.total || apis.length} 个接口`, 'blue');
209 -
210 - if (apis.length > 0) {
211 - log('\n前 10 个接口:', 'bright');
212 - apis.forEach((api, index) => {
213 - const method = (api.attributes?.method || 'GET').toUpperCase();
214 - const path = api.attributes?.path || '/';
215 - const name = api.attributes?.name || api.id;
216 -
217 - log(` ${index + 1}. ${method} ${path}`, 'cyan');
218 - log(` ${name}`, 'reset');
219 - });
220 -
221 - if (response.total > 10) {
222 - log(`\n ... 还有 ${response.total - 10} 个接口`, 'yellow');
223 - }
224 - } else if (response.__empty) {
225 - log('⚠️ 接口列表响应为空,已按空列表处理', 'yellow');
226 - }
227 -
228 - return true;
229 - } catch (err) {
230 - log(`❌ 获取接口列表失败: ${err.message}`, 'red');
231 - return false;
232 - }
233 -}
234 -
235 -// 主函数
236 -async function main() {
237 - log('\n🧪 Apifox 连接测试工具', 'bright');
238 - log('=' .repeat(50), 'bright');
239 -
240 - // 1. 加载配置
241 - log('\n📝 步骤 1/4: 加载配置文件', 'cyan');
242 - const config = loadConfig();
243 - if (!config) {
244 - process.exit(1);
245 - }
246 -
247 - log('✅ 配置文件加载成功', 'green');
248 - log(` Token: ${config.VITE_APIFOX_TOKEN.substring(0, 15)}...`, 'blue');
249 - log(` 项目 ID: ${config.VITE_APIFOX_PROJECT_ID}`, 'blue');
250 -
251 - // 2. 验证格式
252 - log('\n🔍 步骤 2/4: 验证配置格式', 'cyan');
253 -
254 - let valid = true;
255 -
256 - if (!validateToken(config.VITE_APIFOX_TOKEN)) {
257 - valid = false;
258 - }
259 -
260 - if (!validateProjectId(config.VITE_APIFOX_PROJECT_ID)) {
261 - valid = false;
262 - }
263 -
264 - if (!valid) {
265 - log('\n❌ 配置验证失败,请修正后重试', 'red');
266 - process.exit(1);
267 - }
268 -
269 - log('✅ 配置格式验证通过', 'green');
270 -
271 - // 3. 测试项目信息
272 - log('\n🌐 步骤 3/4: 测试 API 连接', 'cyan');
273 - const projectSuccess = await testProjectInfo(config);
274 -
275 - if (!projectSuccess) {
276 - log('\n❌ API 连接测试失败', 'red');
277 - log('\n💡 建议:', 'yellow');
278 - log(' 1. 检查网络连接', 'yellow');
279 - log(' 2. 验证 Token 和项目 ID 是否正确', 'yellow');
280 - log(' 3. 确认账号有该项目的访问权限', 'yellow');
281 - process.exit(1);
282 - }
283 -
284 - // 4. 测试接口列表
285 - const apiSuccess = await testApiList(config);
286 -
287 - if (!apiSuccess) {
288 - log('\n⚠️ 接口列表获取失败,但项目连接正常', 'yellow');
289 - process.exit(1);
290 - }
291 -
292 - // 完成
293 - log('\n' + '='.repeat(50), 'bright');
294 - log('✅ 所有测试通过!', 'green');
295 - log('\n🎉 配置正确,可以开始同步 API 了', 'green');
296 - log('\n运行以下命令同步 API:', 'blue');
297 - log(' pnpm api:sync', 'bright');
298 - log('');
299 -}
300 -
301 -// 运行
302 -main().catch(err => {
303 - log(`\n❌ 测试失败: ${err.message}`, 'red');
304 - console.error(err);
305 - process.exit(1);
306 -});
1 -#!/usr/bin/env node
2 -
3 -/**
4 - * 测试不同的 Apifox API 端点
5 - */
6 -
7 -const fs = require('fs');
8 -const path = require('path');
9 -const https = require('https');
10 -
11 -// 加载配置
12 -function loadConfig() {
13 - const envPath = path.join(__dirname, '../.env.apifox');
14 - const env = fs.readFileSync(envPath, 'utf-8');
15 - const config = {};
16 -
17 - env.split('\n').forEach(line => {
18 - const [key, ...valueParts] = line.split('=');
19 - const value = valueParts.join('=');
20 - if (key && !key.startsWith('#') && value) {
21 - config[key.trim()] = value.trim();
22 - }
23 - });
24 -
25 - return config;
26 -}
27 -
28 -// 发送 HTTPS 请求
29 -function httpsRequest(options) {
30 - return new Promise((resolve, reject) => {
31 - const req = https.request(options, (res) => {
32 - const chunks = [];
33 -
34 - res.on('data', chunk => {
35 - chunks.push(chunk);
36 - });
37 -
38 - res.on('end', () => {
39 - const raw = Buffer.concat(chunks).toString('utf8').trim();
40 -
41 - if (!raw) {
42 - resolve({ statusCode: res.statusCode, data: null, headers: res.headers });
43 - return;
44 - }
45 -
46 - try {
47 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
48 - resolve({ statusCode: res.statusCode, data: json, headers: res.headers });
49 - } catch (err) {
50 - resolve({ statusCode: res.statusCode, raw, parseError: err.message, headers: res.headers });
51 - }
52 - });
53 - });
54 -
55 - req.on('error', reject);
56 - req.end();
57 - });
58 -}
59 -
60 -// 测试端点
61 -async function testEndpoint(config, endpointPath, description) {
62 - console.log(`\n测试: ${description}`);
63 - console.log(`端点: ${endpointPath}`);
64 -
65 - const options = {
66 - hostname: 'api.apifox.com',
67 - path: endpointPath,
68 - method: 'GET',
69 - headers: {
70 - 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
71 - 'Content-Type': 'application/json'
72 - }
73 - };
74 -
75 - try {
76 - const response = await httpsRequest(options);
77 -
78 - console.log(`状态码: ${response.statusCode}`);
79 - console.log(`Content-Type: ${response.headers['content-type']}`);
80 - console.log(`Content-Length: ${response.headers['content-length']}`);
81 -
82 - if (response.parseError) {
83 - console.log(`❌ JSON 解析失败: ${response.parseError}`);
84 - console.log(`原始响应 (前 200 字符): ${response.raw?.substring(0, 200)}`);
85 - } else if (response.data) {
86 - console.log(` 成功`);
87 -
88 - // 显示响应结构
89 - if (Array.isArray(response.data)) {
90 - console.log(` - data 是数组,长度: ${response.data.length}`);
91 - } else if (typeof response.data === 'object') {
92 - console.log(` - data 是对象,键: ${Object.keys(response.data).join(', ')}`);
93 - }
94 -
95 - // 如果有接口数据,显示第一个
96 - if (Array.isArray(response.data) && response.data.length > 0) {
97 - console.log(` - 第一个接口:`, JSON.stringify(response.data[0]).substring(0, 150));
98 - }
99 - } else {
100 - console.log(`⚠️ 响应为空`);
101 - }
102 -
103 - return response.statusCode === 200 && response.data && (Array.isArray(response.data) ? response.data.length > 0 : true);
104 - } catch (err) {
105 - console.log(`❌ 请求失败: ${err.message}`);
106 - return false;
107 - }
108 -}
109 -
110 -// 主函数
111 -async function main() {
112 - const config = loadConfig();
113 - const projectId = config.VITE_APIFOX_PROJECT_ID;
114 -
115 - console.log('='.repeat(60));
116 - console.log('测试不同的 Apifox API 端点');
117 - console.log('='.repeat(60));
118 -
119 - // 尝试不同的端点
120 - const endpoints = [
121 - { path: `/api/v1/projects/${projectId}/apis?pageSize=10`, desc: '当前使用的端点 (/apis)' },
122 - { path: `/api/v1/projects/${projectId}/interfaces?pageSize=10`, desc: '尝试 /interfaces' },
123 - { path: `/v1/projects/${projectId}/apis?pageSize=10`, desc: '不带 /api 前缀' },
124 - { path: `/api/v1/projects/${projectId}/api-lists?pageSize=10`, desc: '尝试 /api-lists' },
125 - { path: `/api/v1/projects/${projectId}/endpoints?pageSize=10`, desc: '尝试 /endpoints' },
126 - ];
127 -
128 - let successCount = 0;
129 -
130 - for (const endpoint of endpoints) {
131 - const success = await testEndpoint(config, endpoint.path, endpoint.desc);
132 - if (success) successCount++;
133 - }
134 -
135 - console.log('\n' + '='.repeat(60));
136 - console.log(`测试完成!成功: ${successCount}/${endpoints.length}`);
137 - console.log('='.repeat(60));
138 -}
139 -
140 -main();
1 -#!/usr/bin/env node
2 -
3 -/**
4 - * 测试 Apifox 导出 API
5 - */
6 -
7 -const fs = require('fs');
8 -const path = require('path');
9 -const https = require('https');
10 -
11 -// 加载配置
12 -function loadConfig() {
13 - const envPath = path.join(__dirname, '../.env.apifox');
14 - const env = fs.readFileSync(envPath, 'utf-8');
15 - const config = {};
16 -
17 - env.split('\n').forEach(line => {
18 - const [key, ...valueParts] = line.split('=');
19 - const value = valueParts.join('=');
20 - if (key && !key.startsWith('#') && value) {
21 - config[key.trim()] = value.trim();
22 - }
23 - });
24 -
25 - return config;
26 -}
27 -
28 -// 发送 HTTPS 请求
29 -function httpsRequest(options) {
30 - return new Promise((resolve, reject) => {
31 - const req = https.request(options, (res) => {
32 - const chunks = [];
33 -
34 - res.on('data', chunk => {
35 - chunks.push(chunk);
36 - });
37 -
38 - res.on('end', () => {
39 - const raw = Buffer.concat(chunks).toString('utf8').trim();
40 -
41 - if (!raw) {
42 - resolve({ statusCode: res.statusCode, data: null, headers: res.headers });
43 - return;
44 - }
45 -
46 - try {
47 - const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
48 - resolve({ statusCode: res.statusCode, data: json, headers: res.headers });
49 - } catch (err) {
50 - resolve({ statusCode: res.statusCode, raw, headers: res.headers });
51 - }
52 - });
53 - });
54 -
55 - req.on('error', reject);
56 - req.end();
57 - });
58 -}
59 -
60 -// 主函数
61 -async function main() {
62 - const config = loadConfig();
63 - const projectId = config.VITE_APIFOX_PROJECT_ID;
64 -
65 - console.log('='.repeat(60));
66 - console.log('测试 Apifox 导出 API');
67 - console.log('='.repeat(60));
68 -
69 - // 尝试导出 OpenAPI 格式
70 - const exportEndpoints = [
71 - { path: `/api/v1/projects/${projectId}/export-openapi`, desc: '导出 OpenAPI' },
72 - { path: `/api/v1/projects/${projectId}/export-openapi/3.0.0`, desc: '导出 OpenAPI 3.0' },
73 - { path: `/api/v1/projects/${projectId}/export-openapi/3.0.0/json`, desc: '导出 OpenAPI 3.0 JSON' },
74 - ];
75 -
76 - for (const endpoint of exportEndpoints) {
77 - console.log(`\n测试: ${endpoint.desc}`);
78 - console.log(`端点: ${endpoint.path}`);
79 -
80 - const options = {
81 - hostname: 'api.apifox.com',
82 - path: endpoint.path,
83 - method: 'GET',
84 - headers: {
85 - 'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
86 - 'Content-Type': 'application/json',
87 - 'X-Apifox-Version': '2024-06-14' // 尝试添加版本头
88 - }
89 - };
90 -
91 - try {
92 - const response = await httpsRequest(options);
93 -
94 - console.log(`状态码: ${response.statusCode}`);
95 - console.log(`Content-Type: ${response.headers['content-type']}`);
96 - console.log(`Content-Length: ${response.headers['content-length']}`);
97 -
98 - if (response.statusCode === 200 && response.raw) {
99 - console.log(`✅ 成功获取数据 (${response.raw.length} 字符)`);
100 -
101 - // 尝试解析为 JSON
102 - try {
103 - const json = JSON.parse(response.raw);
104 - console.log(` - JSON 解析成功`);
105 - console.log(` - 顶层键: ${Object.keys(json).join(', ')}`);
106 -
107 - // 检查是否有接口数据
108 - if (json.paths) {
109 - const pathCount = Object.keys(json.paths).length;
110 - console.log(` - OpenAPI paths 数量: ${pathCount}`);
111 -
112 - if (pathCount > 0) {
113 - console.log(` ✓ 找到接口数据!`);
114 - // 保存到文件
115 - const outputPath = path.join(__dirname, `../test-openapi-${Date.now()}.json`);
116 - fs.writeFileSync(outputPath, JSON.stringify(json, null, 2), 'utf-8');
117 - console.log(` - 已保存到: ${outputPath}`);
118 - return; // 找到数据就退出
119 - }
120 - }
121 - } catch (err) {
122 - console.log(` - JSON 解析失败,可能是其他格式`);
123 - }
124 - } else if (response.data) {
125 - console.log(`✅ 响应包含 data 字段`);
126 - console.log(` - data 键: ${Object.keys(response.data).join(', ')}`);
127 - } else {
128 - console.log(`⚠️ 响应为空`);
129 - }
130 -
131 - } catch (err) {
132 - console.log(`❌ 请求失败: ${err.message}`);
133 - }
134 - }
135 -
136 - console.log('\n' + '='.repeat(60));
137 -}
138 -
139 -main();
1 +import { fn, fetch } from '@/api/fn';
2 +
3 +const Api = {
4 + WeekHot: '/srv/?a=file&t=week_hot',
5 +}
6 +
7 +/**
8 + * @description 本周热门资料
9 + * @remark
10 + * @param {Object} params 请求参数
11 + * @param {string} params.page 页码,从0开始
12 + * @param {string} params.limit 每页数量
13 + * @returns {Promise<{
14 + * code: number; // 状态码
15 + * msg: string; // 消息
16 + * data: {
17 + * list: Array<{
18 + * meta_id: integer; // 文件ID
19 + * name: string; // 文件名称
20 + * src: string; // 文件URL
21 + * size: string; // 文件大小
22 + * read_people_count: integer; // 学习人数
23 + * read_people_percent: number; // 学习人数比例
24 + * }>;
25 + * };
26 + * }>}
27 + */
28 +export const weekHotAPI = (params) => fn(fetch.get(Api.WeekHot, params));
1 +import { fn, fetch } from '@/api/fn';
2 +
3 +const Api = {
4 + FileList: '/srv/?a=get_file_list&t=file_list',
5 +}
6 +
7 +/**
8 + * @description 文档列表
9 + * @remark
10 + * @param {Object} params 请求参数
11 + * @param {string} params.client_id (可选) 主体id
12 + * @param {string} params.limit (可选)
13 + * @param {string} params.page (可选)
14 + * @param {string} params.cid (可选) 分类id
15 + * @returns {Promise<{
16 + * code: number; // 状态码
17 + * msg: string; // 消息
18 + * data: {
19 + * categories: Array<{
20 + * id: integer; // 分类id
21 + * name: string; // 分类名
22 + * }>;
23 + * list: Array<{
24 + * id: integer; // 产品id
25 + * product_name: string; // 产品名
26 + * recommend: string; // 推荐位: normal-普通, hot-热卖
27 + * form_sn: string; //
28 + * created_time: string; // 创建时间
29 + * categories: array; // 产品所属分类
30 + * tags: array; // 产品标签
31 + * }>;
32 + * total: integer; // 产品总数
33 + * };
34 + * }>}
35 + */
36 +export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));