mm_os
Version:
这是超级美眉服务端框架,用于快速构建应用程序。
668 lines (635 loc) • 15.9 kB
JavaScript
const path = require('path');
const fs = require('fs');
const Item = require('mm_machine').Item;
const Param = require('../param/drive.js'); // 是MM自带的参数机制,可以不使用
const Sql = require('../sql/drive.js'); // 是MM自带的参数机制,可以不使用
const Oauth = require('./oauth.js'); // 是MM自带的身份验证机制,基于Oauth2.0,可以不使用
const Ret = require('mm_ret').Ret;
if (!$.dict) {
if (!$.dict.session_id) {
$.dict.session_id = "mm:uuid";
}
}
/**
* Api接口驱动类
* @extends {Item}
* @class
*/
class Drive extends Item {
/**
* 构造函数
* @param {String} dir 当前目录
* @constructor
*/
constructor(dir) {
super(dir, __dirname);
this.default_file = "./api.json";
this.save_dir = './static/file/';
this.url_path = "./file/";
// oauth身份验证配置 + 函数
this.oauth;
// param参数配置 + 函数
this.param;
// sql模板配置 + 函数
this.sql;
/* 通用项 */
// 默认启用热更新
this.mode = 3;
// 配置参数
this.config = {
// 名称, 由中英文和下“_”组成, 用于修改或卸载 例如: demo
"name": "",
// 状态 0未启用,1启用
"state": 1,
// 标题, 介绍接口作用
"title": "示例接口",
// 描述, 用于描述该接口有什么用的
"description": "暂无描述",
// 路径 例如:/demo
"path": "",
// 接口类型,api表示服务端数据类型,web表示网页类型,app表示第三方应用类型
"type": "api",
// 返回的数据类型
"contentType": "json",
// 文件路径, 当调用函数不存在时,会先从文件中加载
"func_file": "",
// 回调函数名 用于决定调用脚本的哪个函数
"func_name": "",
// 请求方式, 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,
// 执行顺序, 数字越小,越优先执行
"sort": 100,
// oauth身份验证模型, 参考oauth文件
"oauth": {
"scope": true,
"signIn": false,
"vip": 0,
"gm": 0,
"mc": 0,
"user_admin": [],
"user_group": []
}
};
}
}
/**
* 新建配置
* @param {String} 文件
*/
Drive.prototype.new_config = function(file) {
var fl = __dirname + "/config.tpl.json";
if (fl.hasFile()) {
var text = fl.loadText();
if (text) {
var l = $.slash;
var arr = file.split(l);
arr = arr.slice(arr.indexOf('app'));
var app_name = arr[1];
var name = arr[arr.length - 2];
var api_name = arr[arr.length - 3];
var scope = api_name.replace('_manage', '').replace('_client', '').replace('api_', '');
if (api_name === "api_client") {
text = text.replaceAll('{path}', `/api/${name}`);
text = text.replaceAll('{name}', `${scope}_${name}`);
} else if (api_name === "api_manage") {
text = text.replaceAll('{path}', `/apis/${name}`);
text = text.replaceAll('{name}', `${scope}_${name}_manage`);
} else if (api_name.indexOf('_manage') !== -1) {
text = text.replaceAll('{path}', `/apis/${scope}/${name}`);
text = text.replaceAll('{name}', `${scope}_${name}_manage`);
} else if (api_name.indexOf('_client') !== -1) {
text = text.replaceAll('{path}', `/api/${scope}/${name}`);
text = text.replaceAll('{name}', `${scope}_${name}`);
} else {
text = text.replaceAll('{path}', `/api/${scope}/${name}`);
text = text.replaceAll('{name}', `${scope}_${name}`);
}
text = text.replaceAll('{0}', name);
file.saveText(text);
}
}
};
/**
* 加载完成后执行
*/
Drive.prototype.load_after = function() {
var cg = this.config;
this.loadParam(cg.param_path);
this.loadSql(cg.sql_path);
this.loadOauth();
};
/**
* 设置参数对象
* @param {Object} param 参数对象
*/
Drive.prototype.setParam = 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.filename === o.filename) {
$.param.list[i] = param;
has = true;
break;
}
}
if (!has) {
$.param.list.push(param);
}
}
}
};
/**
* 获取现有参数
* @param {String} file 文件名
* @return {Object} 获取的对象
*/
Drive.prototype.getParam = function(file) {
if ($.param) {
var obj = $.param.list.getObj({
filename: 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.dir);
var param = this.getParam(p);
if (param) {
this.param = param;
} else {
this.param = new Param(this.dir);
await this.param.exec('load_config', p);
this.setParam(this.param);
this.param.load('load');
}
}
};
/**
* 设置Sql
* @param {Object} param sql对象
*/
Drive.prototype.setSql = 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.filename === o.filename) {
$.sql.list[i] = sql;
has = true;
break;
}
}
if (!has) {
$.sql.list.push(sql);
}
}
}
};
/**
* 获取现有sql模板
* @param {String} file 文件名
* @return {Object} 获取的对象
*/
Drive.prototype.getSql = function(file) {
if ($.sql) {
var lt = $.sql.list;
if (lt) {
var obj = lt.getObj({
filename: 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.dir);
var sql = this.getSql(p);
if (sql) {
this.sql = sql;
} else {
this.sql = new Sql(this.dir);
await this.sql.exec('load_config', p);
this.setSql(this.sql);
this.sql.exec('load');
}
// if (this.sql && this.param && this.param.config) {
// var list = this.param.config.list;
// if (list && list.length) {
// this.sql.params = list;
// }
// }
}
};
/**
* 加载身份验证配置
* @param {Object} cg 配置对象
*/
Drive.prototype.loadOauth = function() {
var cg = this.config.oauth;
if (cg) {
this.oauth = new Oauth(this.dir);
this.oauth.loadObj(cg);
}
};
/**
* 保存文件
* @param {Object} files 上传的文件
*/
Drive.prototype.save_file = function(files) {
var file;
var url;
if (files.file) {
var f = files.file;
// 创建可读流
var stamp = Date.now();
var name = f.name || f.originalFilename;
file = ("./" + name).fullname(this.save_dir);
file.addDir();
// 创建写入流
var readStream = fs.createReadStream(f.filepath);
try {
const writeStream = fs.createWriteStream(file);
readStream.pipe(writeStream);
// writeStream.close();
url = this.url_path + name;
} catch (err) {
$.log.error('保存文件失败', err);
}
}
return {
file,
url
}
};
/* 回调函数集 */
$.get_state = 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 = Object.assign({}, 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 数据管理器
* @return {Object} 执行结果
*/
Drive.prototype.get_state = async function(ctx, db) {
var o;
// 获取请求参数
var u = ctx.session ? ctx.session.user : null;
if (u) {
o = Object.assign({}, u);
o.token = ctx.session.uuid;
} else {
var token = ctx.headers[$.dict.token];
if (token) {
u = await $.cache.get($.dict.session_id + '_' + token);
if (u) {
if (typeof(u) === "string") {
o = Object.assign({}, u.toJson());
} else if (Object.keys(u).length > 0) {
o = Object.assign({}, u);
}
if (!o.user_id) {
var obj = await $.get_state(db, token);
if (obj) {
o = Object.assign({}, obj.user);
ctx.session.user = obj.user;
o.token = ctx.session.uuid;
obj.state.token = o.token;
} else {
o = null;
}
} else {
o.token = token;
}
} else {
var obj = await $.get_state(db, token);
if (obj) {
o = Object.assign({}, obj.user);
ctx.session.user = obj.user;
o.token = ctx.session.uuid;
obj.state.token = o.token;
}
}
}
}
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 数据管理器
* @return {Object} 执行结果
*/
Drive.prototype.before = async function(ctx, db) {
if (this.config.oauth && this.config.oauth.signIn) {
db.user = await this.get_state(ctx, db);
}
};
/**
* 主要函数
* @param {Object} ctx 请求上下文
* @param {Object} db 数据管理器
* @return {Object} 执行结果
*/
Drive.prototype.main = async function(ctx, db) {
if (ret) {
console.log("是啥?", this.config.path, this.config.name, this.sql);
}
if (this.sql) {
var req = ctx.request;
// 获取文件
if (req.files) {
var fobj = this.save_file(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 数据管理器
* @return {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) {
$.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) {
$.log.error("接口错误", ctx.path, err);
}
return body;
};
/**
* 获取缓存
* @param {Object} ctx 请求上下文
* @return {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 正文参数
*/
Drive.prototype.setCache = async function(ctx, body) {
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;
return " ";
} 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} ret 设置响应结果
* @param {Object} res 响应器
* @param {String} t 请求类型
* @return {String} 处理后的响应结果
*/
Drive.prototype.body = function(ret, res, t) {
var type;
if (res.type) {
type = res.type;
}
if (ret) {
var tp = typeof(ret);
if (tp === "object") {
if (!type) {
/// 设置响应头
if (t.indexOf('xml') !== -1) {
type = t;
} else {
type = "application/json; charset=utf-8";
}
res.type = type;
} else {
if (type.indexOf('json') !== -1) {
type = "application/json; charset=utf-8";
} else if (type.indexOf('html') !== -1) {
type = "text/html";
} else if (type.indexOf('xml') !== -1) {
type = "text/xml; charset=utf-8";
} else {
type = "text/plain; charset=utf-8";
}
res.type = type;
}
if (type.indexOf('/xml') !== -1) {
return $.toXml(ret);
} else {
return JSON.stringify(ret);
}
} else if (tp === "string") {
ret = ret.trim();
if (!type) {
if (ret.startWith('{') && ret.endWith('}')) {
res.type = "application/json; charset=utf-8";
} else if (ret.startWith('[') && ret.endWith(']')) {
res.type = "application/json; charset=utf-8";
} else if (ret.endWith("</html>")) {
res.type = "text/html";
} else {
res.type = "text/plain; charset=utf-8";
}
}
}
}
return ret;
};
/**
* 验证参数
* @param {Object} query url参数
* @param {Object} body 内容参数
* @param {String} method 方法
* @return {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 内容上下文
* @return {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 内容上下文
* @return {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 plus;
var l = $.slash;
if (!app) {
app = this.filename.between("app" + l, l);
}
if (!name) {
name = this.filename.between("plugin" + l, l);
}
var plugins = $.pool.plugin[app];
if (plugins) {
plus = plugins.get(name);
}
return plus
}
module.exports = Drive;