mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
676 lines (627 loc) • 17.6 kB
JavaScript
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;