Electron + Playwright 一文多发应用架构设计

文章目录

一次登录,长期运行。解决各种验证码破解的痛点。

一、整体架构概览

1. 架构分层

应用采用经典的 Electron 分层架构,结合 Playwright 自动化能力,实现主进程与渲染进程的职责分离:

层级 核心职责 技术栈
渲染进程(UI层) 用户交互、多Tab站点界面展示、文章编辑、发布控制 React/Vue + CSS 框架
主进程(核心控制层) 窗口管理、Playwright 实例管理、IPC 通信 Electron 主进程 API
业务逻辑层 站点配置管理、登录状态管理、发布流程控制 Node.js + 自定义业务模块
自动化层 浏览器自动化、页面操作、数据抓取 Playwright
数据持久层 登录状态持久化、站点配置存储、文章草稿存储 SQLite/JSON 文件

2. 核心模块关系

IPC 通信 管理 创建 对应 调用 持久化 执行 调用 操作 渲染进程 UI 主进程 Playwright 实例池 站点 BrowserContext 站点 Tab 窗口 登录状态管理 本地存储 发布流程引擎 站点发布脚本

二、核心模块设计

1. Playwright 实例管理模块

核心功能
  • 管理 Playwright 引擎的启动与关闭
  • 为每个站点创建独立的 BrowserContext(实现登录状态隔离)
  • 维护 BrowserContext 池,复用资源,提高性能
关键设计
  • 实例池机制:限制最大同时运行的 BrowserContext 数量,避免资源占用过高
  • Context 隔离:每个站点对应独立的 BrowserContext,确保 cookies、localStorage 互不干扰
  • 自动恢复:启动时从本地存储加载站点登录状态,自动恢复 Context
javascript 复制代码
// 核心代码示例(主进程)
class PlaywrightManager {
  constructor() {
    this.playwright = null;
    this.browser = null;
    this.contextPool = new Map(); // key: 站点ID, value: BrowserContext
  }
  
  async init() {
    this.playwright = await require('playwright').chromium.launch({
      headless: false, // 非无头模式,便于用户查看登录过程
      args: ['--no-sandbox'] // Electron 环境下需要的参数
    });
  }
  
  async createContext(siteId) {
    // 检查是否已存在 Context,存在则复用
    if (this.contextPool.has(siteId)) {
      return this.contextPool.get(siteId);
    }
    
    // 从本地加载站点登录状态
    const savedState = await this.loadSiteState(siteId);
    const context = await this.browser.newContext({
      storageState: savedState || undefined,
      viewport: { width: 1280, height: 720 }
    });
    
    this.contextPool.set(siteId, context);
    return context;
  }
  
  async saveSiteState(siteId) {
    const context = this.contextPool.get(siteId);
    if (context) {
      await context.storageState({ path: `./states/${siteId}.json` });
    }
  }
}

2. 多 Tab 窗口管理模块

核心功能
  • 在 Electron 应用内创建多个 Tab 窗口,每个 Tab 对应一个站点
  • 实现 Tab 切换、关闭、新增等基本操作
  • 将 Playwright 页面的渲染内容实时显示到对应 Tab 中
关键设计
  • BrowserView 技术 :使用 Electron 的 BrowserView 组件作为 Tab 容器,嵌入 Playwright 页面
  • View 与 Context 绑定:每个 BrowserView 关联一个 Playwright BrowserContext 的页面
  • 响应式布局:支持 Tab 数量变化时的自适应布局调整
javascript 复制代码
// 核心代码示例(主进程)
class TabManager {
  constructor(mainWindow) {
    this.mainWindow = mainWindow;
    this.views = new Map(); // key: tabId, value: BrowserView
    this.currentTabId = null;
  }
  
