mm_machine
Version:
这是超级美眉框架机制构建辅助模块,用于快速构建一个机制,支持动态加载、热更新、模块管理等功能,并具有增强的错误处理和现代JavaScript特性支持。
555 lines (505 loc) • 13.2 kB
JavaScript
/**
* @fileOverview 机制构建帮助类函数
* @author <a href="http://qww.elins.cn">邱文武</a>
* @version 1.6
*/
const util = require('util');
const Item = require('./item.js');
/**
* @class Index索引类
*/
class Index {
/**
* 构造函数
* @param {Object} scope 作用域
* @param {String} dir_base 模块目录
* @constructor
*/
constructor(scope, dir_base) {
// 作用域(同时作为检索的后缀名)
this.scope = scope || ($.val && $.val.scope) ? $.val.scope + '' : '';
// Index接口列表
this.list = [];
/**
* 机制类型
*/
this.type = "";
/**
* 排序项
*/
this.sort_key = "sort";
/**
* 模块目录
*/
this.dir_base = dir_base;
/**
* 模式
* 1.生产模式,改变文件不会重新加载
* 2.热更新模式,改变配置文件会重新加载配置,不重新加载脚本
* 3.热重载模式,改变配置文件都会加载配置和脚本
* 4.重载模式,执行完后重新加载脚本,避免变量污染
* 5.热更新+重载模式,改变配置文件重新加载配置和脚本,执行完后重新加载脚本
*/
this.mode = 1;
}
/**
* 私有方法:执行模块方法
* @param {Object} module 模块对象
* @param {String} method 方法名称
* @param {Array} params 参数数组
* @returns {Promise<any>} 执行结果
* @private
*/
async _executeMethod(module, method, params = []) {
if (!module || !method) return null;
try {
// 确保模块已加载
if (!module.complete) {
await module.exec('load');
}
// 执行方法
const ret = module.exec(method, ...params);
const result = util.types.isPromise(ret) ? await ret : ret;
// 根据模式决定是否重载
if (this.mode >= 4) {
module.exec('reload');
}
return result;
} catch (err) {
$.log.error(`执行模块方法失败: ${err.message}`);
return null;
}
}
}
/**
* 清除接口缓存
*/
Index.prototype.clear = function () {
this.list = [];
};
/**
* 默认驱动
*/
Index.prototype.Drive = Item;
/**
* 加载项
* @param {String} dir 文件路径
* @param {Object} cg 配置参数
* @param {String} file 配置文件
* @returns {Promise<Object|null>} 加载的驱动或配置对象
*/
Index.prototype.load_item = async function (dir, cg, file) {
if (!dir || !file) {
$.log.error('load_item: 缺少必要参数');
return null;
}
try {
if (this.Drive) {
const drive = new this.Drive(dir, this.dir_base.fullname());
drive.mode = this.mode;
if (cg) {
await drive.exec('load_config', file, cg.name);
await drive.exec('set_config', cg);
} else {
await drive.exec('load_config', file);
}
this.list.push(drive);
return drive;
} else {
let json = file.loadJson();
if (!json) {
const fl = "./config.tpl.json".fullname(this.dir_base);
if (fl.hasFile()) {
fl.copyFile(file);
json = file.loadJson();
}
}
if (json) {
this.list.push(json);
}
return json;
}
} catch (err) {
$.log.error('加载项失败:', err);
return null;
}
};
/**
* 加载列表
* @param {Array} list 文件列表
* @returns {Promise<void>}
*/
Index.prototype.load_list = async function (list) {
// 遍历文件路径
if (!Array.isArray(list)) {
$.log.error('load_list: 列表参数必须是数组');
return;
}
// 使用for...of和async/await以保证正确的执行顺序
for (const file of list) {
await this.load_file(file, true);
}
};
/**
* 排序
*/
Index.prototype.sort = function () {
this.list.sort((o1, o2) => {
const p1 = o1.config?.[this.sort_key] || 0;
const p2 = o2.config?.[this.sort_key] || 0;
return p2 - p1;
});
};
/**
* 更新前
*/
Index.prototype.update_before = async function (dir) {
// $.log.debug("更新前")
}
/**
* 更新后
*/
Index.prototype.update_after = async function (dir) {
// $.log.debug("更新后")
}
/**
* 更新所有配置
* @param {String} searchPath 检索路径
* @param {Boolean} accurate 精准路径,默认为false
* @returns {Promise<void>}
*/
Index.prototype.update_config_all = async function (searchPath, accurate) {
try {
// 规范化路径
let normalizedPath = searchPath;
if (!normalizedPath) {
normalizedPath = './app/';
} else {
// 直接使用传入的路径,不强制添加app/
normalizedPath = normalizedPath.fullname();
}
let list_scope = [];
try {
if (!accurate) {
// 获取所有应用路径
const search_dir = this.scope && this.scope !== $.val.scope
? `${this.type}_${this.scope}`
: this.type;
list_scope = $.dir.getAll(normalizedPath, search_dir);
} else {
list_scope = $.dir.getAll(normalizedPath);
}
} catch (err) {
$.log.error("检索目录失败!", err);
}
for (const f of list_scope) {
// 获取所有配置文件
const list_file = $.file.getAll(f, `*${this.type}.json`);
await this.load_list(list_file);
}
} catch (err) {
$.log.error(`更新所有配置失败: ${err.message}`);
}
};
/**
* 更新配置
* @param {Object} dir
*/
Index.prototype.update_config_have = async function (dir) {
const list = this.list;
for (const o of list) {
const file = o.filename;
if (file) {
try {
const config = file.loadJson();
if (config) {
if (Array.isArray(config)) {
// 使用find方法查找匹配项
const targetConfig = config.find(cg => cg.name === o.config.name);
if (targetConfig) {
await o.exec('set_config', targetConfig);
}
} else {
await o.exec('set_config', config);
}
}
} catch (err) {
$.log.error(`更新配置失败: ${err.message}`);
}
}
}
};
/**
* 更新配置
* @param {Object} dir - 检索目录
* @param {Boolean} accurate - 精准路径,默认为false
* @param {Boolean} clear - 是否清除现有配置,默认为false
* @returns {Promise<void>}
*/
Index.prototype.update_config = async function (dir, accurate = false, clear = false) {
try {
if (clear) {
this.clear();
await this.update_config_all(dir, accurate);
} else {
// 如果没有指定目录,则更新已有的配置文件
await this.update_config_have();
}
this.sort();
} catch (err) {
$.log.error(`配置更新失败: ${err.message}`);
}
};
/**
* 更新JS
* @returns {Promise<void>}
*/
Index.prototype.update_script = async function () {
const list = this.list;
for (const o of list) {
if (o.config?.state === 1) {
await o.exec('load');
}
}
};
/**
* 更新
* @param {String} dir 检索的路径
* @param {Boolean} accurate 是否精准路径,默认为false
* @param {Boolean} loadJS 是否加载JS,默认为true
* @param {Boolean} clear 是否清除现有配置,默认为true
* @returns {Promise<void>}
*/
Index.prototype.update_main = async function (dir, accurate = false, loadJS = true, clear = true) {
await this.update_config(dir, accurate, clear);
if (loadJS) {
await this.update_script();
}
};
/**
* 更新配置
* @param {String} dir 检索的路径
* @param {Boolean} loadJS 是否加载JS
*/
Index.prototype.update = async function (dir, accurate = false, loadJS = true, clear = true) {
await this.update_before(dir);
await this.update_main(dir, accurate, loadJS, clear);
await this.update_after(dir);
};
/**
* 查询配置项
* @param {String} name 名称
* @return {Object|null} 返回单项配置
*/
Index.prototype.get = function (name) {
if (!name) return null;
return this.list.find(item => item.config?.name === name) || null;
};
/**
* 设置配置项
* @param {String} name 配置项名称
* @param {Object} cg 配置对象
* @return {Boolean} 是否设置成功
*/
Index.prototype.set = function (name, cg) {
if (!name || !cg) return false;
const item = this.get(name);
if (item) {
// 使用Object.assign合并属性
Object.assign(item.config, cg);
return true;
}
return false;
};
/**
* 保存
* @returns {Boolean} 是否保存成功
*/
Index.prototype.save = async function () {
const list = this.list;
for (const o of list) {
try {
await o.exec('save');
} catch (err) {
$.log.error(`保存失败: ${err.message}`);
}
}
return true;
};
/**
* 添加插件
* @param {Object} config 配置对象
* @returns {Promise<Object|null>} 新添加的插件对象
*/
Index.prototype.add = async function (config) {
if (!config || !config.name) {
$.log.error('添加插件失败: 缺少必要的配置信息');
return null;
}
try {
// 检查是否已存在
const existing = this.get(config.name);
if (existing) {
$.log.warn(`插件 ${config.name} 已存在`);
return existing;
}
const item = new Item(this, config);
this.list.push(item);
this.sort();
return item;
} catch (err) {
$.log.error(`添加插件失败: ${err.message}`);
return null;
}
};
/**
* 删除插件
* @param {String} name 插件名称
* @returns {Promise<Boolean>} 是否删除成功
*/
Index.prototype.del = async function (name) {
if (!name) return false;
const index = this.list.findIndex(item => item.config?.name === name);
if (index === -1) return false;
try {
const item = this.list[index];
await item.exec('unload');
this.list.splice(index, 1);
return true;
} catch (err) {
$.log.error(`删除插件失败: ${err.message}`);
return false;
}
};
/**
* 加载插件
* @param {String} name 插件名称
* @returns {Promise<Object|null>} 加载的插件对象
*/
Index.prototype.load = async function (name) {
if (!name) return null;
const item = this.get(name);
if (!item) return null;
try {
if (item.config?.state === 1) {
await item.exec('load');
}
return item;
} catch (err) {
$.log.error(`加载插件 ${name} 失败: ${err.message}`);
return null;
}
};
/**
* 卸载插件
* @param {String} name 插件名称
* @returns {Promise<Boolean>} 是否卸载成功
*/
Index.prototype.unload = async function (name) {
if (!name) return false;
const item = this.get(name);
if (!item) return false;
try {
await item.exec('unload');
return true;
} catch (err) {
$.log.error(`卸载插件 ${name} 失败: ${err.message}`);
return false;
}
};
/**
* 重新加载插件
* @param {String} name 插件名称
* @returns {Promise<Object|null>} 重新加载的插件对象
*/
Index.prototype.reload = async function (name) {
if (!name) return null;
const item = this.get(name);
if (!item) return null;
try {
await item.exec('reload');
return item;
} catch (err) {
$.log.error(`重新加载插件 ${name} 失败: ${err.message}`);
return null;
}
};
/**
* 通过文件加载配置
* @param {String} file 文件名
* @param {Boolean} create 不存在则进行创建
* @returns {Promise<Object|null|String>} 加载的驱动对象、null或错误信息
*/
Index.prototype.load_file = async function (file, create = false) {
try {
const dir = file.dirname();
// 载入文件
const obj = file.loadJson();
if (obj) {
if (Array.isArray(obj)) {
for (const o of obj) {
// 实例化一个驱动
await this.load_item(dir, o, file);
}
} else {
return await this.load_item(dir, null, file);
}
} else if (create) {
return await this.load_item(dir, null, file);
} else {
return `${file}文件不存在`;
}
return null;
} catch (err) {
$.log.error(`加载文件失败: ${err.message}`);
return `加载文件失败: ${err.message}`;
}
};
/**
* 调用函数
* @param {String} name 模块名
* @param {String} method 函数名
* @param {Object} params 参数集合
* @returns {Promise<any>} 执行结果
*/
Index.prototype.run = async function (name, method, ...params) {
if (name) {
const module = await this.get(name);
return module && module.config?.state === 1 ? this._executeMethod(module, method, params) : null;
} else if (name === null) {
let result = null;
for (const module of this.list) {
if (module.config?.state === 1) {
result = await this._executeMethod(module, method, params);
if (result && module.config?.end) {
break;
}
}
}
return result;
}
return null;
};
/**
* 执行方法
* @param {String} name 插件名称
* @param {String} method 方法名称
* @param {Object} option 配置参数
* @returns {Promise<any>} 执行结果
*/
Index.prototype.exec = async function (name, method, ...params) {
if (name) {
const module = await this.get(name);
return module ? this._executeMethod(module, method, params) : null;
} else if (name === null) {
for (const module of this.list) {
await this._executeMethod(module, method, params);
}
}
return null;
};
/**
* @module 导出Index类
*/
exports.Index = Index;
exports.Item = Item;