插件通信
概述
本文档描述了插件系统中插件与网页系统之间的双向通信机制,以及如何在实际业务中使用。
设计思想
核心思路:插件作为"中间层"连接主进程和渲染进程

Preview
网页系统 API
插件管理 API
上传插件
javascript
// 选择插件文件
const filePath = await window.customPluginAPI.selectPluginFile();
// 上传插件
const result = await window.customPluginAPI.uploadPlugin(filePath);
// 返回: { success: true, pluginId: "plugin-1234567890" }管理插件
javascript
// 获取插件列表
const plugins = await window.customPluginAPI.getPluginList();
// 返回: [{ id, name, version, status: "running" | "stopped", ... }]
// 启动插件
await window.customPluginAPI.startPlugin(pluginId);
// 停止插件
await window.customPluginAPI.stopPlugin(pluginId);
// 删除插件
await window.customPluginAPI.deletePlugin(pluginId);
// 更新插件
await window.customPluginAPI.updatePlugin(pluginId, newFilePath);插件通信 API
监听插件消息
javascript
// 监听插件发来的消息
const unsubscribe = window.customPluginAPI.onPluginMessage((message) => {
console.log('收到插件消息:', message);
// message 结构: { pluginId, event, data }
switch (message.event) {
case 'login-success':
handleLoginSuccess(message.data);
break;
case 'data-synced':
refreshData(message.data);
break;
}
});
// 取消监听
unsubscribe();向插件发送消息
javascript
// 向指定插件发送消息
window.customPluginAPI.sendToPlugin(
'com.example.user-manager', // 插件ID
'user-login', // 事件名
{ username, password }, // 数据
);响应插件调用
javascript
// 监听插件的调用请求
const unsubscribe = window.customPluginAPI.onPluginInvoke((request, reply) => {
console.log('插件调用方法:', request.method);
// request: { type, requestId, method, args, pluginId }
switch (request.method) {
case 'getSystemStatus':
reply({ online: true, timestamp: Date.now() });
break;
case 'getUserPreferences':
reply({ theme: 'dark', language: 'zh-CN' });
break;
default:
reply({ error: 'unknown_method' });
}
});
// 取消监听
unsubscribe();插件 API
Window API(操作网页)
插件中可以使用 window 对象来操作网页,需要在 manifest.json 中声明相应权限。
基础 API(无需权限)
javascript
// 获取页面信息
const url = await window.getURL(); // 当前页面URL
const title = await window.getTitle(); // 页面标题
const ua = await window.getUserAgent(); // UserAgent
// 重载页面
await window.reload();DOM API(需要 dom 权限)
javascript
// 查询元素
const element = await window.querySelector('.user-info');
// 返回: { tagName, className, id, textContent, innerHTML, value }
const elements = await window.querySelectorAll('button');
// 返回: [{ tagName, className, id, textContent }, ...]
// 修改样式
await window.setStyles('.main-content', {
backgroundColor: '#f5f5f5',
padding: '20px',
});
// 添加/删除CSS类
await window.addClass('.header', 'active');
await window.removeClass('.header', 'hidden');
// 设置属性
await window.setAttribute('.input', 'disabled', 'true');Storage API(需要 storage 权限)
javascript
// localStorage
const token = await window.getLocalStorage('auth-token');
await window.setLocalStorage('auth-token', 'xxx');
await window.removeLocalStorage('auth-token');
// sessionStorage
const temp = await window.getSessionStorage('temp-data');
await window.setSessionStorage('temp-data', 'xxx');Notification API(需要 notification 权限)
javascript
// 发送通知
await window.notify('登录成功', '欢迎回来!', {
icon: '/icon.png',
tag: 'login',
});Eval API(需要 eval 权限,高危)
javascript
// 执行任意JavaScript代码
const buttonCount = await window.eval(`
document.querySelectorAll('button').length
`);IPC API(与网页通信)
向网页发送消息
javascript
// 发送消息给网页
ipc.send('login-success', {
user: { id: 1, name: '张三' },
});
ipc.send('data-synced', {
timestamp: Date.now(),
count: 100,
});监听网页消息
javascript
// 监听网页发来的消息
ipc.on('user-login', (data) => {
console.log('收到登录请求:', data);
// data: { username, password }
handleLogin(data);
});
ipc.on('user-logout', async () => {
console.log('收到登出请求');
await handleLogout();
});
// 移除监听
ipc.off('user-login');调用网页方法
javascript
// 调用网页方法并等待返回
const status = await ipc.invoke('getSystemStatus');
// 返回: { online: true, timestamp: 1699999999 }
const preferences = await ipc.invoke('getUserPreferences');
// 返回: { theme: 'dark', language: 'zh-CN' }
// 超时或错误
const result = await ipc.invoke('unknownMethod');
// 返回: { error: 'timeout' } 或 { error: 'window_unavailable' }其他 API
javascript
// 使用项目封装的logger
logger.info('插件启动成功');
logger.warn('警告信息');
logger.error('错误信息', error);
// 使用允许的模块(需在 manifest.json 的 permissions 中声明)
const axios = require('axios');
const crypto = require('crypto-js');
// 定时器
const timer = setInterval(() => {
logger.info('定时任务执行');
}, 5000);
clearInterval(timer);完整示例
插件示例:用户管理插件
manifest.json
json
{
"id": "com.example.user-manager",
"name": "用户管理插件",
"version": "1.0.0",
"author": "开发者",
"description": "处理用户登录、权限验证等业务",
"main": "index.js",
"permissions": ["axios", "dom", "storage", "notification"]
}index.js
javascript
module.exports = {
/**
* 插件启动
*/
start: async function () {
logger.info('=== 用户管理插件启动 ===');
// 1. 检查是否已登录
const token = await window.getLocalStorage('auth-token');
if (token) {
logger.info('用户已登录,验证 token');
await this.validateToken(token);
}
// 2. 监听网页登录请求
ipc.on('user-login', async (data) => {
logger.info('收到登录请求:', data.username);
await this.handleLogin(data);
});
// 3. 监听网页登出请求
ipc.on('user-logout', async () => {
logger.info('收到登出请求');
await this.handleLogout();
});
// 4. 定时刷新 token
this.refreshTimer = setInterval(
async () => {
await this.refreshToken();
},
15 * 60 * 1000,
); // 每15分钟
logger.info('=== 用户管理插件启动完成 ===');
},
/**
* 处理登录
*/
handleLogin: async function (credentials) {
try {
// 1. 调用后端API
const axios = require('axios');
const response = await axios.post('https://api.example.com/auth/login', {
username: credentials.username,
password: credentials.password,
});
if (response.data.success) {
const { token, user } = response.data;
// 2. 保存token
await window.setLocalStorage('auth-token', token);
await window.setLocalStorage('user-info', JSON.stringify(user));
// 3. 通知网页登录成功
ipc.send('login-success', { user });
// 4. 显示通知
await window.notify('登录成功', `欢迎回来,${user.name}!`);
logger.info('登录成功:', user.name);
} else {
// 登录失败
ipc.send('login-failed', { message: response.data.message });
}
} catch (error) {
logger.error('登录异常:', error);
ipc.send('login-error', { message: '登录服务异常' });
}
},
/**
* 处理登出
*/
handleLogout: async function () {
try {
// 1. 清除本地存储
await window.removeLocalStorage('auth-token');
await window.removeLocalStorage('user-info');
// 2. 通知网页
ipc.send('logout-success');
logger.info('登出成功');
} catch (error) {
logger.error('登出异常:', error);
}
},
/**
* 验证token
*/
validateToken: async function (token) {
try {
const axios = require('axios');
const response = await axios.get('https://api.example.com/auth/validate', {
headers: { Authorization: `Bearer ${token}` },
});
if (response.data.valid) {
logger.info('Token 有效');
return true;
} else {
logger.warn('Token 已失效');
await this.handleLogout();
return false;
}
} catch (error) {
logger.error('Token 验证失败:', error);
return false;
}
},
/**
* 刷新token
*/
refreshToken: async function () {
try {
const token = await window.getLocalStorage('auth-token');
if (!token) return;
const axios = require('axios');
const response = await axios.post('https://api.example.com/auth/refresh', {
token,
});
if (response.data.token) {
await window.setLocalStorage('auth-token', response.data.token);
logger.info('Token 刷新成功');
}
} catch (error) {
logger.error('Token 刷新失败:', error);
}
},
/**
* 插件停止
*/
destroy: async function () {
logger.info('=== 用户管理插件停止 ===');
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
},
};网页系统示例:登录页面(Vue 3)
vue
<template>
<div class="login-container">
<div v-if="!isLoggedIn" class="login-panel">
<h2>用户登录</h2>
<input v-model="username" placeholder="用户名" />
<input v-model="password" type="password" placeholder="密码" />
<button @click="handleLogin">登录</button>
<p v-if="error" class="error">{{ error }}</p>
</div>
<div v-else class="user-panel">
<h2>欢迎,{{ currentUser?.name }}</h2>
<button @click="handleLogout">登出</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const username = ref('');
const password = ref('');
const error = ref('');
const isLoggedIn = ref(false);
const currentUser = ref(null);
let unsubscribe = null;
onMounted(() => {
// 监听插件消息
unsubscribe = window.customPluginAPI.onPluginMessage((message) => {
console.log('收到插件消息:', message);
switch (message.event) {
case 'login-success':
isLoggedIn.value = true;
currentUser.value = message.data.user;
error.value = '';
console.log('登录成功:', message.data.user);
break;
case 'login-failed':
error.value = message.data.message;
console.log('登录失败:', message.data.message);
break;
case 'login-error':
error.value = message.data.message;
console.log('登录错误:', message.data.message);
break;
case 'logout-success':
isLoggedIn.value = false;
currentUser.value = null;
console.log('登出成功');
break;
}
});
});
onUnmounted(() => {
unsubscribe?.();
});
// 处理登录
function handleLogin() {
error.value = '';
// 发送登录请求给插件
window.customPluginAPI.sendToPlugin('com.example.user-manager', 'user-login', {
username: username.value,
password: password.value,
});
}
// 处理登出
function handleLogout() {
window.customPluginAPI.sendToPlugin('com.example.user-manager', 'user-logout');
}
</script>
<style scoped>
.login-container {
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
.login-panel,
.user-panel {
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
}
input {
display: block;
width: 100%;
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
margin: 10px 5px 0 0;
padding: 10px 20px;
background: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #45a049;
}
.error {
color: red;
margin-top: 10px;
}
</style>权限系统
权限配置
在 manifest.json 中声明插件需要的权限:
json
{
"permissions": [
"axios", // 使用 axios 模块
"crypto-js", // 使用 crypto-js 模块
"dom", // DOM 操作权限
"storage", // 存储访问权限
"notification", // 通知权限
"eval" // 执行任意代码权限(高危)
]
}权限说明
| 权限 | 说明 | 风险等级 |
|---|---|---|
axios | 允许使用 axios 进行网络请求 | 中 |
crypto-js | 允许使用加密库 | 低 |
dom | 允许操作网页DOM | 中 |
storage | 允许访问 localStorage/sessionStorage | 中 |
notification | 允许发送系统通知 | 低 |
eval | 允许执行任意 JavaScript 代码 | 高 |
通信流程图
网页 → 插件

Preview
插件 → 网页

Preview
插件调用网页方法

Preview
最佳实践
错误处理
javascript
// 插件中
try {
await this.handleLogin(data);
} catch (error) {
logger.error('登录失败:', error);
ipc.send('login-error', {
message: error.message || '未知错误',
});
}
// 网页中
window.customPluginAPI.onPluginMessage((message) => {
if (message.event === 'login-error') {
ElMessage.error(message.data.message);
}
});超时处理
javascript
// 插件调用网页方法时有5秒超时
const result = await ipc.invoke('someMethod');
if (result.error === 'timeout') {
logger.warn('网页响应超时');
// 处理超时逻辑
}清理资源
javascript
// 插件的 destroy 方法会自动清理 IPC 监听
destroy: async function() {
// 清理定时器
if (this.timer) clearInterval(this.timer);
// 其他清理逻辑
// IPC 监听器会自动清理,无需手动处理
}调试技巧
查看日志
插件日志会自动写入到项目日志文件:
logs/2025-11/2025-11-11.log控制台输出
javascript
// 插件中使用 logger
logger.info('调试信息');
logger.debug('详细调试信息');
// 也可以使用 console(仅主进程控制台可见)
console.log('主进程日志');网页控制台
javascript
// 网页中监听所有插件消息
window.customPluginAPI.onPluginMessage((message) => {
console.log('📨 插件消息:', message);
});常见问题
Q1: 插件无法发送消息?
A: 检查主窗口是否存在,插件发送消息需要主窗口处于活动状态。
Q2: 网页收不到插件消息?
A: 确保在 onMounted 中注册了 onPluginMessage 监听器,且插件ID正确。
Q3: invoke 一直超时?
A: 确保网页端注册了 onPluginInvoke 监听器,并正确调用 reply 方法。
Q4: 权限不足?
A: 在 manifest.json 的 permissions 数组中添加相应权限。
附录:完整 API 速查表
网页端 API
javascript
// 插件管理
window.customPluginAPI.selectPluginFile();
window.customPluginAPI.uploadPlugin(filePath);
window.customPluginAPI.deletePlugin(pluginId);
window.customPluginAPI.updatePlugin(pluginId, filePath);
window.customPluginAPI.startPlugin(pluginId);
window.customPluginAPI.stopPlugin(pluginId);
window.customPluginAPI.getPluginList();
// 插件通信
window.customPluginAPI.onPluginMessage(callback);
window.customPluginAPI.sendToPlugin(pluginId, event, data);
window.customPluginAPI.onPluginInvoke((request, reply) => {});插件端 API
javascript
// Window API
window.getURL();
window.getTitle();
window.getUserAgent();
window.reload();
window.querySelector(selector);
window.querySelectorAll(selector);
window.setStyles(selector, styles);
window.addClass(selector, className);
window.removeClass(selector, className);
window.setAttribute(selector, attr, value);
window.getLocalStorage(key);
window.setLocalStorage(key, value);
window.removeLocalStorage(key);
window.getSessionStorage(key);
window.setSessionStorage(key, value);
window.notify(title, body, options);
window.eval(code);
// IPC API
ipc.send(event, data);
ipc.on(event, handler);
ipc.off(event);
ipc.invoke(method, ...args);
// 其他
logger.info / warn / error / debug();
require('axios');
require('crypto-js');
setTimeout / setInterval / clearTimeout / clearInterval;