  async createTab(siteId, siteName) {
    // 创建 BrowserView
    const view = new BrowserView({
      webPreferences: {
        nodeIntegration: false,
        contextIsolation: true
      }
    });
    
    // 获取对应站点的 Playwright 页面
    const context = await playwrightManager.createContext(siteId);
    const page = await context.newPage();
    
    // 将 Playwright 页面的 URL 加载到 BrowserView
    await page.goto(siteConfig[siteId].loginUrl);
    const pageUrl = await page.url();
    view.webContents.loadURL(pageUrl);
    
    // 添加到主窗口
    this.mainWindow.addBrowserView(view);
    this.views.set(tabId, { view, siteId, siteName });
    
    // 切换到新 Tab
    this.switchTab(tabId);
  }
  
  switchTab(tabId) {
    const { view } = this.views.get(tabId);
    // 调整 BrowserView 大小,使其充满主窗口
    const [width, height] = this.mainWindow.getSize();
    view.setBounds({ x: 0, y: 60, width, height: height - 60 });
    this.mainWindow.setTopBrowserView(view);
    this.currentTabId = tabId;
  }
}

3. 登录状态管理模块

核心功能
  • 记录每个站点的登录状态(已登录/未登录)
  • 持久化存储登录凭证(cookies、localStorage)
  • 提供登录状态查询和验证接口
关键设计
  • 状态持久化:使用 SQLite 数据库存储站点登录状态元数据,Playwright 上下文状态存储为 JSON 文件
  • 登录验证机制:定期检查站点页面是否处于登录状态,自动更新状态
  • 手动登录支持:允许用户在应用内手动完成登录操作,登录后自动保存状态
javascript 复制代码
// 核心代码示例
class LoginStateManager {
  constructor() {
    this.db = new sqlite3.Database('./data.db');
    this.initDatabase();
  }
  
  initDatabase() {
    this.db.run(`
      CREATE TABLE IF NOT EXISTS site_login_states (
        site_id TEXT PRIMARY KEY,
        is_logged_in INTEGER DEFAULT 0,
        last_login_time DATETIME,
        state_file_path TEXT
      )
    `);
  }
  
  async checkLoginStatus(siteId) {
    const context = await playwrightManager.getContext(siteId);
    const page = await context.newPage();
    await page.goto(siteConfig[siteId].homeUrl);
    
    // 根据站点特性判断是否已登录(如检查页面是否包含用户名元素)
    const isLoggedIn = await page.$(siteConfig[siteId].loginIndicator) !== null;
    
    // 更新数据库状态
    this.db.run(
      'UPDATE site_login_states SET is_logged_in = ?, last_login_time = ? WHERE site_id = ?',
      [isLoggedIn ? 1 : 0, new Date().toISOString(), siteId]
    );
    
    return isLoggedIn;
  }
}

4. 发布流程引擎

核心功能
  • 管理多站点发布任务队列
  • 执行站点发布脚本
  • 处理发布结果,提供反馈
关键设计
  • 插件化设计:每个站点的发布逻辑封装为独立插件,便于扩展
  • 任务队列:支持并发发布,可配置最大并发数
  • 发布结果反馈:实时向渲染进程推送发布进度和结果
javascript 复制代码
// 核心代码示例
class PublishEngine {
  constructor() {
    this.taskQueue = [];
    this.maxConcurrent = 3;
    this.runningTasks = 0;
  }
  
  addTask(article, siteIds) {
    siteIds.forEach(siteId => {
      this.taskQueue.push({ article, siteId });
    });
    this.processQueue();
  }
  
  async processQueue() {
    while (this.taskQueue.length > 0 && this.runningTasks < this.maxConcurrent) {
      const task = this.taskQueue.shift();
      this.runningTasks++;
      this.executeTask(task).finally(() => {
        this.runningTasks--;
        this.processQueue();
      });
    }
  }
  
