hookehuyr

fix(apifox): 改进 HTTPS 请求处理和数据验证

- 使用 Buffer.concat() 正确处理二进制数据流
- 添加空响应处理,避免解析错误
- 移除 UTF-8 BOM 标记(\uFEFF)
- 使用 Array.isArray() 验证响应数据类型
- 统一改进 apifox-sync.js、apifox-to-openapi.js、test-apifox-connection.js

提升 Apifox API 集成的稳定性和容错能力

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -50,6 +50,25 @@
---
## [2026-02-02] - 修复 Apifox 响应解析
### 修复
- 修复 Apifox 空响应导致 JSON 解析失败的问题
- 空响应时按空数据处理并返回可识别状态
- 接口列表解析增加空响应提示
- 影响文件:scripts/test-apifox-connection.js, scripts/apifox-sync.js, scripts/apifox-to-openapi.js
---
**详细信息**
- **影响文件**: scripts/test-apifox-connection.js, scripts/apifox-sync.js, scripts/apifox-to-openapi.js
- **技术栈**: Node.js
- **测试状态**: ✅ 已通过(pnpm api:test, pnpm lint;lint 有现存警告)
- **备注**:
- 接口列表为空时不会中断连接测试
---
## [2026-02-02] - 修复 Apifox Token 验证
### 修复
......
......@@ -78,19 +78,31 @@ function loadEnv() {
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
const chunks = [];
res.on('data', chunk => {
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(data);
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
......@@ -119,7 +131,7 @@ async function fetchApis() {
try {
const response = await httpsRequest(options);
const apis = response.data || [];
const apis = Array.isArray(response.data) ? response.data : [];
log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
return apis;
......
......@@ -84,19 +84,31 @@ function loadEnv() {
function httpsRequest(options) {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
const chunks = [];
res.on('data', chunk => {
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(data);
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
......@@ -125,7 +137,7 @@ async function fetchApis() {
try {
const response = await httpsRequest(options);
const apis = response.data || [];
const apis = Array.isArray(response.data) ? response.data : [];
log(`✅ 成功获取 ${apis.length} 个接口`, 'green');
return apis;
......
......@@ -100,19 +100,31 @@ function httpsRequest(options) {
log(`📡 发送请求到: https://${options.hostname}${options.path}`, 'blue');
const req = https.request(options, (res) => {
let data = '';
const chunks = [];
res.on('data', chunk => {
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(data);
const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
if (res.statusCode === 200) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.message || data}`));
reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
}
} catch (err) {
reject(new Error(`解析响应失败: ${err.message}`));
......@@ -141,6 +153,10 @@ async function testProjectInfo(config) {
try {
const response = await httpsRequest(options);
if (!response || !response.data) {
throw new Error('响应为空或缺少 data 字段');
}
const project = response.data;
log('✅ 项目信息获取成功', 'green');
......@@ -186,7 +202,7 @@ async function testApiList(config) {
try {
const response = await httpsRequest(options);
const apis = response.data || [];
const apis = Array.isArray(response.data) ? response.data : [];
log('✅ 接口列表获取成功', 'green');
log(` 共找到 ${response.total || apis.length} 个接口`, 'blue');
......@@ -205,6 +221,8 @@ async function testApiList(config) {
if (response.total > 10) {
log(`\n ... 还有 ${response.total - 10} 个接口`, 'yellow');
}
} else if (response.__empty) {
log('⚠️ 接口列表响应为空,已按空列表处理', 'yellow');
}
return true;
......