'use strict' //bamboo入口 import Koa from 'koa'; //https://x-extends.gitee.io/xe-utils/#/ import xe from 'xe-utils' const fs = require('fs'); // 文件模块 const log4js = require("log4js"); const path = require('path') const requireDirectory = require("require-directory"); const Router = require("koa-router"); const EventEmitter = require('events'); const schedule = require("node-schedule"); const Sequelize = require("sequelize"); const Parameter = require('parameter'); const objectPath = require("object-path"); module.exports = class Bamboo extends Koa { constructor(agrs, options) { super(options) this.asyncStatus = {} //异步启动状态 this.fulfill = false //启动完成 this.modelAmend = false //模型文件是否有改动 this.config = {} this.utils = {} this.registeredContextUtils({"xe": xe}) this.registeredParameter() // const ajv = new Ajv({}) // this.registeredContextUtils({"ajv": ajv}) this.logger = null this.Sequelize = null this.mysql = null this.sqlite = null this.ajv = null this.event = new EventEmitter() this.registeredConfig() this.setLogger() this.registeredError() this.initDB() this.registeredMiddleware() this.registeredRouter() this.listen(8884) this.init() } init() { const initInterval = setInterval(() => { console.log('启动中...'); const asyncStatusList = xe.toArray(this.asyncStatus) console.log(this.asyncStatus); console.log(asyncStatusList); const asyncStatus = asyncStatusList.filter(item => item === 0) if (asyncStatus.length === 0) { this.fulfill = true const init = require(this.path('app/init.js')); init(this.application) this.onFulfill() clearInterval(initInterval) } }, 1000) } async initDB() { await this.registeredSqlite() await this.registeredDB() } //启动完成事件 onFulfill() { this.registeredEvent() this.registeredSchedule() } listen(args) { const isNumber = this.isNumber(args) || !args const host = isNumber ? "127.0.0.1" : args.host const path = isNumber ? "/" : args.host const port = isNumber ? args : args.host console.log(`bamboo:`) console.log(`http://${host}:${port}${path}`) return super.listen(args) } //注册config registeredConfig() { console.log('注册 config'); const hash = requireDirectory(module, this.path('config')); this.config = hash } //注册 错误 registeredError() { console.log('注册 错误'); this.errorException = {} const hash = requireDirectory(module, this.path('lib/bamboo/err'), { visit: (obj) => { for (let key of Object.keys(obj)) { this.errorException[key] = (message, code) => { throw new obj[key](message, code) } } return obj } }); } registeredParameter() { this.parameter = new Parameter(); } //注册 mysql数据库 async registeredDB() { console.log('注册 数据库'); this.asyncStatus['registeredDB'] = 0 const { database, username, password, options, } = this.config.database.mysql this.Sequelize = Sequelize const sequelize = new Sequelize(database, username, password, { ...options, operatorsAliases: this.operatorsAliases }); try { await sequelize.authenticate(); console.log('数据库连接成功'); } catch (error) { console.error('数据库连接失败', error); } requireDirectory(module, this.path('app/model'), { visit: (obj, joined, filename) => { const parse = path.parse(filename); const model = {} for (let key of Object.keys(obj.model)) { obj.model[key].type = this.Sequelize[obj.model[key].type] model[key] = obj.model[key] } model['id'] = { type : this.Sequelize.INTEGER, comment : '表自增id', allowNull : false, unique : 'id', primaryKey : true, autoIncrement: true, } sequelize.define(parse.name, model) return obj } }); this.mysql = sequelize console.log('this.modelAmend', this.modelAmend); if (this.modelAmend) { console.log('生成模型结构到数据库'); await sequelize.sync({alter: true}); } this.asyncStatus['registeredDB'] = 1 } //注册 sqlite 数据库(记录model文件是否有改动,如果有就同步模型到mysql数据库) async registeredSqlite() { console.log('注册 sqlite 数据库'); this.asyncStatus['registeredSqlite'] = 0 const sqlite = new Sequelize(this.config.database.sqlite); try { await sqlite.authenticate(); console.log('sqlite数据库连接成功'); } catch (error) { console.error('sqlite数据库连接失败', error); } // const MysqlMD5 = sqlite.define('MysqlMD5', { // fileName: Sequelize.STRING, // md5 : Sequelize.TEXT // }); requireDirectory(module, this.path('app/sqlite/model'), { visit: (obj, joined, filename) => { const parse = path.parse(filename); const model = {} for (let key of Object.keys(obj.model)) { obj.model[key].type = Sequelize[obj.model[key].type] model[key] = obj.model[key] } sqlite.define(parse.name, model) return obj } }); await sqlite.sync({alter: true}); const {MysqlMD5} = sqlite.models this.modelAmend = true requireDirectory(module, this.path('app/model'), { visit: async (obj, joined, filename) => { const parse = path.parse(filename); const md5 = this.getFileMd5(this.path('app/model/') + filename) const MysqlMD5Data = await MysqlMD5.findOne({where: {fileName: 'User'}}) if (!MysqlMD5Data) { this.modelAmend = true await MysqlMD5.create({fileName: parse.name, md5: md5}) } else { if (MysqlMD5Data.md5 !== md5) { console.log('有改动的模型', MysqlMD5Data.fileName); this.modelAmend = true MysqlMD5.update({md5}, {where: {fileName: 'User'}}) } } return obj } }); this.sqlite = sqlite this.asyncStatus['registeredSqlite'] = 1 } get operatorsAliases() { const Op = Sequelize.Op; //操作符别名 const operatorsAliases = { $eq : Op.eq, $ne : Op.ne, $gte : Op.gte, $gt : Op.gt, $lte : Op.lte, $lt : Op.lt, $not : Op.not, $in : Op.in, $notIn : Op.notIn, $is : Op.is, $like : Op.like, $notLike : Op.notLike, $iLike : Op.iLike, $notILike : Op.notILike, $regexp : Op.regexp, $notRegexp : Op.notRegexp, $iRegexp : Op.iRegexp, $notIRegexp : Op.notIRegexp, $between : Op.between, $notBetween : Op.notBetween, $overlap : Op.overlap, $contains : Op.contains, $contained : Op.contained, $adjacent : Op.adjacent, $strictLeft : Op.strictLeft, $strictRight : Op.strictRight, $noExtendRight: Op.noExtendRight, $noExtendLeft : Op.noExtendLeft, $substring : Op.substring, $startsWith : Op.startsWith, $endsWith : Op.endsWith, $and : Op.and, $or : Op.or, $any : Op.any, $all : Op.all, $values : Op.values, $col : Op.col }; return operatorsAliases } //文件md5值 getFileMd5(url) { const buffer = fs.readFileSync(url); const hash = require('crypto').createHash('md5'); hash.update(buffer, 'utf8'); const md5 = hash.digest('hex'); return md5 } //注册 router registeredRouter() { console.log('注册 router'); const router = new Router(); const hash = requireDirectory(module, this.path('app/controller'), { visit: (obj) => { for (let methodElement of obj.method) { router[methodElement](obj.path, async (ctx, next) => { const validate = this.parameter.validate(obj.params, ctx.request.body); if (validate) { console.error(validate); this.errorException.ParameterException(validate); } else { await obj.controller(ctx, this.application) next(); } }); } return obj } }); super.use(router.routes()) super.use(router.allowedMethods()) } //注册 事件 registeredEvent() { console.log('注册 事件'); const hash = requireDirectory(module, this.path('app/event'), { visit: (obj, joined, filename) => { const parse = path.parse(filename); // super.on(parse.name, obj); for (let key of Object.keys(obj)) { this.event.on(`${parse.name}.${key}`, e[key]); } return obj } }); console.log(this.event.listeners); } //注册 定时任务 registeredSchedule() { console.log('注册 定时任务'); const hash = requireDirectory(module, this.path('app/schedule'), { visit: (obj, joined, filename) => { //https://www.cnblogs.com/yalong/p/15601391.html if (!process.env.NODE_APP_INSTANCE || process.env.NODE_APP_INSTANCE === '0') { //防止pm2多个线程重复执行 const parse = path.parse(filename); if (obj.run) { obj.schedule(this.application) } schedule.scheduleJob(parse.name, obj.time, () => obj.schedule(this.application)) } return obj } }); } //注册 middleware registeredMiddleware() { console.log('注册 middleware'); const hash = requireDirectory(module, this.path('app/middleware'), { visit: (obj, joined, filename) => { // console.log(obj, joined, filename); // const parse = path.parse(filename); // const config = this.config.middleware[parse.name] // // super.use(obj(config || {})) // super.use(async (ctx, next) => { // ctx['logger'] = this.logger // return await obj.fun(ctx, next, this.application) // }) return obj } }); let hashList = xe.toArray(hash) hashList = xe.orderBy(hashList,"sort") hashList = hashList.filter(item=>item.use) hashList.forEach(item=>{ super.use(async (ctx, next) => { ctx['logger'] = this.logger return await item.fun(ctx, next, this.application) }) }) console.log(hashList); } //注册 utils registeredContextUtils(args) { this.utils = { ...this.utils, ...args } } //logger setLogger(args) { log4js.configure(this.config['log']); this.logger = log4js.getLogger(); } path(args) { return this.isPath + '/' + args } isNumber(args) { return typeof args === 'number' } isString(args) { return typeof args === 'string' } isObject(args) { return typeof args === 'object' } get isPath() { return path.resolve('./') } get xe() { return xe } resetDbData() { this.tableData = null this.tableWhere = null this.tablePage = null this.tableLimit = null this.tableOrder = null this.tableGroup = null this.tableAttributes = null } /** * 指定表名 * @param {object} data 表名. */ table(data) { this.tableName = data return this } /** * 设置事务 */ async setTransaction() { console.log("设置事务"); this.t = await this.mysql.transaction() return this.t } /** * 提交事务,如果设置了事务不提交,任务不会执行 */ async commitTransaction() { try { await this.t.commit() } catch (error) { await this.t.rollback() } this.t = null } /** * 筛选条件 * @param {object} data 筛选条件对象. */ where(data) { this.tableWhere = data return this } /** * 模糊查询 * @param {string} value 模糊查询内容. * @param {array} searchData 模糊查询搜索的字段(默认表的全部字段). */ search(value, searchData) { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableWhere) { this.tableWhere = {} } if (!this.tableWhere['$or']) { this.tableWhere['$or'] = [] } if (!searchData) { searchData = Object.keys(this.mysql.models[this.tableName].rawAttributes) } for (let key of searchData) { const search = {} search[key] = {"$substring": value || ''} this.tableWhere['$or'].push(search) } return this } /** * 数据分组 * @param {string|array} value 传需要分组的字段['createdAt']. */ group(value) { this.tableGroup = value return this } /** * 仅选择某些属性 * @param {array} value 仅选择某些属性['foo', ['bar', 'baz'], 'qux']. */ attributes(value) { this.tableAttributes = value return this } /** * 页数 * @param {int} value 页数从0开始. */ page(value) { this.tablePage = value return this } /** * 条数 * @param {int} value 条数. */ limit(value) { this.tableLimit = value return this } get getLimit() { return this.tableLimit || null } /** * 数据 * @param {any} value 数据. */ data(value) { this.tableData = value return this } /** * 时间排序 * @param {array} value 时间排序(默认按更新时间排序). */ order(value) { this.tableOrder = value return this } /** * 字段值增加 * @param {string} data 要增加的字段和值{xxx:1,xxxx:2}. * @param {number} max 字段增加后的值不能大于最大值 * @param {number} setValue 如果增加后的字段大于max,设置字段值为n */ async setInc(max, setValue) { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableData) { this.errorException.HttpException("请数据") } if (!this.tableWhere) { this.errorException.HttpException("请筛选值") } let res = await this.mysql.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) for (let key of Object.keys(this.tableData)) { if (res[key] + this.tableData[key] > max) { if (setValue === 0 || setValue) { const data = {} Object.keys(this.tableData).map(item => {data[item] = setValue}) await this.mysql.models[this.tableName].update( data, { where : this.tableWhere, transaction: this.t } ) res = { ...res.dataValues, ...data } return {res: true, value: res} } return {res: false, value: res} } } await this.mysql.models[this.tableName].increment( this.tableData, { where : this.tableWhere, transaction: this.t } ) res = await this.mysql.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) return {res: true, value: res.dataValues} } /** * 字段值减小 * @param {string} data 要减小的字段和值{xxx:1,xxxx:2}. * @param {number} min 字段减小后的值不能小于最小值 * @param {number} setValue 如果减小后的字段小于min,设置字段值为n */ async setDec(min, setValue) { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableData) { this.errorException.HttpException("请数据") } if (!this.tableWhere) { this.errorException.HttpException("请筛选值") } let res = await this.mysql.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) for (let key of Object.keys(this.tableData)) { if (res[key] - this.tableData[key] < min) { if (setValue === 0 || setValue) { const data = {} Object.keys(this.tableData).map(item => {data[item] = setValue}) await this.mysql.models[this.tableName].update( data, { where : this.tableWhere, transaction: this.t } ) res = { ...res.dataValues, ...data } return {res: true, value: res} } return {res: false, value: res} } } await this.mysql.models[this.tableName].decrement( this.tableData, { where : this.tableWhere, transaction: this.t } ) res = await this.mysql.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) return {res: true, value: res.dataValues} } /** * 常用时间筛选 * @param {string|array|Number} value 时间内容:按时间段:['2000-1-1','2000-1-2'],按常用时间:day,按最近60分钟:60. * @param {string} field 时间字段(默认createdAt字段) */ time(value, field) { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!field) { field = "createdAt"} if (!this.tableWhere) { this.tableWhere = {} } if (!this.tableWhere['$and']) { this.tableWhere['$and'] = [] } const {fn, col, where, literal} = Sequelize switch (value) { case 'yday': // 昨天 this.tableWhere['$and'].push(where(fn('TO_DAYS', col(this.tableName + '.' + field)), '-', fn('TO_DAYS', fn('NOW')), '<=', 1)) break; case 'day': //当天 this.tableWhere['$and'].push(where(fn('TO_DAYS', col(this.tableName + '.' + field)), '=', fn('TO_DAYS', fn('NOW')))) break; case 'week': //本周 this.tableWhere['$and'].push(where(fn('YEARWEEK', fn('date_format', col(this.tableName + '.' + field), '%Y-%m-%d')), '=', fn('YEARWEEK', fn('now')))) break; case 'month': //当月 this.tableWhere['$and'].push(where(fn('DATE_FORMAT', col(this.tableName + '.' + field), '%Y%m'), '=', fn('DATE_FORMAT', fn('CURDATE'), '%Y%m'))) break; case 'lmonth': //上个月 this.tableWhere['$and'].push(where(fn('PERIOD_DIFF', fn('date_format', fn('now'), '%Y%m'), fn('date_format', col(this.tableName + '.' + field), '%Y%m')), '=', 1)) break; case 'year': //当年 this.tableWhere['$and'].push(where(fn('YEAR', col(this.tableName + '.' + field)), '=', fn('YEAR', fn('NOW')))) break; default: if (xe.isArray(value)) { //时间范围筛选 const data = {} data[field] = {"$between": value} this.tableWhere['$and'].push(data) } if (xe.isNumber(value)) { const minute = {} minute[field] = {"$lt": new Date(), "$gt": new Date(new Date() - value * 60 * 1000)} this.tableWhere['$and'].push(minute) } break; } return this } /** * 查询1条数据 * @param {Transaction} options.transaction 运行查询的事务. */ async find(options) { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) this.resetDbData() return res && res.dataValues || null } /** * 统计查询结果数 */ async count() { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].count( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return res } /** * 求和 */ async sum() { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].sum( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return res } /** * 查询最大值 */ async max() { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].max( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return res } /** * 查询最小值 */ async min() { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].min( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return res } /** * 更新数据 * @param {object} data 数据. * @param {boolean} options.paranoid 如果为 true,则只会更新未删除的记录。如果为 false,将更新已删除和未删除的记录。仅适用于模型的 options.paranoid 为真。. * @param {Array} options.fields 要更新的字段(默认为所有字段) * @param {boolean} options.validate 每一行在插入之前是否应该经过验证。如果一行未通过验证,则整个插入将失败 * @param {boolean} options.hooks 在批量更新挂钩之后运行? * @param {boolean} options.sideEffects 是否更新任何虚拟二传手的副作用。 * @param {boolean} options.individualHooks 在更新挂钩之前运行?如果为真,这将执行一个 SELECT,然后执行单独的 UPDATE。需要一个选择,因为需要将行数据传递给钩子 * @param {boolean | Array} options.returning 如果为真,则附加 RETURNING 以取回所有定义的值;如果是列名数组,则附加 RETURNING 以获取特定列(仅限 Postgres) * @param {number} options.limit 要更新多少行(仅适用于 mysql 和 mariadb,对于 MSSQL 实现为 TOP(n);对于 sqlite,仅当存在 rowid 时才支持) * @param {Function} options.logging在运行查询以记录 sql 时执行的函数。 * @param {boolean} options.benchmark 将查询执行时间(以毫秒为单位)作为第二个参数传递给日志记录函数(options.logging)。 * @param {Transaction} options.transaction 运行查询的事务 * @param {boolean} options.silent 如果为 true,则不会更新 updatedAt 时间戳。 */ async update(options) { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableData) { this.errorException.HttpException("请数据") } const res = await this.mysql.models[this.tableName].update( this.tableData, { where: this.tableWhere, ...options } ) return res } /** * 删除数据 * @param {object} options 参数. * @param {boolean} options.hooks 在批量销毁挂钩之前运行. * @param {boolean} options.individualHooks 如果设置为 true,destroy 将选择与 where 参数匹配的所有记录,并将在每行上的 destroy 钩子之前执行. * @param {number} options.limit 要删除多少行. * @param {boolean} options.force 删除而不是将 deletedAt 设置为当前时间戳(仅在启用偏执狂时适用). * @param {boolean} options.truncate 如果设置为 true,支持它的方言将使用 TRUNCATE 而不是 DELETE FROM。如果表被截断,则忽略 where 和 limit 选项. * @param {boolean} options.cascade 仅与 TRUNCATE 一起使用。截断所有具有对命名表的外键引用的表,或者截断由于 CASCADE 而添加到组中的任何表. * @param {transaction} options.transaction 运行查询的事务. * @param {Function} options.logging 在运行查询以记录 sql 时执行的函数。 * @param {boolean} options.benchmark 将查询执行时间(以毫秒为单位)作为第二个参数传递给日志记录函数(options.logging)。 */ async delete(options) { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].destroy( { where: this.tableWhere, ...options } ) return res } /** * 保存数据,如果数据已存在就更新,否则创建数据,可以传对象或数组,如果是需要更新数据,必须包含id */ async save() { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableData) { this.errorException.HttpException("请传要保存的数据") } let data = this.tableData let updateOnDuplicate = [] let keyData = {} if (xe.isArray(data)) { if (!data.length) { this.errorException.HttpException("请传要保存的数据") } keyData = data[0] } else { keyData = data data = [data] } for (let key of Object.keys(keyData)) { if (key !== 'id') { updateOnDuplicate.push(key) } } const res = await this.mysql.models[this.tableName].bulkCreate(data, {returning: true, updateOnDuplicate: updateOnDuplicate, transaction: this.t} ) this.resetDbData() return res } /** * 查询所有符合条件的数据 * @return {array} dataValues 查询结果. */ async findAll() { if (!this.tableName) { this.errorException.HttpException("请传表名") } const res = await this.mysql.models[this.tableName].findAll( { where : this.tableWhere, offset : (this.tablePage && this.tableLimit) ? this.tablePage - 1 * this.tableLimit : null, limit : this.tableLimit, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return res.map(item => item.dataValues) } /** * 如果数据不存在就创建数据,否则反查询结果 * @return {object} dataValues 查询结果. */ async findOrCreate() { if (!this.tableName) { this.errorException.HttpException("请传表名") } if (!this.tableData) { this.errorException.HttpException("请传data") } const res = await this.mysql.models[this.tableName].findOrCreate( { defaults : this.tableData, where : this.tableWhere, transaction: this.t } ) this.resetDbData() return res } /** * 分页查询数据 * @param {array} args.include 关联查询. * @return {int} count 总行数. * @return {array} rows 数据列表. */ async findAndCountAll(args = {}) { if (!this.tableName) { this.errorException.HttpException("请传表名") } const {count, rows} = await this.mysql.models[this.tableName].findAndCountAll( { where : this.tableWhere, offset : (this.tablePage && this.tableLimit) ? this.tablePage - 1 * this.tableLimit : null, limit : this.tableLimit, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.resetDbData() return {count, rows: rows.map(item => item.dataValues)} } /** * json批量任务 * @param {array} taskList json格式的任务列表. */ async jsonTasks(taskList) { const res = [] for (let tk of taskList) { const tkKeys = Object.keys(tk) for (let key of tkKeys) { if (key === "as") { continue; } if (key === "where" || key === "data") { const findJsonRes = this.findJsonValue(tk[key], (n) => { if (typeof n === "string") { // console.log(tk[key],n); if (n.indexOf("$as") !== -1) { return true } } return false }) if (findJsonRes) { const jsonPath = findJsonRes.path.join(".") const jsonPath2 = objectPath.get(tk[key], jsonPath);//$as.user_data.id const asName = jsonPath2.split(".")[1] const asFilter = res.filter(item => { // console.log(Object.keys(item), asName); return Object.keys(item)[0] === asName }) // console.log(asFilter); // console.log(jsonPath2); if (asFilter.length > 0) { // console.log(objectPath.get(asFilter[0], jsonPath2.split(".").slice(1).join("."))); //999 // console.log(jsonPath2.split(".").slice(1).join("."));//user_data.id // console.log(tk[key]);//user_data.id objectPath.set(tk[key], jsonPath, objectPath.get(asFilter[0], jsonPath2.split(".").slice(1).join("."))); } } } console.log(tk[key]); const tkRes = await this[key](tk[key]) if ( key === "save" || key === "count" || key === "sum" || key === "max" || key === "min" || key === "find" || key === "findAll" || key === "findOrCreate" || key === "jsonTasks" || key === "findAndCountAll" ) { console.log(tk["as"]); res.push(tk["as"] ? {[tk["as"]]: tkRes} : tkRes) } } } return res } //深度遍历JSON,搜索值 findJsonValue(n, value, path) { if (n === value || (xe.isFunction(value) && value(n))) { return {value: n, path} } path = path || [] // 获取所有的子节点,并遍历 if (typeof n === "object") { const nkeys = Object.keys(n) for (let k of nkeys) { // concat() 方法用于连接两个或多个数组 const res = this.findJsonValue(n[k], value, path.concat(k)); if (res) { return res } } } } get application() { return { config : this.config, utils : this.utils, log : this.logger, err : this.errorException, event : this.event, sequelize : this.Sequelize, mysql : this.mysql, sqlite : this.sqlite, table : this.table, where : this.where, data : this.data, search : this.search, group : this.group, attributes : this.attributes, page : this.page, setDec : this.setDec, setInc : this.setInc, limit : this.limit, time : this.time, setTransaction : this.setTransaction, commitTransaction: this.commitTransaction, save : this.save, count : this.count, sum : this.sum, max : this.max, min : this.min, find : this.find, findAll : this.findAll, findOrCreate : this.findOrCreate, findAndCountAll : this.findAndCountAll, resetDbData : this.resetDbData, jsonTasks : this.jsonTasks, findJsonValue : this.findJsonValue, //======= 简写函数 // S: this.sqlite.models, // M: this.mysql.models, C: this.config, U: this.utils, } } }