  async executeTask(task) {
    const { article, siteId } = task;
    try {
      // 加载站点发布插件
      const publishPlugin = require(`./plugins/${siteId}.js`);
      // 获取对应站点的 Playwright 页面
      const context = await playwrightManager.getContext(siteId);
      const page = await context.newPage();
      // 执行发布
      await publishPlugin.publish(page, article);
      // 发送发布成功事件
      ipcMain.emit('publish-success', { siteId, articleId: article.id });
    } catch (error) {
      // 发送发布失败事件
      ipcMain.emit('publish-fail', { siteId, articleId: article.id, error: error.message });
    }
  }
}

5. 站点配置与插件系统

核心功能
  • 管理各自媒体站点的配置信息(登录 URL、发布 URL、选择器等)
  • 提供插件接口,支持新增站点
关键设计
  • 配置文件驱动:站点配置存储为 JSON 文件,便于修改和扩展
  • 插件接口标准化 :定义统一的插件接口,要求每个插件实现 publish 方法
  • 热插拔支持:支持在应用运行时加载新插件
json 复制代码
// 站点配置示例(sites/config.json)
{
  "zhihu": {
    "name": "知乎",
    "loginUrl": "https://www.zhihu.com/signin",
    "homeUrl": "https://www.zhihu.com",
    "loginIndicator": ".ProfileCard-name",
    "publishUrl": "https://zhuanlan.zhihu.com/write"
  },
  "juejin": {
    "name": "掘金",
    "loginUrl": "https://juejin.cn/login",
    "homeUrl": "https://juejin.cn",
    "loginIndicator": ".username",
    "publishUrl": "https://juejin.cn/new-entry"
  }
}
javascript 复制代码
// 站点发布插件示例(plugins/zhihu.js)
module.exports = {
  async publish(page, article) {
    // 导航到发布页面
    await page.goto('https://zhuanlan.zhihu.com/write');
    
    // 填写标题
    await page.fill('input[placeholder="请输入标题"]', article.title);
    
    // 填写内容(假设使用富文本编辑器)
    await page.fill('.ProseMirror', article.content);
    
    // 选择分类
    await page.click('.category-selector');
    await page.click(`.category-item:has-text("${article.category}")`);
    
    // 发布
    await page.click('button:has-text("发布")');
    
    // 等待发布完成
    await page.waitForSelector('.publish-success', { timeout: 30000 });
  }
};

三、UI 设计方案

1. 主界面布局

复制代码
+-------------------------------------------------+
| 应用标题栏 | 文章编辑 | 发布控制 | 设置          |
+-------------------------------------------------+
| 标签栏:[知乎] [掘金] [CSDN] [+]                |
+-------------------------------------------------+
|                                                 |
|                站点页面内容                      |
|                                                 |
|                                                 |
+-------------------------------------------------+
| 状态栏:发布进度 | 当前站点 | 登录状态          |
+-------------------------------------------------+

2. 核心界面说明

  • 标签栏:显示已打开的站点 Tab,支持切换、关闭和新增
  • 站点页面内容区:显示当前选中站点的 Playwright 页面内容,用户可在此完成登录操作
  • 文章编辑区:支持 Markdown 编辑,提供预览功能
  • 发布控制区:选择要发布的站点,执行发布操作,显示发布结果

四、数据存储设计

