mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
652 lines (585 loc) • 16.2 kB
JavaScript
const Item = require('mm_machine').Drive;
// 提供一个全局方法容器
if (!$.methods) {
$.methods = {};
}
/**
* websocket驱动类
* @augments {Item}
* @class
*/
class Drive extends Item {
static config = {
// 同步消息循环发送的时间间隔
'interval': 1000
};
/**
* 构造函数
* @param {object} config 配置参数
* @param {object} parent 父对象
* @class
*/
constructor(config, parent) {
super({ ...Drive.config, ...config }, parent);
}
}
/**
* 获取驱动实例的全局存储键名
* @returns {string} 存储键名
* @private
*/
Drive.prototype._getStorageKey = function () {
return 'socket_drive_' + (this.config.name || 'default');
};
/**
* 确保数据引用有效(热更新后调用)
* @private
*/
Drive.prototype._ensureRefs = function () {
var storage_key = this._getStorageKey();
// 检查全局存储是否存在
if (!$.sockets || !$.sockets[storage_key]) {
// 如果全局存储不存在,重新初始化
this._preset();
return;
}
// 确保数据引用正确
this._storage = $.sockets[storage_key];
// 重新建立数据引用
if (!this.clients || this.clients !== this._storage.clients) {
this.clients = this._storage.clients;
}
if (!this.list_msg || this.list_msg !== this._storage.list_msg) {
this.list_msg = this._storage.list_msg;
}
};
/**
* 预设配置
*/
Drive.prototype._preset = function () {
// 开放给前端调用的函数
this.methods = { ...$.methods };
// 初始化全局存储结构
var storage_key = this._getStorageKey();
if (!$.sockets) {
$.sockets = {};
}
if (!$.sockets[storage_key]) {
$.sockets[storage_key] = {
clients: {},
list_msg: []
};
}
// 使用全局存储的数据
this._storage = $.sockets[storage_key];
// 保持对全局数据的引用(为了向后兼容)
this.clients = this._storage.clients;
this.list_msg = this._storage.list_msg;
};
/**
* 新建脚本
* @param {string} file 文件
*/
Drive.prototype.newScript = function (file) {
var fl = __dirname + '/script.tpl.js';
if (fl.hasFile()) {
var text = fl.loadText();
if (text) {
var l = $.slash;
if (file.indexOf('socket' + l) !== -1) {
var name = file.between('socket' + l, l);
text = text.replaceAll('{0}', name);
}
file.saveText(text);
}
}
};
/**
* 新建配置
* @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('socket' + l) !== -1) {
var name = file.between('socket' + l, l);
text = text.replaceAll('{0}', name);
}
file.saveText(text);
}
}
};
/**
* 获取session ID
* @param {object} ctx HTTP上下文
* @returns {string} 返回用户的uuid
*/
Drive.prototype.getToken = async function (ctx) {
var uuid = await ctx.cookies.get('mm:uuid');
if (!uuid) {
var hd = ctx.request.header;
var agent = hd['user-agent'];
if (!agent) {
agent = 'mm';
}
var start = agent.md5().substring(0, 32);
var stamp = Date.parse(new Date()) / 1000;
uuid = (ctx.ip + '_' + stamp).aesEncode(start);
}
return uuid;
};
/**
* 收到消息时处理函数
* @param {string} body_str 消息正文字符串
* @param {object} ctx http上下文
* @param {string} token 临时访问牌
*/
Drive.prototype.onmessage = async function (body_str, ctx, token) {
// 解析消息内容
var msg = {};
if (body_str && (body_str.indexOf('[') === 0 || body_str.indexOf('{') === 0)) {
try {
msg = JSON.parse(body_str);
} catch (error) {
console.error('Socket消息结构体不对:', error);
}
}
// 处理回复消息(如果有ID字段)
if (msg && msg.id) {
// 调用驱动的消息回复处理
this._handleResponse(msg);
}
var ret = await this.run(body_str, ctx, token);
if (ret) {
var ws = ctx.websocket;
if (typeof (ret) === 'object') {
ws.send(JSON.stringify(ret));
} else {
ws.send(ret);
}
}
};
/**
* 状态变更通知
* @param {string} type 通知类型
* @param {string} body_str 消息正文字符串
* @param {object} ctx http上下文
* @param {string} token 临时访问牌
* @returns {boolean} 返回true表示做状态修改, 例如关闭时为true, 会删除该客户端
*/
Drive.prototype.noticy = async function (type, body_str, ctx, token) {
// this.log('debug', '通知:', '关闭了');
return true;
};
/**
* 关闭连接时处理函数
* @param {string} body_str 消息正文字符串
* @param {object} ctx http上下文
* @param {string} token 临时访问牌
*/
Drive.prototype.onclose = async function (body_str, ctx, token) {
var del = await this.noticy('close', ctx, token);
if (del) {
var lt = this.clients[token];
var index = lt.indexOf(ctx);
lt.splice(index, 1);
}
};
/**
* 设置websocket
* @param {object} ctx http上下文
* @param {string} token 临时访问牌
*/
Drive.prototype.setSocket = function (ctx, token) {
var ws = ctx.websocket;
// 增加消息队列
ws.list_msg = [];
/**
* 设置发送请求
* @param {string} method 方法名称
* @param {object} params 请求参数
* @param {Function} func 回调函数
*/
var _this = this;
ws.req = async function (method, params, func) {
var key = _this.config.name + '';
var data = {
id: key + new Date().getTime() + Math.random(),
method: method,
params: params
};
this.send(JSON.stringify(data));
if (func) {
data.func = func;
this.list_msg.push(data);
}
};
// 设置事件 —— 获取消息时和socket关闭时
ws.on('message', async (body_str) => {
_this.onmessage(body_str, ctx, token);
});
ws.on('close', async (body_str) => {
_this.onclose(body_str, ctx, token);
});
};
/**
* 握手成功, 发送首条返回内容
* @param {object} ctx http 上下文
* @param {string} token 临时访问牌
*/
Drive.prototype.success = function (ctx, token) {
var ret = $.ret.bl(true, 'connection succeeded');
// 首次响应加上身份牌
ret.result.token = token;
// ID为0表示连接成功
ret.id = 0;
ctx.websocket.send(JSON.stringify(ret));
};
/**
* 添加客户端
* @param {object} ctx 请求上下文
*/
Drive.prototype.add = async function (ctx) {
var token = await this.getToken(ctx);
if (!this.clients[token]) {
this.clients[token] = [];
}
this.setSocket(ctx, token);
this.success(ctx, token);
this.clients[token].push(ctx);
};
/**
* 发送消息到指定客户端
* @param {string} token 客户端token
* @param {string} method 方法名称
* @param {object} params 请求参数
* @returns {boolean} 发送是否成功
*/
Drive.prototype.send = function (token, method, params) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
var list = this.clients[token];
if (list && list.length > 0) {
var data = {
method: method,
params: params
};
for (var i = 0; i < list.length; i++) {
var ctx = list[i];
var ws = ctx.websocket;
if (ws && ws.readyState === 1) { // WebSocket.OPEN
ws.send(JSON.stringify(data));
}
}
return true;
}
return false;
};
/**
* 发送消息到所有连接的客户端
* @param {string} method 方法名称
* @param {object} params 请求参数
*/
Drive.prototype.sendAll = function (method, params) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
var data = {
method: method,
params: params
};
for (var token in this.clients) {
var list = this.clients[token];
for (var i = 0; i < list.length; i++) {
var ctx = list[i];
var ws = ctx.websocket;
if (ws && ws.readyState === 1) { // WebSocket.OPEN
ws.send(JSON.stringify(data));
}
}
}
};
/**
* 发送请求到指定客户端
* @param {string} token 客户端token
* @param {string} method 方法名称
* @param {object} params 请求参数
* @param {Function} func 回调函数
* @param {number} timeout 超时时间(毫秒)
* @returns {boolean} 发送是否成功
*/
Drive.prototype.req = function (token, method, params, func, timeout = 0) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
var list = this.clients[token];
if (list && list.length > 0) {
var data = {
id: this._genMsgId(),
method: method,
params: params
};
if (func) {
data.func = func;
data.timestamp = Date.now();
data.timeout = timeout || 3000;
// 将消息添加到驱动的消息队列
this.list_msg.push(data);
// 设置超时移除定时器
var _this = this;
data.timer = setTimeout(() => {
_this._delMsgById(data.id);
}, data.timeout);
}
for (var i = 0; i < list.length; i++) {
var ctx = list[i];
var ws = ctx.websocket;
if (ws && ws.readyState === 1) { // WebSocket.OPEN
ws.send(JSON.stringify(data));
}
}
return true;
}
return false;
};
/**
* 发送请求到所有连接的客户端
* @param {string} method 方法名称
* @param {object} params 请求参数
* @param {Function} func 回调函数
* @param {number} timeout 超时时间(毫秒)
*/
Drive.prototype.reqAll = function (method, params, func, timeout = 0) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
var data = {
id: this._genMsgId(),
method: method,
params: params
};
if (func) {
data.func = func;
data.timestamp = Date.now();
data.timeout = timeout || 3000;
// 将消息添加到驱动的消息队列
this.list_msg.push(data);
// 设置超时移除定时器
var _this = this;
data.timer = setTimeout(() => {
_this._delMsgById(data.id);
}, data.timeout);
}
for (var token in this.clients) {
var list = this.clients[token];
for (var i = 0; i < list.length; i++) {
var ctx = list[i];
var ws = ctx.websocket;
if (ws && ws.readyState === 1) { // WebSocket.OPEN
ws.send(JSON.stringify(data));
}
}
}
};
/**
* 生成消息ID
* @returns {string} 消息ID
* @private
*/
Drive.prototype._genMsgId = function () {
return 'drive_' + Date.parse(new Date()) + '_' + Math.random().toString(36).substr(2, 9);
};
/**
* 根据ID移除消息
* @param {string} msg_id 消息ID
* @private
*/
Drive.prototype._delMsgById = function (msg_id) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
for (var i = 0; i < this.list_msg.length; i++) {
if (this.list_msg[i].id === msg_id) {
// 清除定时器
if (this.list_msg[i].timer) {
clearTimeout(this.list_msg[i].timer);
}
this.list_msg.splice(i, 1);
break;
}
}
};
/**
* 处理消息回复
* @param {object} msg 回复消息
* @private
*/
Drive.prototype._handleResponse = function (msg) {
// 确保数据引用有效(热更新保护)
this._ensureRefs();
if (!msg || !msg.id) {
return;
}
for (var i = 0; i < this.list_msg.length; i++) {
let o = this.list_msg[i];
if (o.id === msg.id && o.func) {
try {
// 执行回调函数
o.func(msg);
} catch (error) {
console.error('Socket驱动回调函数执行错误:', error);
}
// 从列表中移除已处理的消息
this._delMsgById(o.id);
break;
}
}
};
/**
* 执行
* @param {string} body 正文字符串
* @param {object} ctx 请求上下文
* @param {string} token 临时访问牌
* @returns {object} 返回执行结果
*/
Drive.prototype.run = async function (body, ctx, token) {
try {
var by = body.toString();
var ws = ctx.websocket;
var json = by.toJson();
var req = ctx.request;
var request = { headers: req.headers, query: req.query, token: token };
if (!json) {
return await this.main(by, ws, request);
}
return await this._handleJson(json, ws, request);
} catch (err) {
this.log('error', 'webscoket 错误', err);
return $.ret.error(10000, '代码错误!原因:' + err.toString());
}
};
/**
* 处理 JSON 请求
* @param {object} json JSON 对象
* @param {object} ws WebSocket
* @param {object} request 请求对象
* @returns {object} 返回处理结果
* @private
*/
Drive.prototype._handleJson = async function (json, ws, request) {
var { id, method } = json;
if (json.result && id) {
return this._handleRet(json, ws);
}
if (method && this.methods[method]) {
return await this._callMethod(json, ws, request);
}
return await this.main(json, ws, request);
};
/**
* 处理结果回调
* @param {object} json JSON 对象
* @param {object} ws WebSocket
* @private
*/
Drive.prototype._handleRet = function (json, ws) {
var id = json.id;
var lt = ws.list_msg;
var len = lt.length;
for (var i = 0; i < len; i++) {
var o = lt[i];
if (id === o.id) {
o.func(json.result);
lt.splice(i, 1);
return;
}
}
};
/**
* 调用方法
* @param {object} json JSON 对象
* @param {object} ws WebSocket
* @param {object} request 请求对象
* @returns {object} 返回调用结果
* @private
*/
Drive.prototype._callMethod = async function (json, ws, request) {
var { id, method } = json;
var result = await this.methods[method](json.params, ws, request);
if (!result) {
return;
}
if (typeof (result) == 'object' && !Array.isArray(result)) {
return { id, ...result };
}
var ret = { result };
if (id) {
ret.id = id;
}
return ret;
};
/**
* 非定义函数时执行
* @param {object} body 请求正文
* @param {object} websocket Websocket服务
* @param {object} request 请求协议头
* @returns {object} 返回执行结果
*/
Drive.prototype.main = async function (body, websocket, request) {
return null;
};
/**
* 初始化函数, 用于定义开放给前端的函数
*/
Drive.prototype.init = async function () {
};
/**
* 加载完成时
*/
Drive.prototype.loadAfter = function () {
var m = this.methods;
/**
* 获取所有方法
* @param {object} params 参数
* @param {object} ws Websocket服务
* @param {object} request 请求协议头
* @returns {object} 返回执行结果
*/
m.getMethods = async function (params, ws, request) {
return Object.keys(m);
};
};
/**
* 获取插件
* @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;