UNPKG

mm_os

Version:

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

652 lines (585 loc) 16.2 kB
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;