1. 存储方案

  • 站点配置 :JSON 文件(sites/config.json
  • 登录状态
    • 状态元数据:SQLite 数据库(data.db
    • Playwright 上下文状态:JSON 文件(states/{siteId}.json
  • 文章草稿 :Markdown 文件(drafts/{articleId}.md
  • 发布历史 :SQLite 数据库(data.db

2. 数据库表结构

sql 复制代码
-- 站点登录状态表
CREATE TABLE site_login_states (
  site_id TEXT PRIMARY KEY,
  is_logged_in INTEGER DEFAULT 0,
  last_login_time DATETIME,
  state_file_path TEXT
);

-- 发布历史表
CREATE TABLE publish_history (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  article_id TEXT,
  article_title TEXT,
  site_id TEXT,
  site_name TEXT,
  publish_time DATETIME,
  status TEXT, -- success/fail
  error_message TEXT
);

五、核心流程设计

1. 应用启动流程

启动 Electron 应用 初始化 Playwright 引擎 加载站点配置 初始化数据库 创建主窗口 加载已保存的登录状态 显示主界面

2. 站点登录流程

已登录 未登录 用户点击新增站点 Tab 创建 BrowserView 和 Playwright Context 加载站点登录页面 用户手动完成登录 检测登录状态 保存登录状态到本地 更新站点登录状态

3. 文章发布流程

未登录 已登录 用户编辑文章 选择要发布的站点 检查所选站点登录状态 提示用户登录 添加发布任务到队列 发布引擎处理任务 执行站点发布脚本 更新发布状态 显示发布结果

六、技术难点及解决方案

1. Playwright 在 Electron 中的集成

  • 问题:Electron 环境下 Playwright 可能无法正常启动
  • 解决方案
    • 使用 Playwright 的 chromium 浏览器,而非 Electron 内置浏览器
    • 添加必要的启动参数:--no-sandbox--disable-setuid-sandbox
    • 确保 Playwright 版本与 Electron 兼容

2. 多站点登录状态隔离

  • 问题:不同站点的登录状态可能相互干扰
  • 解决方案:为每个站点创建独立的 Playwright BrowserContext,实现完全隔离

3. 应用内显示多个浏览器界面

  • 问题:如何在 Electron 应用内同时显示多个站点的浏览器页面
  • 解决方案:使用 Electron 的 BrowserView 组件,每个组件关联一个 Playwright 页面

4. 站点发布脚本的扩展性

  • 问题:如何方便地添加新的自媒体站点支持
  • 解决方案:采用插件化设计,定义统一的插件接口,新增站点只需编写对应插件

七、扩展与优化建议

  1. 支持无头模式:在用户完成登录后,可切换到无头模式运行,减少资源占用
  2. 发布模板支持:为不同站点提供发布模板,自动适配站点格式要求
  3. 定时发布功能:支持设置发布时间,定时自动发布文章
  4. 发布结果统计:提供发布成功率、耗时等统计数据
  5. 自动更新机制:支持应用和插件的自动更新
  6. 多账号支持:允许同一站点配置多个账号,实现多账号发布

八、总结

本架构设计基于 Electron + Playwright 技术栈,实现了一个功能完整、架构清晰的一文多发应用。核心特点包括:

  1. 多 Tab 设计:在应用内实现多个站点的同时管理,无需打开外部浏览器
  2. 登录状态持久化:每个站点仅需登录一次,下次启动自动恢复
  3. 插件化架构:便于扩展新的自媒体站点支持
  4. 良好的用户体验:直观的界面设计,清晰的发布流程
  5. 高性能:通过 BrowserContext 池和任务队列机制,优化资源使用

该架构设计兼顾了功能完整性、扩展性和性能,能够满足用户一文多发的核心需求,同时为后续功能扩展提供了良好的基础。

相关推荐
想要成为糕糕手1 小时前
深入理解 JavaScript 中的 “this”:从自由变量到绑定规则
前端·javascript
咖猫1 小时前
guacamole-web 1.5.5 index.html
前端·javascript·html
getapi1 小时前
Express 是一个基于 Node.js 的轻量级、灵活的 Web 应用框架,广泛用于构建后端服务和 API
前端·node.js·express
渣波1 小时前
🧳 我的 React Trip 之旅(5):我的 AI 聊天机器人,今天又把用户气笑了
前端·javascript
boombb1 小时前
数据驱动与CSS预定义样式:实现灵活多变的Banner布局
前端
JIngJaneIL1 小时前
基于Java失物招领系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·vue
鼎道开发者联盟1 小时前
当界面会思考:AIGUI八要素驱动DingOS实现“感知-生成-进化“闭环
前端·人工智能·ai·gui
豐儀麟阁贵1 小时前
9.3获取字符串信息
java·开发语言·前端·算法
苦夏木禾1 小时前
使用css制作一个环形进度展示圈
前端·css