目录结构
project/
├── plugins/
│ ├── shopSignIn.js // 示例插件
├── PluginFramework.js // 插件框架
├── HotReloadPluginFramework.js // 插件框架
├── PluginParser.js // 规则解析器
└── index.js // 启动文件
PluginFramework.js插件管理器
const fs = require('fs');
const path = require('path');
const PluginParser = require('./PluginParser');
class PluginFramework {
constructor(pluginDirectory) {
this.plugins = {}; // 存储插件
this.pluginDirectory = pluginDirectory || path.join(__dirname, 'plugins'); // 插件目录
this.loadPlugins(); // 加载插件
}
// 加载插件
loadPlugins() {
const pluginFiles = fs.readdirSync(this.pluginDirectory);
pluginFiles.forEach(file => {
if (file.endsWith('.js')) {
const pluginName = path.basename(file, '.js');
const pluginPath = path.join(this.pluginDirectory, file);
// 如果插件已加载,跳过
if (this.plugins[pluginName]) {
console.log(`插件 ${pluginName} 已经加载,跳过`);
return;
}
const plugin = require(pluginPath);
this.plugins[pluginName] = this.loadPluginRules(pluginName, pluginPath);
console.log(`插件 ${pluginName} 已加载`);
}
});
}
// 解析插件规则
loadPluginRules(pluginName, pluginPath) {
const pluginContent = fs.readFileSync(pluginPath, 'utf-8');
// 创建解析器实例并解析插件注释
const parser = new PluginParser();
const pluginMetadata = parser.parse(pluginContent);
// console.log(`插件 ${pluginName} 的元数据:`, pluginMetadata);
// 确保返回插件的实际函数
const pluginFunction = require(pluginPath); // 这里是加载插件文件并获得其导出的函数
return {
pluginFunction, // 保存实际插件函数
rules: pluginMetadata.rules// 保存规则
};
}
// 从插件文件内容中提取规则
extractRules(content) {
const ruleRegex = /@rule\s+([^\n]+)/g; // 匹配 @rule 后面的一行规则
const rules = [];
let match;
while ((match = ruleRegex.exec(content)) !== null) {
// 去除规则字符串中的多余空格和换行符
const rule = match[1].replace(/[\r\n]+/g, '').trim();
rules.push(new RegExp(rule)); // 转换为正则表达式并存储
}
return rules;
}
// 触发插件处理消息事件
triggerEvent(s) {
if (!s || !s.message) {
console.error("消息对象格式错误,缺少 message 属性");
return;
}
// 插件链的执行
let currentIndex = 0; // 插件索引
const next = () => {
if (currentIndex < Object.keys(this.plugins).length) {
const plugin = this.plugins[Object.keys(this.plugins)[currentIndex]];
currentIndex++;
// 遍历插件的规则
let matched = false;
plugin.rules.forEach(rule => {
if (!matched && rule.test(s.message)) {
// 匹配成功,执行插件的处理函数
plugin.pluginFunction(s);
matched = true; // 标记已匹配
}
});
//如果没有匹配到规则,传递到下一个插件
if (!matched) {
next(); // 查找下一个符合规则的插件
}
} else {
console.log("所有插件均未匹配到规则,消息处理结束");
}
};
s.next = next; // 保存继续传递的函数
next(); // 启动第一个插件
}
}
module.exports = PluginFramework;
PluginParser.js插件规则解析器实现
class PluginParser {
// 解析注释内容
parse(content) {
const metadata = {};
// 提取规则
metadata.rules = this.extractRules(content);
// 提取其他字段
metadata.description = this.extractField(content, '@description');
metadata.team = this.extractField(content, '@team');
metadata.author = this.extractField(content, '@author');
metadata.version = this.extractField(content, '@version');
metadata.name = this.extractField(content, '@name');
metadata.priority = this.extractField(content, '@priority');
metadata.admin = this.extractField(content, '@admin');
metadata.disable = this.extractField(content, '@disable');
metadata.public = this.extractField(content, '@public');
metadata.authentication = this.extractField(content, '@authentication');
metadata.classification = this.extractField(content, '@classification');
return metadata;
}
// 提取特定字段的值
extractField(content, field) {
const regex = new RegExp(`\\${field}\\s+([^\n]+)`);
const match = content.match(regex);
return match ? match[1].trim() : null;
}
// 从插件文件内容中提取规则
extractRules(content) {
const ruleRegex = /@rule\s+([^\n]+)/g; // 匹配 @rule 后面的一行规则
const rules = [];
let match;
while ((match = ruleRegex.exec(content)) !== null) {
// 去除规则字符串中的多余空格和换行符
const rule = match[1].replace(/[\r\n]+/g, '').trim();
rules.push(new RegExp(rule)); // 转换为正则表达式并存储
}
return rules;
}
}
module.exports = PluginParser;
扩展PluginFramework实现热插热拔
// HotReloadPluginFramework.js
const PluginFramework = require('./PluginFramework'); // 导入基础插件框架
const chokidar = require('chokidar');
const path = require('path');
const fs = require('fs');
class HotReloadPluginFramework extends PluginFramework {
constructor(pluginDirectory) {
super(pluginDirectory); // 调用父类构造函数
this.pluginFileCache = {}; // 用来缓存插件的文件内容
this.watchPluginDirectory(); // 开始监听插件目录
}
// 监听插件目录的变化
watchPluginDirectory() {
const watcher = chokidar.watch(this.pluginDirectory, {
ignored: /^\./, // 忽略隐藏文件
persistent: true,
});
watcher.on('change', (filePath) => {
const pluginName = path.basename(filePath, '.js');
// 只有当文件内容真正发生变化时,才重新加载
this.reloadPluginIfChanged(pluginName, filePath);
});
watcher.on('add', (filePath) => {
const pluginName = path.basename(filePath, '.js');
// 只有插件没有加载过时,才重新加载
if (this.plugins[pluginName]) {
return;
}
console.log(`插件文件 ${pluginName} 被添加`);
this.reloadPlugin(pluginName, filePath);
});
watcher.on('unlink', (filePath) => {
const pluginName = path.basename(filePath, '.js');
console.log(`插件文件 ${pluginName} 被删除`);
this.unloadPlugin(pluginName); // 卸载被删除的插件
});
}
// 判断插件文件是否发生变化
reloadPluginIfChanged(pluginName, filePath) {
try {
// 检查 filePath 是否有效
if (!filePath) {
throw new Error(`插件文件路径无效: ${filePath}`);
}
// 读取文件内容
const newContent = fs.readFileSync(filePath, 'utf-8');
// 比较缓存的内容和当前内容
const cachedContent = this.pluginFileCache[pluginName];
if (cachedContent !== newContent) {
this.reloadPlugin(pluginName, filePath);
this.pluginFileCache[pluginName] = newContent; // 更新缓存的内容
}
} catch (err) {
console.error(`加载插件 ${pluginName} 时发生错误: ${err.message}`);
}
}
// 重新加载插件
reloadPlugin(pluginName, filePath) {
try {
console.log(`重新加载插件 ${pluginName}`);
this.unloadPlugin(pluginName); // 卸载旧插件
// 检查文件路径是否有效
if (!filePath || !fs.existsSync(filePath)) {
throw new Error(`插件文件 ${filePath} 不存在`);
}
// 清除缓存,确保插件被重新加载
delete require.cache[require.resolve(filePath)];
// 重新加载插件
const plugin = require(filePath);
this.plugins[pluginName] = plugin;
console.log(`插件 ${pluginName} 已重新加载`);
} catch (err) {
console.error(`加载插件 ${pluginName} 时发生错误: ${err.message}`);
}
}
// 卸载插件
unloadPlugin(pluginName) {
if (this.plugins[pluginName]) {
// 删除插件缓存
delete this.plugins[pluginName];
console.log(`插件 ${pluginName} 已卸载`);
}
}
}
module.exports = HotReloadPluginFramework; // 导出 HotReloadPluginFramework 类
添加一个插件测试一下效果
/**
* @description 监听签到的店铺
* @team youfak
* @author youfak
* @version 2.0.1
* @name 店铺签到
* @rule ([A-Fa-f0-9]{32})&(\d+)&(\d+)
* @rule ^(qd [0-9A-Fa-f]{32})
* @priority 100000
* @admin false
* @disable false
* @public false
* @authentication true
* @classification ["工具"]
*/
module.exports = async s => {
const message = s.message.trim();
console.log(message);
// 如果消息匹配店铺签到规则
if (/([A-Fa-f0-9]{32})&(\d+)&(\d+)/.test(message)) {
s.text("匹配到店铺签到规则!");
return; // 如果匹配,直接响应并退出
}
// 如果没有匹配规则,传递消息给下一个插件
s.next();
};
启动程序
const fs = require('fs');
const path = require('path');
// 引入 PluginFramework 类
// const PluginFramework = require('./PluginFramework');
// 创建框架实例
// const framework = new PluginFramework(path.join(__dirname, 'plugins')); // 这里指定了插件目录
// 引入 HotReloadPluginFramework 类 扩展PluginFramework实现热重载
const HotReloadPluginFramework = require('./HotReloadPluginFramework');
// 创建 HotReloadPluginFramework 实例,指定插件目录
const pluginDirectory = path.join(__dirname, 'plugins'); // 插件目录
const framework = new HotReloadPluginFramework(pluginDirectory);
// 模拟消息对象
const s = {
message: "qd 1234567890abcdef1234567890abcdef&123&456", // 模拟消息
text: (msg) => { console.log(msg); }, // 输出返回消息
next: () => { console.log("消息未匹配到规则,继续传递"); } // 继续传递
};
// 确保 `s.message` 存在且为字符串
if (s.message && typeof s.message === 'string') {
// 触发事件,处理消息
framework.triggerEvent(s);
} else {
console.error("消息内容为空或格式不正确");
}
评论区