diff --git a/extend/event/config.js b/extend/event/config.js new file mode 100644 index 0000000..9d49da3 --- /dev/null +++ b/extend/event/config.js @@ -0,0 +1,3 @@ +module.exports = { + path:"app/*/event/*.js" +} diff --git a/extend/event/index.js b/extend/event/index.js new file mode 100644 index 0000000..d13363f --- /dev/null +++ b/extend/event/index.js @@ -0,0 +1,16 @@ +/** + * 事件插件 + * 加载event文件夹下的事件 + */ +const config = require("./config") +const EventEmitter = require('events'); +module.exports = async (app) => { + app.event = new EventEmitter() + const evList = await app.load(config.path) + evList.forEach(item => { + Object.keys(item.res).forEach(key => { + //载入所有事件 + app.event.on(key, item.res[key]) + }) + }) +} diff --git a/extend/load/config.js b/extend/load/config.js deleted file mode 100644 index a197a85..0000000 --- a/extend/load/config.js +++ /dev/null @@ -1,4 +0,0 @@ -//加载配置文件 -module.exports = (app) => { - -} diff --git a/lib/bamboo/LICENSE.txt b/lib/bamboo/LICENSE.txt new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/lib/bamboo/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/bamboo/README.md b/lib/bamboo/README.md new file mode 100644 index 0000000..55bf673 --- /dev/null +++ b/lib/bamboo/README.md @@ -0,0 +1,189 @@ +**有限状态机** + +用于编排复杂的流程业务,如订单流转,审批流程 + +# 安装 + +``` +npm install bamboo_smf -S +``` + +# 使用 + +``` +const Smf = require('bamboo_smf'); +const circuit1 = new Smf() +//订单正常流程 +circuit1.init('order', 'new') +.push('new', 'order.pay') +.push('pay', 'order.confirm', 'refund.start') +.push('confirm', 'order.end') +.push('end') + + + +const circuit2 = new Smf() +//订单退款流程 +circuit2.init('refund', 'start') +.push('start', 'refund.reimburse') +.push('reimburse', 'refund.end') +.push('end') + +const processFlow = new Smf() +//把编排好的流程数据导入 +processFlow.setSmfDataList([ +circuit1.getSmfData(), +circuit2.getSmfData(), +]) + +//监听流程变更 +processFlow.on((e, paras) => { + console.log(e,paras); +}) + +//跳转下个流程 +processFlow.setStatus('order.new').nextTo() +// or +processFlow.nextTo('order.new') +``` + +# Smf.init + +定义初始值状态 + +``` +const circuit = new Smf() +circuit.init('状态链名称', '初始状态') +``` + +# Smf.push + +添加转变内容 + +``` +circuit.push('状态名称', '下一个流程地址(区块名称.状态名称)','异常时的流程地址(区块名称.状态名称)') +``` + +# Smf.getSmfData + +获取全部定义的json数据 + +``` +circuit.getSmfData() +``` + +# Smf.setSmfDataList + +设置所有区块数据 + +``` +circuit.setSmfDataList([区块数据]) +``` + +# Smf.setStatus + +设置当前状态 + +``` +circuit.setStatus('当前状态(区块名称.状态名称)') +``` + +# Smf.get + +获取当前状态的数据 + +``` +circuit.get() +``` + +# Smf.getStatusName + +获取当前状态的名称 + +``` +circuit.getStatusName() +``` + +# Smf.getCurrentState + +获取当前区域和状态 + +``` +circuit.getCurrentState() +``` + +# Smf.getTo + +获取当前状态的to区域和状态 + +``` +circuit.getTo() +``` + +# Smf.getErr + +获取当前状态的err区域和状态 + +``` +circuit.getErr() +``` + +# Smf.start + +触发开始状态 + +``` +circuit.start('区块名称','事件入参') +``` + +# Smf.end + +触发结束状态(最后一个状态) + +``` +circuit.end('区块名称','事件入参') +``` + +# Smf.next + +按当前状态跳转下一个 + +``` +circuit.next('事件:to,err','跳转时可以重新定义当前状态','事件入参') +``` + +# Smf.nextTo + +按当前状态跳转到to + +``` +circuit.next('跳转时可以重新定义当前状态','事件入参') +``` + +# Smf.nextErr + +按当前状态跳转到err + +``` +circuit.next('跳转时可以重新定义当前状态','事件入参') +``` + +# Smf.on + +触发跳转时的事件 + +``` +circuit.on((e, paras) => { + console.log(e,paras); + /** + e:当前状态数据: { + status_name: 'ling', + to: 'test.end', + err: '', + currentState: 'test.ling' + } + **/ +}) + + +``` diff --git a/lib/bamboo/index.js b/lib/bamboo/index.js index fc4c10c..110992a 100644 --- a/lib/bamboo/index.js +++ b/lib/bamboo/index.js @@ -1,1063 +1,62 @@ -'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 Koa = require("koa") +const glob = require("glob") 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"); + +/** + * bamboo 核心 + * @param {string} extend_directory 扩展库目录 + */ 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 {string|array} value 传需要分组的字段['createdAt']. - */ - 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, - } - } + /*初始化业务目录*/ + constructor(extend_directory, root) { + super(); + this.root = this.isPath() || root; + this.extend_directory = extend_directory || `extend/*/index.js` + console.log(`当前根目录${this.root}`); + console.log(`当前插件目录${this.extend_directory}`); + this.loadEvent() + this.startServer() + } + + /*加载扩展库目录*/ + async loadEvent() { + const list = await this.load(this.extend_directory) + for (let listElement of list) { + await listElement.res(this) + } + } + + /*当前根目录*/ + isPath(args) { + return path.resolve('./' + (args || "")) + } + + /*加载文件*/ + load(directory, root) { + const path_root = root || this.root + return new Promise((resolve, reject) => { + const options = { + root: path_root + } + glob(directory, options, function (er, files) { + if (er) { + reject(er) + } + console.log("获取到的文件:", files); + files = files.map(item => { + const parse = path.parse(item); + return { + parse, + res: require(path.resolve(path_root + "/" + item)) + } + }) + resolve(files) + }) + }) + } + + /*启动服务*/ + startServer(prod) { + this.listen(prod || 3000) + } } diff --git a/lib/bamboo/package.json b/lib/bamboo/package.json new file mode 100644 index 0000000..5de819c --- /dev/null +++ b/lib/bamboo/package.json @@ -0,0 +1,15 @@ +{ + "name": "bamboo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "robin", + "license": "ISC", + "dependencies": { + "glob": "^8.0.3", + "koa": "^2.13.4" + } +} diff --git a/lib/bamboo/bamboo.js b/lib/bamboo2/bamboo.js similarity index 100% rename from lib/bamboo/bamboo.js rename to lib/bamboo2/bamboo.js diff --git a/lib/bamboo/db.js b/lib/bamboo2/db.js similarity index 100% rename from lib/bamboo/db.js rename to lib/bamboo2/db.js diff --git a/lib/bamboo/errorException.js b/lib/bamboo2/errorException.js similarity index 100% rename from lib/bamboo/errorException.js rename to lib/bamboo2/errorException.js diff --git a/lib/bamboo2/index.js b/lib/bamboo2/index.js new file mode 100644 index 0000000..fc4c10c --- /dev/null +++ b/lib/bamboo2/index.js @@ -0,0 +1,1063 @@ +'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 {string|array} value 传需要分组的字段['createdAt']. + */ + 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, + } + } +} diff --git a/lib/bamboo/load.js b/lib/bamboo2/load.js similarity index 100% rename from lib/bamboo/load.js rename to lib/bamboo2/load.js diff --git a/lib/bamboo/status.js b/lib/bamboo2/status.js similarity index 100% rename from lib/bamboo/status.js rename to lib/bamboo2/status.js diff --git a/lib/main.js b/lib/main.js index e7a9f8c..ca4290e 100644 --- a/lib/main.js +++ b/lib/main.js @@ -13,6 +13,6 @@ require('@babel/register')({ ] }) require('@babel/polyfill') -const bamboo = require('./bamboo/bamboo') +const bamboo = require('./bamboo/index') const app = new bamboo(); // app.listen(3000); diff --git a/package.json b/package.json index d39ace4..3b5f0e1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "dependencies": { "@babel/core": "^7.16.7", "@babel/node": "^7.16.8", - "@babel/plugin-proposal-decorators": "^7.17.9", + "@babel/plugin-proposal-decorators": "^7.18.2", "@babel/polyfill": "^7.4.4", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.5.5",