mm_os
Version:
这是超级美眉服务端框架,用于快速构建应用程序。
600 lines (565 loc) • 14.8 kB
JavaScript
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;