101 lines
2.8 KiB
JavaScript
101 lines
2.8 KiB
JavaScript
/**
|
||
* 统一任务执行器
|
||
*
|
||
* 功能:
|
||
* - try/catch 包裹每个任务
|
||
* - 自动记录开始/结束/失败日志
|
||
* - 单任务失败不影响其他任务
|
||
*/
|
||
|
||
const logger = require('./logger');
|
||
const config = require('../config');
|
||
const { safeGoto } = require('./page');
|
||
|
||
function withTimeout(promise, timeoutMs, taskName) {
|
||
let timer = null;
|
||
const timeout = new Promise((_, reject) => {
|
||
timer = setTimeout(() => {
|
||
reject(new Error(`${taskName} 单任务超时 ${timeoutMs}ms`));
|
||
}, timeoutMs);
|
||
});
|
||
|
||
return Promise.race([promise, timeout]).finally(() => {
|
||
if (timer) clearTimeout(timer);
|
||
});
|
||
}
|
||
|
||
async function prepareRetry(page, taskName, attempt) {
|
||
logger.info(` → ${taskName} 准备第${attempt}次重试,回到积分中心...`);
|
||
await safeGoto(page, config.urls.points, {
|
||
label: '积分中心',
|
||
timeout: config.navigationTimeout,
|
||
waitFor: ['.g-user-avatar', '.novice-task-item-box'],
|
||
}).catch((err) => {
|
||
logger.warn(` → 重试前刷新积分中心失败: ${err.message}`);
|
||
});
|
||
await page.waitForTimeout(1000).catch(() => {});
|
||
}
|
||
|
||
/**
|
||
* 执行任务(带防护)
|
||
*
|
||
* @param {string} taskName - 任务显示名称(如 "每日Star")
|
||
* @param {Function} handler - 异步任务函数 async (page) => { ... }
|
||
* @param {import('playwright').Page} page - 当前页面
|
||
* @returns {Promise<boolean>} 任务是否成功
|
||
*/
|
||
async function runTask(taskName, handler, page) {
|
||
logger.divider();
|
||
logger.info(`开始执行任务: ${taskName}`);
|
||
|
||
const maxAttempts = Math.max(1, Number(config.retryCount) || 1);
|
||
let lastError = null;
|
||
|
||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||
if (attempt > 1) {
|
||
await prepareRetry(page, taskName, attempt);
|
||
}
|
||
|
||
try {
|
||
logger.info(` → ${taskName} 第${attempt}/${maxAttempts}次尝试`);
|
||
await withTimeout(handler(page), config.taskTimeout, taskName);
|
||
logger.info(`✅ ${taskName} 执行成功`);
|
||
return true;
|
||
} catch (err) {
|
||
lastError = err;
|
||
if (attempt < maxAttempts) {
|
||
logger.warn(`⚠️ ${taskName} 第${attempt}/${maxAttempts}次失败: ${err.message}`);
|
||
} else {
|
||
logger.error(`❌ ${taskName} 执行失败: ${err.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (lastError && lastError.stack) {
|
||
logger.warn(lastError.stack);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 批量执行任务列表
|
||
*
|
||
* @param {import('playwright').Page} page - 主页面
|
||
* @param {Array<{name: string, handler: Function}>} tasks - 任务列表
|
||
* @returns {Promise<Object<string, boolean>>} 任务名 → 是否成功
|
||
*/
|
||
async function runTasks(page, tasks) {
|
||
const results = {};
|
||
|
||
for (const { name, handler } of tasks) {
|
||
results[name] = await runTask(name, handler, page);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
module.exports = {
|
||
runTask,
|
||
runTasks,
|
||
};
|