This commit is contained in:
2026-01-06 23:45:28 +08:00
parent e466f59b84
commit f7b1e9433c
4 changed files with 392 additions and 83 deletions

326
index.js
View File

@@ -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 => {