UNPKG

mm_os

Version:

这是超级美眉服务端框架,用于快速构建应用程序。

600 lines (565 loc) 14.8 kB
const os = require('os'); const util = require('util'); const Item = require('mm_machine').Item; /** * mqtt驱动类 * @extends {Item} * @class */ class Drive extends Item { /** * 构造函数 * @param {String} dir 当前目录 * @constructor */ constructor(dir) { super(dir, __dirname); this.default_file = "./mqtt.json"; this.mode = 3; /* 通用项 */ /** * 配置参数 */ this.config = { // 名称, 由中英文和下“_”组成, 用于修改或卸载 例如: demo "name": "", // mqtt 服务标题 "title": "", // mqtt 服务介绍 "description": "", // 状态 0未启用,1启用 "state": 1, // 订阅的主题 "topic": [], // 主题回复 "topic_receive": [], // 给客户端推送的主题 "topic_push": "mqtt/client/${clientid}", // 接收方式 "qos": 1, // 推送方式 "qos_push": 1, // 保持接收 "retain": true, // 超时反馈 "longtime": 10000, // 调用的脚本 "func_file": "./index.js", // 消息ID "clientid": "clientid", // 消息ID "msgid": "msgid", // 请求方法 "method": "method", // 参数 "params": "params", // 结果 "result": "result", // 回复方法 "method_receive": "${method}-Ack", // 服务器地址 "host": "" }; // 开放给前端调用的函数 this.methods = Object.assign({}, $.methods); // 消息字典 this.drives = {}; } } /** * 配置示例 * @param {String} 文件 */ 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, // 调用的脚本 "func_file": "./index.js", // 消息ID "clientid": "clientid", // 消息ID "msgid": "msgid", // 请求方法 "method": "method", // 参数 "params": "params", // 结果 "result": "result", // 回复方法 "method_receive": "${method}-Ack" } } /** * 新建配置 * @param {String} 文件 */ Drive.prototype.new_config = 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.get_ip = function() { const interfaces = os.networkInterfaces(); for (const iface of Object.values(interfaces)) { for (const config of iface) { if (config.family === 'IPv4' && !config.internal) { return config.address; } } } return '127.0.0.1'; // 如果没有找到,返回本地回环地址 } /** * 新建消息ID * @param {String} method 方法 * @param {String} terminal 终端 */ Drive.prototype.get_msgid = function(method, terminal = 'server') { var time = new Date().getTime(); var ip = this.get_ip(); // var msgid = "ID:localhost-" + clientid + "-" + time; var msgid = `${terminal}:${ip}-${time}-${method}`; return msgid; } /** * 回复的方法格式 * @param {String} method 方法 * @param {String} terminal 终端 */ Drive.prototype.get_method = 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 */ Drive.prototype.get_receive_key = function(method, clientid, msgid) { return this.get_method(method) + "_" + clientid + "_" + msgid; } /** * 获取KEY * @param {String} method 方法 * @param {String} clientid 客户端ID * @param {String} msgid 消息ID */ Drive.prototype.get_key = function(method, clientid, msgid) { return method + "_" + clientid + "_" + msgid; } /** * 补全路径 * @param {String} src * @returns {String} 返回完整的路径 */ Drive.prototype.fullUrl = function(src) { if (!src) { return "" } 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 回调函数 */ Drive.prototype.pushSync = function(clientid, msg, func, longtime = 0) { var { msgid, method, params, topic_push, qos_push } = this.config; var id = msg[msgid]; if (!id) { msg[msgid] = this.get_msgid(msg[method], 'server'); id = msg[msgid]; } var key = this.get_receive_key(msg[method], clientid, id); if (func) { if (!this.drives[clientid]) { this.drives[clientid] = {} } this.drives[clientid][key] = { clientid, id, func }; } var topic = this.get_topic(topic_push, clientid); this.send(topic, msg, qos_push); if (func) { if (!longtime) { longtime = this.config.longtime || 60000; } setTimeout(async () => { // 如果时间到了,回调函数还在队列中,则视为无回复 if (this.drives[clientid][key]) { try { await this.drives[clientid][key].func(null); } catch (error) { $.log.error("MQTT回调错误", key, error); //TODO handle the exception } this.del_msg(clientid, key); } }, longtime); } } /** * 推送消息 * @param {String} clientid 客户端ID * @param {Object} msg 信息 * @param {Number} longtime 超时回馈 */ Drive.prototype.pushS = function(clientid, msg, longtime) { var _this = this; return new Promise(function(resolve, reject) { _this.pushSync(clientid, msg, (res) => { resolve(res); }, longtime); }); } /** * * @param {Object} clientid * @param {Object} online * @param {Object} ip */ Drive.prototype.save_online = async function(clientid, online, ip) { if (!clientid) { } } /** * 更新设备在线 * @param {String} clientid 客户端ID * @param {Boolean} online 是否在线 0为没在线 1为在线 * @param {String} ip 设备IP */ Drive.prototype.update_online = async function(clientid, online = 1, ip = '') { if (!this.drives[clientid]) { this.drives[clientid] = {}; } var time_last = new Date().getTime(); // 跟新时间戳 this.drives[clientid].online = online; if (online) { this.drives[clientid].time_last = time_last; } var { tip, device } = await $.server.check_device(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 超时回馈 */ Drive.prototype.push = function(method, clientid, params, longtime = 0) { var cg = this.config; var msg = {}; msg[cg.msgid] = this.get_msgid(method); msg[cg.method] = method; msg[cg.params] = params; return this.pushS(clientid, msg, longtime); } /** * 删除消息 * @param {String} clientid 设备ID * @param {String} key 消息键 */ Drive.prototype.del_msg = function(clientid, key) { if (this.drives[clientid]) { var dict = this.drives[clientid]; delete dict[key]; } } /** * 处理事件 * @param {String} key 键 * @param {Object} json 信息 */ Drive.prototype.run_event = function(clientid, key, json) { var method = key.left("_"); if (this[method + "_event"]) { try { this[method + "_event"](clientid, json); } catch (err) { $.log.error("MQTT事件执行失败!", key, err); } } } /** * 执行响应 * @param {String} key 键 * @param {String} msgid 消息ID * @param {Object} json 信息 */ Drive.prototype.run = async function(clientid, key, json) { var o; if (this.drives[clientid]) { o = this.drives[clientid][key]; } if (o) { delete this.drives[clientid][key]; try { await o.func(json); } catch (err) { $.log.error("MQTT执行失败!", key, err); } } else { await this.run_event(clientid, key, json); } return o; } /** * 获取客户端ID * @param {String} push_topic 推送过来的主题 * @param {Object} body 消息主体 * @param {Object} msg 消息源 * @param {String} topic 订阅的主题 */ Drive.prototype.get_clientid = 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 {Number} index 索引 * @param {String} topic 订阅的主题 */ Drive.prototype.convert_in = function(push_topic, msg, topic, index) { var cg = this.config; var method = msg[cg.method]; var json = { clientid: "", id: msg[cg.msgid] || this.get_msgid(method, 'client'), method }; if (msg[cg.params]) { json.params = msg[cg.params]; } if (msg[cg.result]) { json.result = msg[cg.result]; } json.clientid = this.get_clientid(push_topic, json.params || json.result || {}, msg, topic); return json; } /** * 数据格式转换-转出 * @param {String} push_topic 推送过来的主题 * @param {Object} msg 消息主体 * @param {Number} index 索引 * @param {String} topic 订阅的主题 */ Drive.prototype.convert_out = function(push_topic, msg, topic, index) { return msg; } /** * 获取回复主题 * @param {String} topic_tpl 主题模板 * @param {String} clientid 客户端ID * @param {Object} params 消息 * @returns 返回主题 */ Drive.prototype.get_topic = function(topic_tpl, clientid, params) { return (topic_tpl || "").replace("${clientid}", clientid); } /** * 处理函数, 用于处理订阅内容 * @param {String} push_topic 推送过来的主题 * @param {Object} body 消息格式 * @param {String} topic 订阅的主题 * @param {Number} index 索引 * @return {Object} 返回执行结果 */ Drive.prototype.handle = async function(push_topic, msg, topic, index) { var json = this.convert_in(push_topic, msg, topic, index); var id = json.id; if (json.result && id) { var key = this.get_key(json.method, json.clientid, id); var o = await this.run(json.clientid, key, json.result); if (o) { return } } if (json.method) { var func; var methods = this.methods; var arr = json.method.split('.'); for (var i = 0; i < arr.length; i++) { var key = arr[i]; var m = methods[key]; if (m) { methods = m; } } if (methods && typeof(methods) == 'function') { func = methods; } if (func) { var id = json.id; var params = json.params; var ret; try { ret = func(json.clientid, params, msg, id); } catch (err) { $.log.error("事件回调失败!", err); } if (ret) { if (util.types.isPromise(ret)) { ret = await ret; } if (ret) { var arr_tc = this.config.topic_receive; if (arr_tc.length > index) { var obj = {}; var cg = this.config; if (id) { obj[cg.msgid] = id; } try { obj[cg.method] = this.get_method(json.method); obj[cg.result] = ret; var topic_receive = this.get_topic(arr_tc[index], json.clientid, params); var reply_msg = this.convert_out(topic_receive, obj, topic, index); await this.send(topic_receive, reply_msg); } catch (err) { $.log.error("MQTT事件响应失败!", err); } } } } } } return }; /** * 主函数, 用于接收并处理订阅内容 * @param {String} push_topic 推送过来的主题 * @param {Object} body 消息格式 * @param {String} topic 订阅的主题 * @param {Number} index 索引 * @return {Object} 返回执行结果 */ Drive.prototype.main = async function(push_topic, msg, topic, index) { return this.handle(push_topic, msg, topic, index); } /** * 接收订阅消息 * @param {Object} push_topic 主题 * @param {Object} msg 消息正文 */ Drive.prototype.main_after = 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 plus; var l = $.slash; if (!app) { app = this.filename.between("app" + l, l); } if (!name) { name = this.filename.between("plugin" + l, l); } var plugins = $.pool.plugin[app]; if (plugins) { plus = plugins.get(name); } return plus } module.exports = Drive;