UNPKG

mm_machine

Version:

这是超级美眉框架机制构建辅助模块,用于快速构建一个机制,支持动态加载、热更新、模块管理等功能,并具有增强的错误处理和现代JavaScript特性支持。

555 lines (505 loc) 13.2 kB
/** * @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;