UNPKG

mm_os

Version:

这是超级美眉服务端框架,用于快速构建应用程序。

1,087 lines (1,026 loc) 27.3 kB
const Item = require('mm_machine').Item; const Excel = require('mm_excel'); /** * Sql操作驱动类 * @extends {Item} * @class */ class Drive extends Item { /** * 构造函数 * @param {String} dir 当前路径 * @constructor */ constructor(dir) { super(dir, __dirname); this.default_file = "./sql.json"; // 保存文件目录 this.save_dir = './file/'; // 读取文件目录 this.url_path = "/file/"; // 默认启用热更新 this.mode = 3; /* 通用项 */ this.params = null; // 配置参数 this.config = { // 名称, 由中英文和下“_”组成, 用于修改或卸载 例如: demo "name": "", // 状态 0未启用,1启用 "state": 1, // 表名 {0} 代表可前端传参自定义查询的表 "table": "{0}", // 主键 用于水平连表查询时 例如:id "key": "", // 排序 {0} 代表可前台传参自定义排序规则 格式: `name` asc, `id` desc "orderby": "{0}", // 默认排序 `id` desc "orderby_default": "", // 显示的字段 {0} 代表可前台传参自定义查询的字段, 例如: `id`,`username`,`name`,`email` "field": "{0}", // 默认显示字段 例如: `id`,`username`,`name` "field_default": "*", // 隐藏字段 有些字段即使前端请求也不能返回,这是通过隐藏字段将其过滤掉, 例如:password *表示包含匹配 "field_hide": ["*password*", "*token*", "salt"], // 分页大小,默认一页显示条数 "page_size": 30, /* 过滤参数 */ "filter": { /** * 表名 */ "table": "table", /** * 查询的页码 */ "page": "page", /** * 查询每页条数 */ "size": "size", /** * 操作方式: 传入参数method=add, 支持参数 add增、del删、set改、get查,为空则为get */ "method": "method", /** * 排序 */ "orderby": "orderby", /** * 查询显示的字段 */ "field": "field", /** * 统计结果: 统计符合条件的结果数,只有当page等于1或0时才会统计 */ "count_ret": "count_ret" }, // 分隔符 用于查询时的多条件处理 "separator": "|", // 支持的方法 add增、del删、set改、get查, 只填get表示只支持查询 // import export del_repeat", "method": "add del set get get_obj import export del_repeat avg sum count update", // sql查询语句 "query": {}, // 默认查询, 当查询条件中不包含该项时,默认添加该项。 例如: { "age": "`age` < 20" } , 当查询参含有age,不调用该项,不存在时,sql会增加该项 "query_default": {}, // sql更改语句 "update": {}, // 默认添加条件,当不包含该项时,默认添加该项。 例如: { "age": "`age` += 1" } , 当查询参含有age,不调用该项,不存在时,sql会增加该项 "body_default": {}, // 文件路径, 当调用函数不存在时,会先从文件中加载 "func_file": "", // 回调函数名 用于决定调用脚本的哪个函数 "func_name": "", // 参数 [] "params": null, // 格式 "format": [ /* { // 表名,当选择转换方式 表转换时需填写 "table": "mm_web_region", // 查询条件,用于加速转换 "query": { "group": "市" }, // 表标题名 "title": "所属省份", // 转换ID "id": "province_id", // 转换主键 "key": "province", // 取名 "name": "name", // 列表 "list": [{ "province_id": 1, "name": "广东省" }, { "province_id": 2, "name": "广西省" }, { "province_id": 3, "name": "湖南省" } ] }, { "title": "是否可见", "key": "show", "list": ["否", "是"] } */ ], /* 去重 */ "del_repeat": { // 判断重复的字段,例如字段名 number "groupBy": "", // 排序方式 例如: `diJia` ASC "orderBy": "" }, /* 逻辑符 */ "logic": {}, // 输出sql语句 "log": false }; } } /** * 执行前, 可用于过滤参数 * @param {Object} params 参数对象 (object) 包含query和body 如{ query, body } * @param {Object} db 数据库管理器 * @return {Object} 过滤后的参数 */ Drive.prototype.before = async function(params, db) { return params; }; /** * 验证, 用于判断是否执行 * @param {Object} params 参数对象 (object) 包含query和body * @param {Object} db 数据管理器 * @return {Boolean} 验证通过返回true, 失败返回false */ Drive.prototype.check = async function(params, db) { return true; }; /** * @return {type} 执行后 可用于附加执行 * @param {Object} params 参数对象 (object) 包含query和body * @param {Object} db 数据管理器 * @return {Object} 最终执行结果 */ Drive.prototype.after = async function(params, db) { return db.ret; }; /** * 更新缓存 * @param {Object} sql */ Drive.prototype.update_cache = async function(table, sql) { }; /** * @ 执行修改 * @param {Object} query 查询url参数 * @param {Object} body 修改时置入body的参数 * @param {Object} db 数据库管理器 */ Drive.prototype.run = async function(query, body, db) { var params = { query, body }; params = await this.before(params, db); if (this.check(params, db)) { var ret = await this.main(params, db); if (!ret.error) { db.ret = ret; return await this.after(params, db); } else { return ret; } } return null; }; /** * SQL操作准备 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回准备参数 */ Drive.prototype.ready = function(db, query, body) { var cg = this.config; var qy = Object.assign({}, query); $.push(db.config.filter, cg.filter, true); db.filter(qy); return { cg, qy }; }; /** * 转为查询条件字符串 * @param {Object} db 数据库管理器 * @param {Object} query 查寻条件 * @param {Object} method 方法 * @return {String} 返回查询条件 */ Drive.prototype.to_where = function(db, query, method) { var { cg, qy } = this.ready(db, query, {}); db.config.separator = cg.separator; if (!query.size && cg.page_size) { db.size = cg.page_size + 0; } if (db.size > 0 && db.page === 0) { db.page = 1; } var f = db.config.filter; // 设置查询字段 var field = query[f.field]; if (cg.field.has("*{0}*")) { if (field) { db.field = cg.field.replace("{0}", field); } else if (method === 'get_obj' && cg.field_obj) { db.field = cg.field_obj + ''; } else if (cg.field_default) { db.field = cg.field_default + ''; } } else { db.field = cg.field + ''; if (field) { qy[f.field] = field; } } // 设置排序方式 var orderby = query[f.orderby]; if (cg.orderby.has("*{0}*")) { if (orderby) { db.orderby = cg.orderby.replace("{0}", orderby); } else if (cg.orderby_default) { db.orderby = cg.orderby_default + ''; } } else { db.orderby = cg.orderby + ''; if (orderby) { qy[f.orderby] = orderby; } } var query_str = db.tpl_query(qy, cg.query); var qt = cg.query_default; if (Object.keys(qt).length > 0) { var id = $.dict.user_id; var id_key = "{" + id + "}"; var user_id = "0"; var user = db.user; if (user && user[id]) { user_id = user[id]; } for (var k in qt) { if (!qy[k]) { query_str += " && " + qt[k].replaceAll(id_key, user_id); } } if (query_str.startsWith(" && ")) { query_str = query_str.replace(" && ", ""); } } return query_str; } /** * 查询(主要) * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} method 方法 * @return {Object} 返回查询结果 */ Drive.prototype.get_main = async function(db, query, method) { var ret; if (this.config.field.has("*{0}*")) { var field = query[db.config.filter.field]; if (field && this.config.field_hide.getMatch(field)) { return $.ret.error(70003, '不合法的查询参数'); } } var query_str = this.to_where(db, query, method); // 查询 if (db.count_ret === "true") { ret = $.ret.body(await db.getCountSql(query_str, db.orderby, db.field)); } else { ret = $.ret.list(await db.getSql(query_str, db.orderby, db.field)); } // if (db.error) { // $.log.error('查询SQL', db.sql, db.error); // } return ret; }; /** * 查询 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回查询结果 */ Drive.prototype.get = async function(db, query, body) { return await this.get_main(db, query); }; /** * 查询单条数据 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回查询结果 */ Drive.prototype.get_obj = async function(db, query, body) { query.page = 1; query.size = 1; var ret = await this.get_main(db, query, 'get_obj'); if (ret.result) { var list = ret.result.list; if (list.length > 0) { return $.ret.obj(list[0]); } else { return $.ret.obj(null); } } return ret; }; /** * 修改(主要) * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回修改结果 */ Drive.prototype.set_main = async function(db, query, body) { var ret; var { cg, qy } = this.ready(db, query, body); var key = cg.key; if (body[key]) { qy[key] = body[key]; } var query_str = db.tpl_query(qy, cg.query); var qt = cg.query_default; if (Object.keys(qt).length > 0) { var id = $.dict.user_id; var id_key = "{" + id + "}"; var user_id = "0"; var user = db.user; if (user && user[id]) { user_id = user[id]; } for (var k in qt) { if (!qy[k]) { query_str += " && " + qt[k].replace(id_key, user_id); } } if (query_str.startsWith(" && ")) { query_str = query_str.replace(" && ", ""); } } var set_str = db.tpl_body(body, cg.update); var n = await db.setSql(query_str, set_str); if (n < 1) { ret = $.ret.error(500, '修改失败!\n' + db.error.message); $.log.error('修改SQL', db.sql, db.error); } else { ret = $.ret.bl(true, '修改成功!'); } return ret; }; /** * 修改 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回查询结果 */ Drive.prototype.set = async function(db, query, body) { return await this.set_main(db, query, body); }; /** * 添加(主要) * @param {Object} db 数据库操作类 * @param {Object} body 添加项 * @return {Object} 返回查询结果 */ Drive.prototype.add_main = async function(db, body) { var ret; var cg = this.config; if (Object.keys(body).length > 0) { var bt = cg.body_default; if (Object.keys(bt).length > 0) { var id = $.dict.user_id; var id_key = "{" + id + "}"; var user_id = "0"; var user = db.user; if (user && user[id]) { user_id = user[id]; } for (var k in bt) { if (!body[k]) { var str = bt[k].replace(id_key, user_id); var key = str.left("=").trim("`"); body[key] = str.right("=").trim("'"); } } } var n = await db.add(body); if (n < 1) { ret = $.ret.error(500, '添加失败!\n' + db.error.message); $.log.error('添加SQL', db.sql, db.error); } else { ret = $.ret.bl(true, '添加成功!'); } } else { ret = $.ret.error(30001, '参数不能为空'); } if (cg.log) { $.log.debug('添加SQL语句', db.sql) } return ret; }; /** * 添加 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回添加结果 */ Drive.prototype.add = async function(db, query, body) { return await this.add_main(db, body); }; /** * 删除(主要) * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @return {Object} 返回查询结果 */ Drive.prototype.del_main = async function(db, query) { var ret; var { cg, qy } = this.ready(db, query, {}); var query_str = db.tpl_query(qy, cg.query); var bl = await db.delSql(query_str); if (bl < 1) { ret = $.ret.error(500, '删除失败!\n' + db.error.message); $.log.error('删除SQL', db.sql, db.error); } else { ret = $.ret.bl(true, '删除成功!'); } if (cg.log) { $.log.debug('删除SQL语句', db.sql) } return ret; }; /** * 删除 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回删除结果 */ Drive.prototype.del = async function(db, query, body = {}) { return await this.del_main(db, Object.assign({}, query, body)); }; /** * 添加或修改(主要) * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回修改结果 */ Drive.prototype.addOrSet_main = async function(db, query, body) { var ret; var { cg, qy } = this.ready(db, query, body); if (Object.keys(body).length > 0 && Object.keys(qy).length > 0) { var n = await db.addOrSet(qy, body); if (n < 1) { ret = $.ret.error(500, '操作失败!\n' + db.error.message); $.log.error('添加或修改SQL', db.sql, db.error); } else { ret = $.ret.bl(true, '操作成功!'); } } else { ret = $.ret.error(30001, '参数不能为空'); } if (cg.log) { $.log.debug('添加或修改SQL语句', db.sql) } return ret; }; /** * 添加或修改 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回查询结果 */ Drive.prototype.addOrSet = async function(db, query, body) { return await this.addOrSet_main(db, query, body); }; /** * 获取数据类型 * @param {String} dataType 数据库数据类型 * @returns {String} 返回数据类型 */ Drive.prototype.get_type = function(dataType) { if (dataType.indexOf('int') !== -1) { return 'number' } else { return 'string' } } /** * 获取数据 * @param {Array} fields 要获取的字段数组 * @return {Array} 返回参数信息列表 */ Drive.prototype.get_params = async function(db, fields) { var cg = this.config; if (cg.params) { return cg.params; } if (this.params) { return this.params; } var lt = []; if ($.pool.db) { var pool = $.pool.db['sys']; if (pool) { var dt = pool.get(cg.table); if (dt && dt.config) { var list = dt.config.fields; for (var i = 0; i < list.length; i++) { var o = list[i]; var name = o.replace(/`/g, ''); lt.push({ name, title: name }); } } } } if (!lt.length) { db.table = cg.table; var list = await db.fields(); for (var i = 0; i < list.length; i++) { var o = list[i]; var name = o.name; var note = o.note ? o.note.replace(":", ":") : name; lt.push({ name, title: note.left(":", true), type: this.get_type(o.type) }); } } if (!fields) { fields = cg.field_default; } if (fields && fields !== "*") { var arr = fields.replace(/`/g, "").split(','); var params = []; arr.map((name) => { var obj = lt.getObj({ name }); if (obj) { params.push(obj); } }); } else { params = lt; } return params; }; /** * 获取导入导出格式 * @param {Object} db 数据库操作类 * @return {Array} 格式列表 */ Drive.prototype.get_format = async function(db) { var dbs = Object.assign({}, db); dbs.size = 0; var fmt = this.config.format; for (var i = 0; i < fmt.length; i++) { var o = fmt[i]; if (o.table) { if (!o.list || o.list.length == 0) { dbs.table = o.table; if (!o.id) { o.id = o.key; } o.list = await dbs.getSql(o.where, null, o.id + "," + o.name); if (o.id !== o.key) { o.list.map((m) => { m[o.key] = m[o.name]; return m; }); } } } } return fmt; }; /** * 导入数据(主要) * @param {Object} db 数据库操作类 * @param {String} file 文件名 * @return {Object} 返回导入结果 */ Drive.prototype.import_main = async function(db, file) { var params = await this.get_params(db); var format = await this.get_format(db); if (file.indexOf($.runPath) !== 0) { file = file.replace(this.url_path, this.save_dir); var path; if ($.config.path) { path = $.config.path.user || $.config.path.static || "/static/"; } else { path = "./static/"; } file = file.fullname(path); } if (!file.hasFile()) { return $.ret.error(30001, file + "文件不存在!"); } var jarr = []; if (file.endsWith(".json")) { jarr = file.loadJson(); } else if (file.endsWith(".xml")) { jarr = file.loadXml(); } else { var excel = new Excel({ file, params, format }); try { jarr = await excel.load(); } catch (e) { $.log.error("导入文件", e); } finally { excel.clear(); excel = null; } } var list = []; var errors = []; var list_error = []; db.table = db.table || this.config.table; if (!jarr.length) { return $.ret.error(10000, '要导入的数据不能为空!'); } var key = this.config.key; if (jarr[0][key]) { for (var i = 0; i < jarr.length; i++) { var o = jarr[i]; var qy = {}; qy[key] = o[key]; var n = await db.addOrSet(qy, o); if (n < 1) { errors.push(db.error); list_error.push(o); } else { list.push(o); } } } else { for (var i = 0; i < jarr.length; i++) { var o = jarr[i]; var n = await db.add(o); if (n < 1) { errors.push(db.error); list_error.push(o); } else { list.push(o); } } } var bl = list.length === jarr.length; var body = $.ret.bl(bl, bl ? '导入成功!' : '导入失败!'); body.result.list = list; if (errors.length) { body.result.list_error = list_error; body.result.errors = errors; } return body; }; /** * 导入数据 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 导入设置 * @return {Object} 返回执行结果 */ Drive.prototype.import = async function(db, query, body) { var params = Object.assign({}, query, body); if (!params.file) { return $.ret.error(60000, '文件名(file)参数不能为空'); } return await this.import_main(db, params.file); }; /** * 导出数据(主要) * @param {Object} db 数据库操作类 * @param {Object} query 查询参数 * @param {Object} body 正文参数(导出设置) * @property {String} body.fields 需要导出的字段 例如: `username`,`gm`,`vip` * @property {String} body.file 文件名 例如: 用户名.xlsx 、用户信息.csv 、用户账户.xls * @property {String} body.path 文件路径 例如: /static/download, 可不填写 * @return {Object} 返回执行结果 */ Drive.prototype.export_main = async function(db, query, body) { var by = await this.get_main(db, query); var message = ""; if (db.error) { message = db.error.message; return $.ret.error(10000, message); } var { path, file, fields } = body; var table = db.table || this.config.table; var date = new Date(); var name = table + "_" + date.stamp() + '.xlsx'; if (!path) { if ($.config.path) { path = $.config.path.user || $.config.path.static || "/static/"; } else { path = "./static/"; } } if (!file) { file = this.save_dir + name; } if (!fields) { if (query.field) { fields = query.field; } else { var f = this.config.field_default; if (f.length !== "*") { fields = f; } } } var params = await this.get_params(db, fields); var format = await this.get_format(db); file = file.fullname(path); file.addDir(); var excel = new Excel({ file, params, format }); var list = by.result.list; try { file = await excel.save(list); } catch (e) { $.log.error("导出保存文件失败!", e); } finally { excel.clear(); excel = null; } var body = $.ret.bl(!!file, file ? '导出成功!' : '导出失败!'); body.result.file = file; body.result.url = this.url_path + name; if (message) { body.result.message = message; } return body; }; /** * 导出数据 * @param {Object} db 数据库操作类 * @param {Object} query 查询条件 * @param {Object} body 修改项 * @return {Object} 返回执行结果 */ Drive.prototype.export = async function(db, query, body) { return await this.export_main(db, query, body); }; /** * 删除重复项(主要) * @param {Object} db 数据库管理器 * @param {Object} params 查询参数 * @return {Object} 返回执行结果 */ Drive.prototype.del_repeat_main = async function(db, params) { var msg = ''; var cg = this.config.del_repeat; var orderBy = params.orderby || cg.orderBy; delete params.orderby; var groupBy = params.groupby || cg.groupBy; delete params.groupby; var f = db.config.filter; // 设置查询字段 var field = params[f.field] || groupBy; delete params[f.field]; db.field = field; var sql = db.toGetSql(params).replace(' * ', ' `' + field + '`, count(*) as len '); sql += ` GROUP BY ${groupBy}`; sql = 'SELECT * FROM (' + sql + ') a WHERE len > 1'; var list = await db.run(sql); if (list.length) { db.page = 1; db.size = 1; var key = this.config.key; for (var i = 0; i < list.length; i++) { var o = list[i]; var len = o.len - 1; delete o.len; for (var n = 0; n < len; n++) { var obj = await db.getObj(o, orderBy, key); if (obj) { await db.del(obj); } } } } else { msg = '没有重复项。'; } return $.ret.bl(!msg, msg ? '去重失败!原因:' + msg : '去重成功!'); }; /** * 删除重复项 * @param {Object} db 数据库管理器 * @param {Object} query 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.del_repeat = async function(db, query, body) { var pm = Object.assign({}, query, body); return await this.del_repeat_main(db, pm); }; /** * 执行模板操作 * @param {Object} params 参数对象 (object) 包含query和body * @param {Object} db 数据管理器 * @return {Object} 返回执行结果 */ Drive.prototype.main = async function(params, db) { var { query, body } = params; var cg = this.config; var method = query.method; if (!method) { method = "get"; } if (!cg.method.has("*" + method + "*")) { return $.ret.error(50001, '不支持的操作方式') } var qy = Object.assign({}, query); delete qy.method; if (this[method]) { db.method = method; // 过滤查询参数 var f = cg.filter; var table = query[f.table]; // 设置操作的数据表 if (cg.table.has("*{0}*")) { if (table) { db.table = cg.table.replace("{0}", table); } else { return $.ret.error(30001, '表名不能为空'); } } else { db.table = cg.table + ''; } return await this[method](db, qy, Object.assign({}, body)); } else { return $.ret.error(50001, '不支持的操作方式'); } }; /** * 总计 * @param {Object} db 数据库管理器 * @param {Object} pm 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.sum_main = async function(db, pm) { var ret; var orderby = pm.orderby || ""; delete pm.orderby; var groupby = pm.groupby; delete pm.groupby; var f = db.config.filter; var field = pm[f.field]; delete pm[f.field]; var query_str = this.to_where(db, pm, "get_list"); if (!groupby || !field) { ret = $.ret.error(30000, "参数groupby、field是必须的,且值不能为空!"); } else { var list = await db.groupSumSql(query_str, groupby, field, orderby); if (!list.length && db.error) { // $.log.error('SUM查询SQL', db.sql, db.error); ret = $.ret.body(db.error); } else { ret = $.ret.list(list); } } return ret; }; /** * 总计 * @param {Object} db 数据库管理器 * @param {Object} query 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.sum = async function(db, query, body) { var pm = Object.assign({}, query, body); return await this.sum_main(db, pm); }; /** * 平均值 * @param {Object} db 数据库管理器 * @param {Object} pm 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.avg_main = async function(db, pm) { var ret; var orderby = pm.orderby || ""; delete pm.orderby; var groupby = pm.groupby; delete pm.groupby; var f = db.config.filter; var field = pm[f.field]; delete pm[f.field]; var query_str = this.to_where(db, pm, "get_list"); if (!groupby || !field) { ret = $.ret.error(30000, "参数groupby、field是必须的,且值不能为空!"); } else { var list = await db.groupAvgSql(query_str, groupby, field, orderby); if (!list.length && db.error) { // $.log.error('AVG查询SQL', db.sql, db.error); ret = $.ret.body(db.error); } else { ret = $.ret.list(list); } } return ret; }; /** * 平均值 * @param {Object} db 数据库管理器 * @param {Object} query 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.avg = async function(db, query, body) { var pm = Object.assign({}, query, body); return await this.avg_main(db, pm); }; /** * 总计 * @param {Object} db 数据库管理器 * @param {Object} pm 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.count_main = async function(db, pm) { var ret; var orderby = pm.orderby || ""; delete pm.orderby; var groupby = pm.groupby; delete pm.groupby; var f = db.config.filter; var field = pm[f.field]; delete pm[f.field]; var query_str = this.to_where(db, pm, "get_list"); if (!groupby || !field) { ret = $.ret.error(30000, "参数groupby、field是必须的,且值不能为空!"); } else { var list = await db.groupCountSql(query_str, groupby, field, orderby); if (!list.length && db.error) { // $.log.error('COUNT查询SQL', db.sql, db.error); ret = $.ret.body(db.error); } else { ret = $.ret.list(list); } } return ret; }; /** * 总计 * @param {Object} db 数据库管理器 * @param {Object} query 查询条件 * @return {Object} 返回执行结果 */ Drive.prototype.count = async function(db, query, body) { var pm = Object.assign({}, query, body); return await this.count_main(db, pm); }; module.exports = Drive;