UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

676 lines (627 loc) 17.6 kB
const os = require('os'); const util = require('util'); const Item = require('mm_machine').Drive; /** * mqtt驱动类 * @augments {Item} * @class */ class Drive extends Item { static config = { // 订阅的主题 'topic': [], // 主题回复 'topic_receive': [], // 给客户端推送的主题 'topic_push': 'mqtt/client/${clientid}', // 接收方式 'qos': 1, // 推送方式 'qos_push': 1, // 保持接收 'retain': true, // 超时反馈 'longtime': 10000, // 消息ID 'clientid': 'clientid', // 消息ID 'msgid': 'msgid', // 请求方法 'method': 'method', // 参数 'params': 'params', // 结果 'result': 'result', // 回复方法 'method_receive': '${method}-Ack', // 服务器地址 'host': '' }; /** * 构造函数 * @param {object} config 配置参数 * @param {object} parent 父对象 * @class */ constructor(config, parent) { super({ ...Drive.config, ...config }, parent); this.mode = 3; } } /** * 配置预设 */ Drive.prototype._preset = function () { // 开放给前端调用的函数 this.methods = { ...$.methods }; // 客户端列表 this.clients = {}; }; /** * 配置示例 * @returns {object} 返回配置示例 */ Drive.prototype.model = function () { // 单层匹配订阅,用于订阅所有客户端要接收的数据 格式:$SYS/<代理服务器>/service/业务/+ // "topic": "$SYS/mm/service/user/+" // 通配方式订阅,用于订阅仅该设备或该客户端要订阅的数据 格式:$SYS/<代理服务器>/service/appid/# // "topic": "$SYS/mm/service/idd6877e1561/#" // 完全匹配订阅,用于特定的业务订阅,业务压力最小,建议尽可能使用完全匹配 // "topic": "$SYS/mm/service/state" return { // 名称, 由中英文和下“_”组成, 用于修改或卸载 例如: demo 'name': 'demo_test', // mqtt 服务标题 'title': '示例MQTT', // mqtt 服务介绍 'description': '', // 状态 0未启用,1启用 'state': 1, // 订阅的主题 'topic': ['mqtt/face/#', 'face/+/response', 'mqtt/server/#'], // 回复订阅主题 'topic_receive': ['mqtt/face/${clientid}', 'face/${clientid}/request'], // 给客户端推送的主题 'topic_push': 'mqtt/client/${clientid}', // 接收方式 'qos': 1, // 推送方式 'qos_push': 1, // 保持接收 'retain': true, // 超时反馈 'longtime': 10000, // 消息ID 'clientid': 'clientid', // 消息ID 'msgid': 'msgid', // 请求方法 'method': 'method', // 参数 'params': 'params', // 结果 'result': 'result', // 回复方法 'method_receive': '${method}-Ack' }; }; /** * 新建配置 * @param {string} file 文件名 */ Drive.prototype.newConfig = function (file) { var fl = __dirname + '/config.tpl.json'; if (fl.hasFile()) { var text = fl.loadText(); if (text) { var l = $.slash; if (file.indexOf('mqtt' + l) !== -1) { var name = file.between('mqtt' + l, l); text = text.replaceAll('{0}', name); } file.saveText(text); } } }; /** * 获取IP * @returns {string} 返回IP地址 */ Drive.prototype.getIp = function () { let interfaces = os.networkInte(); for (let iface of Object.values(interfaces)) { for (let config of iface) { if (config.family === 'IPv4' && !config.internal) { return config.address; } } } return '127.0.0.1'; // 如果没有找到,返回本地回环地址 }; /** * 新建消息ID * @param {string} method 方法 * @param {string} terminal 终端 * @returns {string} 返回消息ID */ Drive.prototype.getMsgId = function (method, terminal = 'server') { var time = new Date().getTime(); var ip = this.getIp(); // var msgid = "ID:localhost-" + clientid + "-" + time; var msgid = `${terminal}:${ip}-${time}-${method}`; return msgid; }; /** * 回复的方法格式 * @param {string} method 方法 * @returns {string} 返回回复方法 */ Drive.prototype.getMethod = function (method) { var str = this.config.method_receive; return str.replace('${method}', method); }; /** * 获取回复KEY * @param {string} method 方法 * @param {string} clientid 客户端ID * @param {string} msgid 消息ID * @returns {string} 返回KEY */ Drive.prototype.getReceiveKey = function (method, clientid, msgid) { return this.getMethod(method) + '_' + clientid + '_' + msgid; }; /** * 获取KEY * @param {string} method 方法 * @param {string} clientid 客户端ID * @param {string} msgid 消息ID * @returns {string} 返回KEY */ Drive.prototype.getKey = function (method, clientid, msgid) { return method + '_' + clientid + '_' + msgid; }; /** * 补全路径 * @param {string} path 路径 * @returns {string} 返回完整的路径 */ Drive.prototype.fullUrl = function (path) { var src = path || ''; if (src.indexOf('http') !== 0) { var host = this.config.host || $.host; if (host.indexOf('http') !== 0) { host = 'http://' + host; } if (src.indexOf('~/') === 0) { src = src.replace('~/', host); } else if (src.indexOf('/') === 0) { src = src.replace('/', host); } } return src; }; /** * 推送消息 * @param {string} topic 推送主题 * @param {object} msg 推送内容 * @param {number} qos 接收方式 * @param {boolean} retain 是否保持推送 */ Drive.prototype.send = function (topic, msg, qos = 0, retain = false) { }; /** * 收到消息 * @param {string} push_topic 推送过来的主题 * @param {object} msg 原消息 * @param {number} index 索引 * @param {string} topic 订阅的主题 */ Drive.prototype.message = async function (push_topic, msg, index, topic) { // console.log("请求后", push_topic, msg, index, topic); }; /** * 推送消息到设备 * @param {string} clientid 客户端ID * @param {object} msg 消息主题 * @param {Function} func 回调函数 * @param {number} longtime 超时时间 */ Drive.prototype.pushSync = function (clientid, msg, func, longtime = 0) { var { msgid, method, topic_push, qos_push } = this.config; var id = msg[msgid]; if (!id) { msg[msgid] = this.getMsgId(msg[method], 'server'); id = msg[msgid]; } var key = this.getReceiveKey(msg[method], clientid, id); if (func) { if (!this.clients[clientid]) { this.clients[clientid] = {}; } this.clients[clientid][key] = { clientid, id, func }; } var topic = this.getTopic(topic_push, clientid); this.send(topic, msg, qos_push); if (func) { setTimeout(async () => { // 如果时间到了,回调函数还在队列中,则视为无回复 if (this.clients[clientid] && this.clients[clientid][key]) { try { await this.clients[clientid][key].func(null); } catch (error) { this.log('error', 'MQTT回调错误', key, error); //TODO handle the exception } this.delMsg(clientid, key); } }, longtime || this.config.longtime || 60000); } }; /** * 推送消息 * @param {string} clientid 客户端ID * @param {object} msg 信息 * @param {number} longtime 超时回馈 * @returns {object} 返回消息主体 */ Drive.prototype.pushS = function (clientid, msg, longtime) { var _this = this; return new Promise((resolve, reject) => { _this.pushSync(clientid, msg, (res) => { resolve(res); }, longtime); }); }; /** * 保存设备在线状态 * @param {string} clientid 客户端ID * @param {object} online 是否在线 0为没在线 1为在线 * @param {object} ip 设备IP */ Drive.prototype.saveOnline = async function (clientid, online, ip) { if (!clientid) { } }; /** * 更新设备在线 * @param {string} clientid 客户端ID * @param {boolean} online 是否在线 0为没在线 1为在线 * @param {string} ip 设备IP */ Drive.prototype.updateOnline = async function (clientid, online = 1, ip = '') { if (!this.clients[clientid]) { this.clients[clientid] = {}; } var time_last = new Date().getTime(); // 跟新时间戳 this.clients[clientid].online = online; if (online) { this.clients[clientid].time_last = time_last; } var { device } = await $.server.checkDevice(clientid); if (device && online && !device.online) { device.online = online; if (ip) { device.ip = ip; } } }; /** * 推送消息 * @param {string} method 方法 * @param {string} clientid 客户端ID * @param {object} params 信息 * @param {number} longtime 超时回馈 * @returns {object} 返回消息主体 */ Drive.prototype.push = function (method, clientid, params, longtime = 0) { var cg = this.config; var msg = {}; msg[cg.msgid] = this.getMsgId(method); msg[cg.method] = method; msg[cg.params] = params; return this.pushS(clientid, msg, longtime); }; /** * 删除消息 * @param {string} clientid 设备ID * @param {string} key 消息键 */ Drive.prototype.delMsg = function (clientid, key) { if (this.clients[clientid]) { var dict = this.clients[clientid]; delete dict[key]; } }; /** * 处理事件 * @param {string} clientid 客户端ID * @param {string} key 键 * @param {object} json 信息 */ Drive.prototype.runEvent = function (clientid, key, json) { var method = key.left('_'); if (this[method + '_event']) { try { this[method + '_event'](clientid, json); } catch (err) { this.log('error', 'MQTT事件执行失败!', key, err); } } }; /** * 执行响应 * @param {string} clientid 客户端ID * @param {string} key 键 * @param {object} json 信息 * @returns {object} 返回执行结果 */ Drive.prototype.run = async function (clientid, key, json) { var o; if (this.clients[clientid]) { o = this.clients[clientid][key]; } if (o) { delete this.clients[clientid][key]; try { await o.func(json); } catch (err) { this.log('error', 'MQTT执行失败!', key, err); } } else { await this.runEvent(clientid, key, json); } return o; }; /** * 获取客户端ID * @param {string} push_topic 推送过来的主题 * @param {object} body 消息主体 * @param {object} msg 消息源 * @param {string} topic 订阅的主题 * @returns {string} 返回客户端ID */ Drive.prototype.getClientId = function (push_topic, body, msg, topic) { var cg = this.config; var clientid = body[cg.clientid] || msg[cg.clientid]; if (!clientid) { var str = topic.left('#'); clientid = push_topic.right(str).left('/', true); } return clientid; }; /** * 数据格式转换-转入 * @param {string} push_topic 推送过来的主题 * @param {object} msg 消息源 * @param {string} topic 订阅的主题 * @param {number} index 索引 * @returns {object} 返回消息主体 */ Drive.prototype.toIn = function (push_topic, msg, topic, index) { var cg = this.config; var method = msg[cg.method]; var json = { clientid: '', id: msg[cg.msgid] || this.getMsgId(method, 'client'), method }; if (msg[cg.params]) { json.params = msg[cg.params]; } if (msg[cg.result]) { json.result = msg[cg.result]; } json.clientid = this.getClientId(push_topic, json.params || json.result || {}, msg, topic); return json; }; /** * 数据格式转换-转出 * @param {string} push_topic 推送过来的主题 * @param {object} msg 消息主体 * @param {string} topic 订阅的主题 * @param {number} index 索引 * @returns {object} 返回消息主体 */ Drive.prototype.toOut = function (push_topic, msg, topic, index) { return msg; }; /** * 获取回复主题 * @param {string} topic_tpl 主题模板 * @param {string} clientid 客户端ID * @param {object} params 消息 * @returns {string} 返回主题 */ Drive.prototype.getTopic = function (topic_tpl, clientid, params) { return (topic_tpl || '').replace('${clientid}', clientid); }; /** * 处理函数, 用于处理订阅内容 * @param {string} push_topic 推送过来的主题 * @param {object} msg 消息正文 * @param {string} topic 订阅的主题 * @param {number} index 索引 * @returns {object} 返回执行结果 */ Drive.prototype.run = async function (push_topic, msg, topic, index) { var json = this.convertIn(push_topic, msg, topic, index); if (await this._handleMessage(json)) { return; } if (json.method) { await this._handleMethodMessage(json, msg, topic, index); } return; }; /** * 处理消息 * @param {object} json 消息对象 * @returns {boolean} 是否处理成功 * @private */ Drive.prototype._handleMessage = async function (json) { var id = json.id; if (json.result && id) { var key = this.getKey(json.method, json.clientid, id); var o = await this.run(json.clientid, key, json.result); if (o) { return true; } } return false; }; /** * 处理方法消息 * @param {object} json 消息对象 * @param {object} msg 原始消息 * @param {string} topic 订阅主题 * @param {number} index 索引 * @private */ Drive.prototype._handleMethodMessage = async function (json, msg, topic, index) { var func = this._getMethod(json.method); if (func) { var ret = await this._execMethod(func, json, msg); if (ret) { await this._sendMethodRes(ret, json, topic, index); } } }; /** * 获取方法 * @param {string} methodStr 方法字符串 * @returns {Function|null} 方法函数 * @private */ Drive.prototype._getMethod = function (methodStr) { var methods = this.methods; var arr = methodStr.split('.'); for (var i = 0; i < arr.length; i++) { var key = arr[i]; var m = methods[key]; if (m) { methods = m; } else { return null; } } return typeof (methods) == 'function' ? methods : null; }; /** * 执行方法 * @param {Function} func 方法函数 * @param {object} json 消息对象 * @param {object} msg 原始消息 * @returns {*} 方法执行结果 * @private */ Drive.prototype._execMethod = async function (func, json, msg) { var id = json.id; var params = json.params; var ret; try { ret = func(json.clientid, params, msg, id); if (util.types.isPromise(ret)) { ret = await ret; } } catch (err) { this.log('error', '事件回调失败!', err); } return ret; }; /** * 发送方法响应 * @param {*} ret 方法执行结果 * @param {object} json 消息对象 * @param {string} topic 订阅主题 * @param {number} index 索引 * @private */ Drive.prototype._sendMethodRes = async function (ret, json, topic, index) { var arr_tc = this.config.topic_receive; if (arr_tc.length > index) { var obj = {}; var cg = this.config; var id = json.id; if (id) { obj[cg.msgid] = id; } try { obj[cg.method] = this.getMethod(json.method); obj[cg.result] = ret; var topic_receive = this.getTopic(arr_tc[index], json.clientid, json.params); var reply_msg = this.convertOut(topic_receive, obj, topic, index); await this.send(topic_receive, reply_msg); } catch (err) { this.log('error', 'MQTT事件响应失败!', err); } } }; /** * 主函数, 用于接收并处理订阅内容 * @param {string} push_topic 推送过来的主题 * @param {object} msg 消息正文 * @param {string} topic 订阅的主题 * @param {number} index 索引 * @returns {object} 返回执行结果 */ Drive.prototype.main = async function (push_topic, msg, topic, index) { return this.handle(push_topic, msg, topic, index); }; /** * 接收订阅消息 * @param {object} ret 执行结果 * @param {object} push_topic 主题 * @param {object} msg 消息正文 * @param {string} topic 订阅的主题 * @param {number} index 索引 */ Drive.prototype.mainAfter = async function (ret, push_topic, msg, topic, index) { await this.message(push_topic, msg, topic, index); }; /** * 获取插件 * @param {string} app 应用名称 * @param {string} name 插件名称 * @returns {object} 返回获取到的插件 */ Drive.prototype.plugin = function (app, name) { var l = $.slash; var app_name = app || this.config_file.between('app' + l, l); var plugin_name = name || this.config_file.between('plugin' + l, l); var plus; var plugins = $.pool.plugin[app_name]; if (plugins) { plus = plugins.get(plugin_name); } return plus; }; /** * 获取模型 * @param {string} type 模型类型 * @returns {object} 返回获取到的模型 */ Drive.prototype.getModel = function (type) { let model = { ...this.config }; let dir = this.getDir(); let l = $.slash; let app_name = dir.between('app' + l, l); let plugin_name = dir.between('plugin' + l, l); let name = dir.basename(); model.app = app_name; model.plugin = plugin_name; model.name = model.name || name; return model; }; module.exports = Drive;