UNPKG

mm_os

Version:

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

708 lines (670 loc) 17.5 kB
const fs = require('fs'); const Item = require('mm_machine').Drive; const Param = require('../param/drive.js'); // 是MM自带的参数机制,可以不使用 const Sql = require('../sql/drive.js'); // 是MM自带的参数机制,可以不使用 const Oauth = require('./oauth.js'); // 是MM自带的身份验证机制,基于Oauth2.0,可以不使用 if (!$.dict) { if (!$.dict.session_id) { $.dict.session_id = 'mm:uuid'; } } /** * Api接口驱动类 * @augments {Item} * @class */ class Drive extends Item { static config = { // 应用名称 例如:demo_app 'app': 'server', // 插件名称 例如:demo_plugin 'plugin': 'sys', // 路径 例如:/demo 'path': '', // 接口类型,api表示服务端数据类型,web表示网页类型,app表示第三方应用类型 'type': 'api', // 请求方式, POST或GET, 为空或ALL表示都可以 'method': 'ALL', // 缓存时长 (单位:秒) 默认:60秒,建议600秒 'cache': 0, // 是否用客户端缓存,即 http 304 状态 'client_cache': false, // param参数验证路径 例如: ./param.json 'param_path': '', // sql模板路径 例如: ./sql.json 'sql_path': '', // 是否验证参数 'check_param': true, // oauth身份验证模型, 参考oauth文件 'oauth': { 'scope': true, 'sign_in': false, 'vip': 0, 'gm': 0, 'mc': 0, 'user_admin': [], 'user_group': [] } }; /** * 构造函数 * @param {object} config 配置项 * @param {object} parent 父对象 * @class */ constructor(config, parent) { super({ ...Drive.config, ...config }, parent); /* 通用项 */ // 默认启用热更新 this.mode = 3; } } /** * 预设配置 */ Drive.prototype._preset = function () { this.save_dir = './static/file/'; this.url_path = '/file/'; // oauth身份验证配置 + 函数 this.oauth = null; // param参数配置 + 函数 this.param = null; // sql模板配置 + 函数 this.sql = null; var cg = this.config; this._loadParam(cg.param_path); this._loadSql(cg.sql_path); this._loadOauth(); }; /** * 设置参数对象 * @param {object} param 参数对象 */ Drive.prototype.updateParam = function (param) { if ($.param) { var lt = $.param.list; if (lt) { var has = false; var len = lt.length; for (var i = 0; i < len; i++) { var o = lt[i]; if (param.config_file === o.config_file) { $.param.list[i] = param; has = true; break; } } if (!has) { $.param.list.push(param); } } } }; /** * 获取现有参数 * @param {string} file 文件名 * @returns {object} 获取的对象 */ Drive.prototype.getParam = function (file) { if ($.param) { var obj = $.param.list.getObj({ config_file: file }); if (obj) { return obj; } } return null; }; /** * 加载参数 * @param {string} file_path 文件路径 */ Drive.prototype._loadParam = async function (file_path) { if (file_path) { var p = file_path.fullname(this.getDir()); var param = this.getParam(p); if (param) { this.param = param; } else { this.param = new Param({}, this); await this.param.call('loadConfig', p); this.updateParam(this.param); this.param.do('load'); } } }; /** * 设置Sql * @param {object} sql sql对象 * @param {string} sql.config_file 文件名 * @param {object} sql.sql sql语句 */ Drive.prototype.updateSql = function (sql) { if ($.sql) { var lt = $.sql.list; if (lt) { var has = false; var len = lt.length; for (var i = 0; i < len; i++) { var o = lt[i]; if (sql.config_file === o.config_file) { $.sql.list[i] = sql; has = true; break; } } if (!has) { $.sql.list.push(sql); } } } }; /** * 获取现有sql模板 * @param {string} file 文件名 * @returns {object} 获取的对象 */ Drive.prototype.getSql = function (file) { if ($.sql) { var lt = $.sql.list; if (lt) { var obj = lt.getObj({ config_file: file }); if (obj) { return obj; } } } return null; }; /** * 加载sql模板 * @param {string} file_path 文件路径 */ Drive.prototype._loadSql = async function (file_path) { if (file_path) { var p = file_path.fullname(this.getDir()); var sql = this.getSql(p); if (sql) { this.sql = sql; } else { this.sql = new Sql({}, this); await this.sql.call('loadConfig', p); this.updateSql(this.sql); this.sql.call('loadScript'); } } }; /** * 加载身份验证配置 */ Drive.prototype._loadOauth = function () { var cg = this.config.oauth; if (cg) { this.oauth = new Oauth(this.getDir()); this.oauth.loadObj(cg); } }; /** * 保存文件 * @param {object} files 上传的文件 * @param {string} files.file 上传的文件对象 * @returns {object} 返回文件路径和URL */ Drive.prototype.saveFile = function (files) { var file; var url; if (files.file) { var f = files.file; // 创建可读流 var name = f.name || f.originalFilename; file = ('./' + name).fullname(this.save_dir); file.addDir(); // 创建写入流 var read_stream = fs.createReadStream(f.filepath); try { let write_stream = fs.createWriteStream(file); read_stream.pipe(write_stream); url = this.url_path + name; } catch (err) { this.log('error', '保存文件失败', err); } } return { file, url }; }; /* 回调函数集 */ $.getState = async function (db, token) { var db1 = db.new('user_state', 'user_id'); var state = await db1.getObj({ token }, null, null, false); if (!state) { return null; } var now = new Date(); if (state.time_sign.interval(now, 'day') <= 30) { db.table = 'user_account'; var user = await db.getObj({ user_id: state.user_id }); var o = { ...user }; o.password_pay = o.password_pay ? '******' : ''; o.password = o.password ? '******' : ''; delete o.salt; delete o.time_create; return { state, user: o }; } else { return null; } }; /** * 获取登录态 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object} 执行结果 */ Drive.prototype.getState = async function (ctx, db) { var o = await this._getUserFromSession(ctx); if (!o) { o = await this._getUserFromToken(ctx, db); } return this._processUserInfo(o); }; /** * 从 session 中获取用户信息 * @param {object} ctx 请求上下文 * @returns {object|null} 用户信息 * @private */ Drive.prototype._getUserFromSession = function (ctx) { var u = ctx.session?.user; if (u) { var o = { ...u }; o.token = ctx.session.uuid; return o; } return null; }; /** * 从 token 中获取用户信息 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object|null} 用户信息 * @private */ Drive.prototype._getUserFromToken = async function (ctx, db) { var token = ctx.headers[$.dict.token]; if (!token) { return null; } var u = await $.cache.get($.dict.session_id + '_' + token); if (u) { return await this._processUserFromCache(u, ctx, db, token); } return await this._getUserFromDb(token, ctx, db); }; /** * 处理缓存中的用户信息 * @param {object|string} u 缓存中的用户信息 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @param {string} token token * @returns {object|null} 用户信息 * @private */ Drive.prototype._processUserFromCache = async function (u, ctx, db, token) { var o; if (typeof (u) === 'string') { o = { ...u.toJson() }; } else if (Object.keys(u).length > 0) { o = { ...u }; } if (!o) { return null; } if (!o.user_id) { return await this._getUserFromDb(token, ctx, db); } o.token = token; return o; }; /** * 从数据库中获取用户信息 * @param {string} token token * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object|null} 用户信息 * @private */ Drive.prototype._getUserFromDb = async function (token, ctx, db) { var obj = await $.getState(db, token); if (obj) { var o = { ...obj.user }; ctx.session.user = obj.user; o.token = ctx.session.uuid; obj.state.token = o.token; return o; } return null; }; /** * 处理用户信息 * @param {object|null} o 用户信息 * @returns {object|null} 处理后的用户信息 * @private */ Drive.prototype._processUserInfo = function (o) { if (o) { o.password_pay = o.password_pay ? '******' : ''; o.password = o.password ? '******' : ''; delete o.salt; delete o.time_create; } return o; }; /** * 获取登录态 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object} 执行结果 */ Drive.prototype.before = async function (ctx, db) { if (this.config.oauth && this.config.oauth.sign_in) { db.user = await this.getState(ctx, db); } }; /** * 主要函数 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object} 执行结果 */ Drive.prototype.main = async function (ctx, db) { if (this.sql) { var req = ctx.request; // 获取文件 if (req.files) { var fobj = this.saveFile(req.files); req.body.file = fobj.file; req.body.url = fobj.url; } var ret = await this.sql.run(req.query, req.body, db); return ret; } else { return $.ret.error(10000, '接口函数未定义'); } }; /** * 调用函数 * @param {object} ctx 请求上下文 * @param {object} db 数据管理器 * @returns {object} 执行结果 */ Drive.prototype.run = async function (ctx, db) { try { var body = await this.getCache(ctx); if (!body) { var req = ctx.request; var md = this.config['method'].toLocaleUpperCase(); if (md !== req.method && md !== 'ALL') { return null; } await this.before(ctx, db); var ret = await this.check(ctx, db); if (!ret) { try { ret = await this.main(ctx, db); } catch (err) { this.log('error', '脚本文件错误', req.path, err); ret = $.ret.error(10000, '脚本文件错误:' + err.toString()); } } var res = ctx.response; body = this.body(ret, res, req.type); this.setCache(ctx, body, res.type); } } catch (err) { this.log('error', '接口错误', ctx.path, err); } return body; }; /** * 获取缓存 * @param {object} ctx 请求上下文 * @returns {string} 响应内容 */ Drive.prototype.getCache = async function (ctx) { var cg = this.config; if (cg.cache) { var req = ctx.request; if (cg.client_cache) { var etag = req.headers['if-none-match']; if (etag) { var stamp = number(etag.replace('"', '').replace('"', '')); var cha = stamp * 1000 - Date.parse(new Date()); if (cha > 0) { ctx.response.status = 304; return ' '; } } } else { var data = await $.cache.get('api_' + req.url); if (data) { var obj = JSON.parse(data); ctx.response.type = obj.type; return obj.body; } } } return null; }; /** * 设置缓存 * @param {object} ctx 请求上下文 * @param {object} body 正文参数 * @param {string} type 响应类型 */ Drive.prototype.setCache = async function (ctx, body, type = null) { var cg = this.config; if (cg.cache && ctx.method === 'GET') { if (cg.client_cache) { var age = cg.cache * 60; ctx.set('Cache-Control', 'max-age=' + age); ctx.etag = Date.parse(new Date()) / 1000 + age; } else { var req = ctx.request; var o = {}; o.body = body; o.type = ctx.response.type; $.cache.set('api_' + req.url, JSON.stringify(o), cg.cache * 60); } } }; /** * 调用函数 * @param {object} result 设置响应结果 * @param {object} res 响应器 * @param {string} t 请求类型 * @returns {string} 处理后的响应结果 */ Drive.prototype.body = function (result, res, t) { var ret = result; var type = res.type; if (!ret) { return ret; } var tp = typeof (ret); if (tp === 'object') { type = this._handleObjectType(res, type, t); return this._convertObject(ret, type); } else if (tp === 'string') { ret = ret.trim(); this._handleStringType(res, type, ret); } return ret; }; /** * 处理对象类型 * @param {object} res 响应器 * @param {string} type 类型 * @param {string} t 请求类型 * @returns {string} 处理后的类型 * @private */ Drive.prototype._handleObjectType = function (res, type, t) { var new_type = type; if (!new_type) { new_type = t.indexOf('xml') !== -1 ? t : 'application/json; charset=utf-8'; res.type = new_type; } else { new_type = this._normType(new_type); res.type = new_type; } return new_type; }; /** * 标准化类型 * @param {string} type 类型 * @returns {string} 标准化后的类型 * @private */ Drive.prototype._normType = function (type) { if (type.indexOf('json') !== -1) { return 'application/json; charset=utf-8'; } else if (type.indexOf('html') !== -1) { return 'text/html'; } else if (type.indexOf('xml') !== -1) { return 'text/xml; charset=utf-8'; } return 'text/plain; charset=utf-8'; }; /** * 转换对象 * @param {object} obj 对象 * @param {string} type 类型 * @returns {string} 转换后的字符串 * @private */ Drive.prototype._convertObject = function (obj, type) { return type.indexOf('/xml') !== -1 ? $.toXml(obj) : JSON.stringify(obj); }; /** * 处理字符串类型 * @param {object} res 响应器 * @param {string} type 类型 * @param {string} str 字符串 * @private */ Drive.prototype._handleStringType = function (res, type, str) { if (!type) { if ((str.startsWith('{') && str.endsWith('}')) || (str.startsWith('[') && str.endsWith(']'))) { res.type = 'application/json; charset=utf-8'; } else if (str.endsWith('</html>')) { res.type = 'text/html'; } else if (str.startsWith('<?xml')) { res.type = 'text/xml; charset=utf-8'; } else { res.type = 'text/plain; charset=utf-8'; } } }; /** * 验证参数 * @param {object} query url参数 * @param {object} body 内容参数 * @param {string} method 方法 * @returns {object} 验证结果 */ Drive.prototype.checkParam = function (query, body, method) { if (this.param) { var msg = this.param.check(query, body, method); if (msg) { var code; if (msg.indexOf('必须') !== -1 || msg.indexOf('为空') !== -1) { code = 30001; } else { code = 30002; } return $.ret.error(code, msg); } } return null; }; /** * 验证参数 * @param {object} ctx 内容上下文 * @returns {object} 验证结果 */ Drive.prototype.check = async function (ctx) { var error; if (this.config.check_param) { var req = ctx.request; error = this.checkParam(req.query, req.body, req.method); } if (!error) { error = await this.checkOauth(ctx); if (error) { error = { error }; } } return error; }; /** * 验证身份 * @param {object} ctx 内容上下文 * @returns {object} 验证结果 */ Drive.prototype.checkOauth = async function (ctx) { var aouth = this.oauth; if (aouth) { return await aouth.check(ctx); } else { return null; } }; /** * 获取插件 * @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; model.path = '/' + app_name + '/' + name; return model; }; module.exports = Drive;