文章目录
-
- 一、整体架构概览
-
- [1. 架构分层](#1. 架构分层)
- [2. 核心模块关系](#2. 核心模块关系)
- 二、核心模块设计
- [三、UI 设计方案](#三、UI 设计方案)
-
- [1. 主界面布局](#1. 主界面布局)
- [2. 核心界面说明](#2. 核心界面说明)
- 四、数据存储设计
-
- [1. 存储方案](#1. 存储方案)
- [2. 数据库表结构](#2. 数据库表结构)
- 五、核心流程设计
-
- [1. 应用启动流程](#1. 应用启动流程)
- [2. 站点登录流程](#2. 站点登录流程)
- [3. 文章发布流程](#3. 文章发布流程)
- 六、技术难点及解决方案
-
- [1. Playwright 在 Electron 中的集成](#1. Playwright 在 Electron 中的集成)
- [2. 多站点登录状态隔离](#2. 多站点登录状态隔离)
- [3. 应用内显示多个浏览器界面](#3. 应用内显示多个浏览器界面)
- [4. 站点发布脚本的扩展性](#4. 站点发布脚本的扩展性)
- 七、扩展与优化建议
- 八、总结
一次登录,长期运行。解决各种验证码破解的痛点。
一、整体架构概览
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)
- 状态元数据:SQLite 数据库(
- 文章草稿 :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 兼容
- 使用 Playwright 的
2. 多站点登录状态隔离
- 问题:不同站点的登录状态可能相互干扰
- 解决方案:为每个站点创建独立的 Playwright BrowserContext,实现完全隔离
3. 应用内显示多个浏览器界面
- 问题:如何在 Electron 应用内同时显示多个站点的浏览器页面
- 解决方案:使用 Electron 的 BrowserView 组件,每个组件关联一个 Playwright 页面
4. 站点发布脚本的扩展性
- 问题:如何方便地添加新的自媒体站点支持
- 解决方案:采用插件化设计,定义统一的插件接口,新增站点只需编写对应插件
七、扩展与优化建议
- 支持无头模式:在用户完成登录后,可切换到无头模式运行,减少资源占用
- 发布模板支持:为不同站点提供发布模板,自动适配站点格式要求
- 定时发布功能:支持设置发布时间,定时自动发布文章
- 发布结果统计:提供发布成功率、耗时等统计数据
- 自动更新机制:支持应用和插件的自动更新
- 多账号支持:允许同一站点配置多个账号,实现多账号发布
八、总结
本架构设计基于 Electron + Playwright 技术栈,实现了一个功能完整、架构清晰的一文多发应用。核心特点包括:
- 多 Tab 设计:在应用内实现多个站点的同时管理,无需打开外部浏览器
- 登录状态持久化:每个站点仅需登录一次,下次启动自动恢复
- 插件化架构:便于扩展新的自媒体站点支持
- 良好的用户体验:直观的界面设计,清晰的发布流程
- 高性能:通过 BrowserContext 池和任务队列机制,优化资源使用
该架构设计兼顾了功能完整性、扩展性和性能,能够满足用户一文多发的核心需求,同时为后续功能扩展提供了良好的基础。