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