From f7b1e9433c070c26e52cd51728ba3b9e9706562a Mon Sep 17 00:00:00 2001 From: jks703 <409745752@qq.com> Date: Tue, 6 Jan 2026 23:45:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 + TROUBLESHOOTING.md | 139 +++++++++++++++++++ config.example.env | 8 +- index.js | 326 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 392 insertions(+), 83 deletions(-) create mode 100644 TROUBLESHOOTING.md diff --git a/.env b/.env index cfac5d6..9bc4414 100644 --- a/.env +++ b/.env @@ -1,5 +1,7 @@ # 速创API配置 API_KEY=G9rXx3Ag2Xfa7Gs8zou6t6HqeZ +#NlJmFJ3af3EsA1g6DhgBAJgCaN + API_BASE_URL=https://api.wuyinkeji.com # 生图接口 diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..fd96c22 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,139 @@ +# 故障排查指南 + +## 查询超时问题诊断 + +如果遇到"查询超时,已查询XX次"的问题,请按以下步骤排查: + +### 1. 检查任务ID是否正确 + +**症状**:查询时返回404错误或任务未找到 + +**解决方法**: +- 查看控制台输出的"完整响应数据",确认任务ID是否正确获取 +- 检查任务ID的类型和格式(应该是数字) +- 确认生图接口返回的响应格式是否符合预期 + +**调试命令**: +```bash +# 运行时会输出完整的API响应,检查任务ID字段 +npm start +``` + +### 2. 检查API响应格式 + +**症状**:查询返回的数据格式不符合预期 + +**解决方法**: +- 查看控制台输出的"完整API响应" +- 确认 `data.code` 是否为 200 +- 确认 `data.data.status` 是否存在 +- 确认 `data.data.image_url` 字段名称是否正确 + +**常见问题**: +- API响应格式可能已更新,需要调整代码中的字段名 +- 某些情况下 `image_url` 可能是 `imageUrl` 或其他名称 + +### 3. 检查图片生成状态 + +**症状**:状态一直显示"排队中"或"生成中",从未变为"成功" + +**可能原因**: +1. **图片生成时间过长**:高分辨率(2K/4K)或复杂prompt需要更长时间 +2. **API服务繁忙**:排队任务较多 +3. **任务实际已失败**:但状态未更新为失败 + +**解决方法**: +- 增加 `MAX_QUERY_COUNT` 到 180 或更高 +- 增加 `QUERY_INTERVAL` 到 10000(10秒) +- 检查API服务状态 +- 尝试降低图片分辨率(使用1K而不是2K) + +### 4. 检查API密钥和权限 + +**症状**:返回401或403错误 + +**解决方法**: +- 确认 `.env` 文件中的 `API_KEY` 正确 +- 检查API密钥是否过期 +- 确认账户余额是否充足 +- 检查API密钥是否有查询接口的权限 + +### 5. 检查网络连接 + +**症状**:请求超时或网络错误 + +**解决方法**: +- 检查网络连接 +- 确认可以访问 `https://api.wuyinkeji.com` +- 检查防火墙设置 +- 尝试增加请求超时时间 + +### 6. 手动测试API + +**使用curl测试查询接口**: +```bash +# 替换 YOUR_API_KEY 和 TASK_ID +curl -X GET "https://api.wuyinkeji.com/api/img/drawDetail?id=TASK_ID" \ + -H "Authorization: YOUR_API_KEY" \ + -H "Content-Type: application/json;charset:utf-8;" +``` + +**使用curl测试生图接口**: +```bash +curl -X POST "https://api.wuyinkeji.com/api/img/nanoBanana-pro" \ + -H "Authorization: YOUR_API_KEY" \ + -H "Content-Type: application/json;charset:utf-8;" \ + -d '{ + "prompt": "test prompt", + "imageSize": "1K", + "aspectRatio": "1:1" + }' +``` + +### 7. 查看详细日志 + +代码已增强日志输出,运行时会显示: +- ✅ 任务提交时的完整响应 +- ✅ 每次查询的详细状态 +- ✅ 每10次查询的完整API响应 +- ✅ 所有错误信息 + +**关键日志位置**: +1. 任务提交后:查看"完整响应数据",确认任务ID +2. 查询过程中:查看"查询结果详情",确认状态值 +3. 错误时:查看"完整错误响应",了解具体错误原因 + +### 8. 常见错误码说明 + +| 错误码 | 说明 | 解决方法 | +|--------|------|----------| +| 200 | 成功 | - | +| 400 | 请求参数错误 | 检查请求参数格式 | +| 401 | 认证失败 | 检查API密钥 | +| 403 | 权限不足 | 检查API密钥权限 | +| 404 | 任务未找到 | 检查任务ID是否正确 | +| 500 | 服务器错误 | 稍后重试或联系API服务商 | + +### 9. 推荐配置 + +对于稳定的图片生成,建议使用以下配置: + +```env +# 查询间隔:10秒(减少API调用频率) +QUERY_INTERVAL=10000 + +# 最大查询次数:180次(对应30分钟) +MAX_QUERY_COUNT=180 + +# 图片尺寸:1K(生成速度更快,适合测试) +IMAGE_SIZE=1K +``` + +### 10. 联系支持 + +如果以上方法都无法解决问题: +1. 收集完整的日志输出 +2. 记录任务ID和错误信息 +3. 联系速创API技术支持 +4. 提供复现步骤和错误详情 + diff --git a/config.example.env b/config.example.env index 8f3c293..8455b91 100644 --- a/config.example.env +++ b/config.example.env @@ -16,9 +16,9 @@ SOURCE_IMAGE_URL=https://your-image-host.com/P1191464.JPG IMAGE_SIZE=2K ASPECT_RATIO=1:1 -# 查询间隔(毫秒) -QUERY_INTERVAL=5000 +# 查询间隔(毫秒)- 建议10秒,减少API调用频率 +QUERY_INTERVAL=10000 -# 最大查询次数 -MAX_QUERY_COUNT=60 +# 最大查询次数 - 建议120-180次(对应20-30分钟) +MAX_QUERY_COUNT=120 diff --git a/index.js b/index.js index ce873fc..8cb9fca 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,10 @@ const config = { apiKey: process.env.API_KEY, apiBaseUrl: process.env.API_BASE_URL || 'https://api.wuyinkeji.com', generateApi: process.env.GENERATE_API || '/api/img/nanoBanana-pro', - queryApi: process.env.QUERY_API || '/api/img/drawDetail', + // 强制使用正确的查询接口路径,避免配置错误 + queryApi: (process.env.QUERY_API && process.env.QUERY_API.includes('drawDetail')) + ? process.env.QUERY_API + : '/api/img/drawDetail', sourceImageUrl: process.env.SOURCE_IMAGE_URL, imageSize: process.env.IMAGE_SIZE || '2K', aspectRatio: process.env.ASPECT_RATIO || '1:1', @@ -17,6 +20,13 @@ const config = { outputDir: path.join(__dirname, 'img_2') }; +// 验证查询接口路径 +if (!config.queryApi.includes('drawDetail')) { + console.warn(`⚠️ 警告: 查询接口路径可能不正确: ${config.queryApi}`); + console.warn(` 已自动修正为: /api/img/drawDetail`); + config.queryApi = '/api/img/drawDetail'; +} + // 确保输出目录存在 if (!fs.existsSync(config.outputDir)) { fs.mkdirSync(config.outputDir, { recursive: true }); @@ -90,11 +100,33 @@ async function generateImage(prompt, imageConfig) { }); if (response.data && response.data.code === 200) { - const taskId = response.data.data?.id || response.data.data?.taskId || response.data.taskId; - console.log(`✅ 任务提交成功,任务ID: ${taskId}`); - return { success: true, taskId, name: prompt.name }; + // 根据API文档,查询接口需要的是 data.id(图片ID),不是 task_id + // 优先使用 data.id,这是查询接口需要的ID + const taskId = response.data.data?.id || + response.data.data?.taskId || + response.data.taskId || + response.data.id; + + // 如果获取到的是 task_id(字符串格式),需要找到对应的 id + // 因为查询接口需要的是数字 id,而不是 task_id + if (!taskId) { + console.error(`❌ 无法获取任务ID,完整响应:`, JSON.stringify(response.data, null, 2)); + return { success: false, error: '无法获取任务ID' }; + } + + console.log(`✅ 任务提交成功`); + console.log(`📋 完整响应数据:`, JSON.stringify(response.data, null, 2)); + console.log(`📝 任务ID (用于查询): ${taskId} (类型: ${typeof taskId})`); + if (response.data.data?.task_id) { + console.log(`📝 任务task_id: ${response.data.data.task_id}`); + } + console.log(`⚠️ 注意: 查询接口需要使用 data.id (${response.data.data?.id || '未找到'}),而不是 task_id`); + + // 确保使用 data.id 作为查询ID + const queryId = response.data.data?.id || taskId; + return { success: true, taskId: queryId, name: prompt.name }; } else { - console.error(`❌ 任务提交失败:`, response.data); + console.error(`❌ 任务提交失败:`, JSON.stringify(response.data, null, 2)); return { success: false, error: response.data }; } } catch (error) { @@ -111,85 +143,218 @@ async function generateImage(prompt, imageConfig) { */ async function queryImageResult(taskId, imageName) { let queryCount = 0; + let queryInterval = null; - return new Promise((resolve, reject) => { - const queryInterval = setInterval(async () => { - queryCount++; - - if (queryCount > config.maxQueryCount) { + // 执行单次查询的函数 + const performQuery = async () => { + queryCount++; + + if (queryCount > config.maxQueryCount) { + if (queryInterval) { clearInterval(queryInterval); - reject(new Error(`查询超时,已查询${queryCount}次`)); + } + throw new Error(`查询超时,已查询${queryCount}次`); + } + + try { + // 根据API文档: https://api.wuyinkeji.com/doc/9 + // 接口地址: https://api.wuyinkeji.com/api/img/drawDetail + // 请求方式: GET + // 请求参数: id (必填, int类型) - 图片ID(注意:是 data.id,不是 task_id) + // 返回格式: data.status (0:排队中,1:生成中,2:成功,3:失败) + // data.image_url (生成的图片地址) + + // 确保查询接口路径正确 + const queryApiPath = config.queryApi || '/api/img/drawDetail'; + if (!queryApiPath.includes('drawDetail')) { + console.error(`❌ 警告: 查询接口路径可能不正确: ${queryApiPath}`); + console.error(` 正确的路径应该是: /api/img/drawDetail`); + } + + // 确保ID是数字类型(查询接口要求int类型) + const queryId = typeof taskId === 'string' && /^\d+$/.test(taskId) + ? parseInt(taskId) + : taskId; + + const queryUrl = `${config.apiBaseUrl}${queryApiPath}?id=${queryId}`; + + console.log(`[${queryCount}/${config.maxQueryCount}] 查询任务状态: ID=${queryId} (原始: ${taskId}, 类型: ${typeof queryId})`); + console.log(`🔗 查询URL: ${queryUrl}`); + if (queryCount === 1) { + console.log(`🔑 使用API密钥: ${config.apiKey ? config.apiKey.substring(0, 10) + '...' : '未设置'}`); + } + + const response = await axios.get(queryUrl, { + headers: { + 'Content-Type': 'application/json;charset:utf-8;', + 'Authorization': config.apiKey + }, + timeout: 10000 + }); + + const data = response.data; + + // 首次查询或每10次查询打印完整响应(用于调试) + if (queryCount === 1 || queryCount % 10 === 0) { + console.log(`📋 完整API响应 (第${queryCount}次查询):`, JSON.stringify(data, null, 2)); + } + + // 根据API文档响应格式: + // code: 状态码 + // msg: 状态信息 + // data.status: 0:排队中,1:生成中,2:成功,3:失败 + // data.image_url: 生成的图片地址 + if (data.code === 200 && data.data) { + // 处理 status 可能是数字或字符串的情况 + const status = parseInt(data.data.status); + const imageUrl = data.data.image_url; + + // 添加详细日志 + const statusText = status === 0 ? '排队中' : status === 1 ? '生成中' : status === 2 ? '成功' : status === 3 ? '失败' : `未知(${status})`; + console.log(`📊 查询结果详情:`, { + status: status, + statusType: typeof data.data.status, + statusText: statusText, + hasImageUrl: !!imageUrl, + imageUrl: imageUrl ? (imageUrl.length > 100 ? imageUrl.substring(0, 100) + '...' : imageUrl) : '无', + imageUrlLength: imageUrl ? imageUrl.length : 0, + prompt: data.data.prompt ? (data.data.prompt.substring(0, 50) + '...') : '无', + createdAt: data.data.created_at || '无', + updatedAt: data.data.updated_at || '无' + }); + + // status: 2 表示成功(使用宽松比较,支持数字2和字符串"2") + if (status === 2 || data.data.status === '2' || data.data.status === 2) { + // 检查 imageUrl 是否有效(不能为空字符串、null、undefined) + const validImageUrl = imageUrl && typeof imageUrl === 'string' && imageUrl.trim().length > 0; + + if (validImageUrl) { + if (queryInterval) { + clearInterval(queryInterval); + } + const finalImageUrl = imageUrl.trim(); + console.log(`✅ 图片生成成功: ${imageName}`); + console.log(`图片URL: ${finalImageUrl}`); + console.log(`图片URL长度: ${finalImageUrl.length}`); + return { imageUrl: finalImageUrl, taskId, imageName, success: true }; + } else { + console.log(`⚠️ 状态为成功但URL无效,继续等待...`); + console.log(` imageUrl值: ${JSON.stringify(imageUrl)}`); + console.log(` imageUrl类型: ${typeof imageUrl}`); + // 如果状态是2但URL无效,继续等待 + return { success: false, continue: true }; + } + } + // status: 3 表示失败 + else if (status === 3 || data.data.status === '3') { + if (queryInterval) { + clearInterval(queryInterval); + } + const errorMsg = data.msg || data.data.msg || '未知错误'; + console.error(`❌ 图片生成失败: ${errorMsg}`); + throw new Error(`图片生成失败: ${errorMsg}`); + } + // status: 0 排队中, 1 生成中 + else { + console.log(`⏳ ${statusText}... (已等待 ${Math.round(queryCount * config.queryInterval / 1000)} 秒)`); + return { success: false, continue: true }; + } + } else if (data.code !== 200) { + // API返回错误 + console.error(`⚠️ 查询返回错误: ${data.msg || '未知错误'} (code: ${data.code})`); + console.error(`完整错误响应:`, JSON.stringify(data, null, 2)); + + // 如果是认证错误或参数错误,立即失败 + if (data.code === 401 || data.code === 403 || data.code === 400) { + if (queryInterval) { + clearInterval(queryInterval); + } + throw new Error(`API错误: ${data.msg} (code: ${data.code})`); + } + // 其他错误继续重试 + return { success: false, continue: true }; + } else { + // data为空或格式不对 + console.warn(`⚠️ 响应格式异常:`, JSON.stringify(data, null, 2)); + console.log(`⏳ 等待中... (响应: ${data.msg || '处理中'})`); + return { success: false, continue: true }; + } + } catch (error) { + if (error.response) { + // HTTP错误响应 + const statusCode = error.response.status; + const errorData = error.response.data; + + if (statusCode === 404) { + // 任务可能还在处理中,或者任务ID不存在 + console.error(`❌ 任务未找到(404): ID=${taskId} (类型: ${typeof taskId})`); + console.error(` 可能的原因:`); + console.error(` 1. 任务ID类型错误 - 查询接口需要数字ID,不是task_id字符串`); + console.error(` 2. 任务还在处理中,稍后再试`); + console.error(` 3. 任务ID不存在或已过期`); + console.error(` 提示: 请检查生图接口返回的 data.id 字段(应该是数字)`); + + // 如果是字符串类型的ID,尝试转换为数字 + if (typeof taskId === 'string' && /^\d+$/.test(taskId)) { + console.log(` 尝试将字符串ID转换为数字: ${parseInt(taskId)}`); + // 不立即失败,继续重试(可能任务还在处理) + } + } else if (statusCode === 401 || statusCode === 403) { + // 认证错误,立即失败 + if (queryInterval) { + clearInterval(queryInterval); + } + console.error(`❌ 认证失败 (${statusCode}):`, errorData); + throw new Error(`认证失败: ${errorData?.msg || error.message}`); + } else { + console.error(`❌ HTTP错误 (${statusCode}):`, errorData || error.message); + } + } else if (error.code === 'ECONNABORTED') { + console.error(`❌ 请求超时:`, error.message); + } else if (error.message && error.message.includes('查询超时')) { + // 这是我们的超时错误,直接抛出 + throw error; + } else { + console.error(`❌ 查询时出错:`, error.message); + if (error.stack) { + console.error(`错误堆栈:`, error.stack); + } + } + // 网络错误等继续重试,不立即失败 + return { success: false, continue: true }; + } + }; + + return new Promise(async (resolve, reject) => { + try { + // 立即执行第一次查询,不等待间隔 + const firstResult = await performQuery(); + if (firstResult.success) { + resolve(firstResult); return; } - try { - // 根据API文档: https://api.wuyinkeji.com/doc/9 - // 接口地址: https://api.wuyinkeji.com/api/img/drawDetail - // 请求方式: GET - // 请求参数: id (必填, int类型) - 图片ID - // 返回格式: data.status (0:排队中,1:生成中,2:成功,3:失败) - // data.image_url (生成的图片地址) - const queryUrl = `${config.apiBaseUrl}${config.queryApi}?id=${taskId}`; - - console.log(`[${queryCount}/${config.maxQueryCount}] 查询任务状态: ID=${taskId}`); - - const response = await axios.get(queryUrl, { - headers: { - 'Content-Type': 'application/json;charset:utf-8;', - 'Authorization': config.apiKey - }, - timeout: 10000 - }); - - const data = response.data; - - // 根据API文档响应格式: - // code: 状态码 - // msg: 状态信息 - // data.status: 0:排队中,1:生成中,2:成功,3:失败 - // data.image_url: 生成的图片地址 - if (data.code === 200 && data.data) { - const status = data.data.status; - const imageUrl = data.data.image_url; - - // status: 2 表示成功 - if (status === 2) { - if (imageUrl) { + // 如果第一次查询未成功,设置定时器继续查询 + queryInterval = setInterval(async () => { + try { + const result = await performQuery(); + if (result.success) { + if (queryInterval) { clearInterval(queryInterval); - console.log(`✅ 图片生成成功: ${imageName}`); - console.log(`图片URL: ${imageUrl}`); - resolve({ imageUrl, taskId, imageName }); - } else { - console.log(`⏳ 图片生成成功但URL未返回,继续等待...`); } - } - // status: 3 表示失败 - else if (status === 3) { - clearInterval(queryInterval); - reject(new Error(`图片生成失败: ${data.msg || data.data.msg || '未知错误'}`)); - } - // status: 0 排队中, 1 生成中 - else { - const statusText = status === 0 ? '排队中' : status === 1 ? '生成中' : `未知状态${status}`; - console.log(`⏳ ${statusText}...`); + resolve(result); } - } else if (data.code !== 200) { - // API返回错误 - console.log(`⚠️ 查询返回错误: ${data.msg || '未知错误'} (code: ${data.code})`); - // 继续重试,不立即失败 - } else { - console.log(`⏳ 等待中... (响应: ${data.msg || '处理中'})`); + // 如果 continue 为 true,继续下一次查询 + } catch (error) { + if (queryInterval) { + clearInterval(queryInterval); + } + reject(error); } - } catch (error) { - if (error.response && error.response.status === 404) { - // 任务可能还在处理中 - console.log(`⏳ 任务处理中,继续等待...`); - } else { - console.error(`查询时出错:`, error.message); - // 不立即失败,继续重试 - } - } - }, config.queryInterval); + }, config.queryInterval); + } catch (error) { + reject(error); + } }); } @@ -199,7 +364,7 @@ async function queryImageResult(taskId, imageName) { async function downloadAndSaveImage(imageUrl, imageName) { try { console.log(`\n开始下载图片: ${imageUrl}`); - + const response = await axios({ url: imageUrl, method: 'GET', @@ -272,10 +437,13 @@ async function main() { console.log('🚀 开始亚马逊产品图生成工作流...\n'); console.log('配置信息:'); console.log(`- API地址: ${config.apiBaseUrl}`); + console.log(`- 生图接口: ${config.generateApi}`); + console.log(`- 查询接口: ${config.queryApi}`); console.log(`- 源图片: ${config.sourceImageUrl || '使用本地图片'}`); console.log(`- 输出目录: ${config.outputDir}`); console.log(`- 图片尺寸: ${config.imageSize}`); - console.log(`- 查询间隔: ${config.queryInterval}ms\n`); + console.log(`- 查询间隔: ${config.queryInterval}ms`); + console.log(`- 最大查询次数: ${config.maxQueryCount}次\n`); if (!config.apiKey) { console.error('❌ 错误: 未设置API_KEY,请在.env文件中配置'); @@ -295,7 +463,7 @@ async function main() { for (const prompt of allPrompts) { const result = await processImageGeneration(prompt); results.push(result); - + // 在任务之间稍作延迟,避免请求过快 if (allPrompts.indexOf(prompt) < allPrompts.length - 1) { console.log('\n等待3秒后处理下一个任务...\n'); @@ -311,7 +479,7 @@ async function main() { const failCount = results.filter(r => !r.success).length; console.log(`✅ 成功: ${successCount}/${results.length}`); console.log(`❌ 失败: ${failCount}/${results.length}`); - + if (failCount > 0) { console.log('\n失败的任务:'); results.filter(r => !r.success).forEach(r => {