/** * 统一任务执行器 * * 功能: * - 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} 任务是否成功 */ 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>} 任务名 → 是否成功 */ 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, };