/** * 浏览器管理模块 * 负责:启动浏览器、创建上下文、加载/保存登录状态 */ const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); const config = require('../config'); const logger = require('./logger'); /** * 启动浏览器实例 * @returns {Promise} */ async function launchBrowser() { const headless = config.browser.headless; logger.info(`启动浏览器... (headless: ${headless})`); logger.info(' → 调用 chromium.launch()...'); const browser = await chromium.launch({ headless, }); logger.info('✅ 浏览器启动成功'); return browser; } /** * 创建浏览器上下文 * - 如果 state.json 存在且有效,加载登录状态 * - 否则创建干净的上下文 * @param {import('playwright').Browser} browser * @returns {Promise<{context: import('playwright').BrowserContext, stateLoaded: boolean}>} */ async function createContext(browser) { let stateLoaded = false; const statePath = path.resolve(config.statePath); logger.info('检查 state.json...'); logger.info(` → 路径: ${statePath}`); if (fs.existsSync(statePath)) { logger.info(' → state.json 文件存在,开始验证内容...'); try { const raw = fs.readFileSync(statePath, 'utf-8'); logger.info(` → 文件大小: ${raw.length} 字节`); const state = JSON.parse(raw); const hasCookies = Array.isArray(state.cookies) && state.cookies.length > 0; const hasOrigins = Array.isArray(state.origins) && state.origins.length > 0; logger.info(` → cookies: ${hasCookies ? state.cookies.length + '条' : '空'}`); logger.info(` → origins: ${hasOrigins ? state.origins.length + '条' : '空'}`); if (!hasCookies && !hasOrigins) { logger.warn(' → state.json 内容无效(空 cookies 和 origins),将重新登录'); stateLoaded = false; } else { logger.info(' → state.json 有效,将加载登录状态'); stateLoaded = true; } } catch (err) { logger.warn(` → state.json 解析失败: ${err.message},将重新登录`); stateLoaded = false; } } else { logger.info(' → state.json 不存在,将创建新上下文'); stateLoaded = false; } const contextOptions = { locale: config.browser.locale, }; if (stateLoaded) { contextOptions.storageState = statePath; } logger.info('正在创建浏览器上下文...'); const context = await browser.newContext(contextOptions); logger.info(stateLoaded ? '✅ 上下文已创建(已加载登录状态)' : '✅ 上下文已创建(未登录)'); return { context, stateLoaded }; } /** * 保存登录状态到 state.json * @param {import('playwright').BrowserContext} context */ async function saveState(context) { const statePath = path.resolve(config.statePath); logger.info(' → 正在保存 state.json...'); await context.storageState({ path: statePath }); logger.info(` → state.json 已保存到 ${statePath}`); } module.exports = { launchBrowser, createContext, saveState, };