diff --git a/.babelrc b/.babelrc index 3ae5d0d..7256fca 100644 --- a/.babelrc +++ b/.babelrc @@ -10,11 +10,9 @@ ] ], "plugins": [ - [ - "babel-plugin-webpack-alias", - { - "config": "./webpack.config.js" - } - ] + ["@babel/plugin-proposal-decorators",{"legacy": true}], +// ["@babel/plugin-proposal-class-properties",{"loose": true}], +// ["babel-plugin-webpack-alias", { "config": "./webpack.config.js" } ] + ] -} \ No newline at end of file +} diff --git a/app/controller/index.js b/app/controller/index.js index 78d885b..999551a 100644 --- a/app/controller/index.js +++ b/app/controller/index.js @@ -1,14 +1,56 @@ 'use strict'; module.exports = { - doc : "", + doc : "默认页面", path : "/", method : ["get", "post"], middleware: [], params : {}, return : {}, controller: async (ctx, app) => { + // app.err.ParameterException() + app.event.emit('test.event2') + const where = { + // id: 4 + } + const data = { + // id: 1, + uid: "77", + } - ctx.body = 'index,xe:' + app.utils.VERSION + // const User = await app.table("User").where(where).findAll() + // const User = await app.table("User").time(["2022-04-9", "2022-04-10"]).findAll() + // const User = await app.table("User").where(where).findOrCreate(data) + // const {count, rows} = await app.table("User").where(where).findAndCountAll() + // const User = await app.table("User").save(data) + // const res = { + // "创建单个数据" : await app.table("User").data({uid: "6666"}).save(), + // "查询单个数据" : await app.table("User").where({id: 2}).find(), + // "查询单个数据,不存在就创建" : await app.table("User").where({id: 999}).data({uid: "6666"}).findOrCreate(), + // "查询所有数据" : await app.table("User").where({uid: "6666"}).findAll(), + // "查询所有数据(时间区间)" : await app.table("User").time(['2022-04-09', '2022-04-10']).findAll(), + // "查询所有数据(当月数据)" : await app.table("User").time('month').findAll(), + // "查询所有数据(30分钟内的数据)": await app.table("User").time(30).findAll(), + // "查询所有数据(分页)" : await app.table("User").where({uid: "6666"}).page(1).limit(1).findAll(), + // "查询所有数据+总行数(分页)" : await app.table("User").where({uid: "6666"}).page(1).limit(1).findAndCountAll(), + // } + + /* + await app.setTransaction() // 设置事务 + await app.table("User").data({uid: "6666"}).save() + await app.table("User").where({id: 2}).find() + await app.table("User").data({uid: "6666"}).save() + await app.table("User").where({id: 2}).find() + await app.commitTransaction() //提交事务,如果设置了事务不提交,任务不会执行 + */ + + // await app.table("User").where({id: 1032}).data({age: 1}).setInc() // 字段值+1 + // await app.table("User").where({id: 1032}).data({age: 2}).setInc() // 字段值+2 + // const {res, value} = await app.table("User").where({id: 1032}).data({age: 2}).setDec(0) // 字段值-2,不能低于0,如果低于0返回false + // const {res, value} = await app.table("User").where({id: 1032}).data({age: 2}).setDec(0,10) // 字段值-2,不能低于0,如果低于0设置为10 + const {res, value} = await app.table("User").where({id: 1032}).data({age: 2}).setInc(20, 10) // 字段值+2,不能大于20,如果大于20设置为10 + + + ctx.body = {res, value} } } diff --git a/lib/bamboo/errorException.js b/app/err/errorException.js similarity index 55% rename from lib/bamboo/errorException.js rename to app/err/errorException.js index f7e9909..7fc90da 100644 --- a/lib/bamboo/errorException.js +++ b/app/err/errorException.js @@ -1,44 +1,44 @@ class HttpException extends Error { - // message为异常信息,errorCode为错误码(开发人员内部约定),code为HTTP状态码 - constructor(message = '服务器异常', errorCode = 10000, code = 400) { + // message为异常信息,code 为错误码(开发人员内部约定),status 为HTTP状态码 + constructor(message, code, status) { super() - this.errorCode = errorCode || 10000 - this.code = code || 400 + this.status = status || 500 + this.code = code || 500 this.message = message || '服务器异常' } } class ParameterException extends HttpException { - constructor(message, errorCode) { + constructor(message, code, status) { super() - this.errorCode = errorCode || 10000 - this.code = 400 + this.status = status || 402 + this.code = code this.message = message || '参数错误' } } class NotFound extends HttpException { - constructor(message, errorCode) { + constructor(message, code, status) { super() - this.errorCode = errorCode || 10001 + this.status = status || 404 this.code = 404 this.message = message || '资源未找到' } } class AuthFailed extends HttpException { - constructor(message, errorCode) { + constructor(message, code, status) { super() - this.errorCode = errorCode || 10002 + this.status = status || 401 this.message = message || '授权失败' this.code = 401 } } class Forbidden extends HttpException { - constructor(message, errorCode) { + constructor(message, code, status) { super() - this.errorCode = errorCode || 10003 + this.status = status || 403 this.message = message || '禁止访问' this.code = 403 } diff --git a/app/exceptionHandle/应用异常定义文件 b/app/err/应用异常定义文件 similarity index 100% rename from app/exceptionHandle/应用异常定义文件 rename to app/err/应用异常定义文件 diff --git a/app/event/test.js b/app/event/test.js new file mode 100644 index 0000000..de77a3e --- /dev/null +++ b/app/event/test.js @@ -0,0 +1,10 @@ +'use strict'; +// 监听事件 +module.exports = { + "event1": (args) => { + console.log(args || 'event1') + }, + "event2": (args) => { + console.log(args || 'event2') + }, +} diff --git a/app/init.js b/app/init.js new file mode 100644 index 0000000..079ab68 --- /dev/null +++ b/app/init.js @@ -0,0 +1,4 @@ +/*系统启动后要做初始化*/ +module.exports = (app) => { + console.log("启动完成"); +} diff --git a/app/middleware/error.js b/app/middleware/error.js new file mode 100644 index 0000000..92a17ba --- /dev/null +++ b/app/middleware/error.js @@ -0,0 +1,12 @@ +'use strict'; +const error = require('koa-json-error') +// 错误处理 +module.exports = error((err) => { + return { + status : err.status, + message : err.message, + code : err.code, + res : false, + // postFormat: (e, obj) => process.env.NODE_ENV === 'production' ? _.omit(obj, 'stack') : obj + } +}) diff --git a/app/middleware/time.js b/app/middleware/time.js index 0d3ef5b..bff09eb 100644 --- a/app/middleware/time.js +++ b/app/middleware/time.js @@ -5,7 +5,6 @@ module.exports = async (ctx, next, app) => { // const timetaken = `${ctx.request.method}${ctx.request.url} 响应时间` // console.time(timetaken) // app.logger.debug("123") - throw new app.err.HttpException await next() // console.timeEnd(timetaken) } diff --git a/app/model/User.js b/app/model/User.js new file mode 100644 index 0000000..43530b4 --- /dev/null +++ b/app/model/User.js @@ -0,0 +1,8 @@ +module.exports = { + doc : "用户", + model: { + uid: {type: "STRING", comment: '用户id'}, + age: {type: "INTEGER", comment: '年龄', defaultValue: 0,}, + + } +} diff --git a/app/schedule/test.js b/app/schedule/test.js new file mode 100644 index 0000000..172f5c7 --- /dev/null +++ b/app/schedule/test.js @@ -0,0 +1,9 @@ +'use strict'; +// 监听事件 +module.exports = { + time : "*/3 * * * * *", + run : true,//系统启动时立即执行一次 + schedule: (app) => { + // console.log('每3秒我执行一次' + app.utils.VERSION) + } +} diff --git a/app/schedule/定时任务 b/app/schedule/定时任务 new file mode 100644 index 0000000..e69de29 diff --git a/app/sqlite/database.sqlite b/app/sqlite/database.sqlite new file mode 100644 index 0000000..87e7ce4 Binary files /dev/null and b/app/sqlite/database.sqlite differ diff --git a/app/sqlite/model/MysqlMD5.js b/app/sqlite/model/MysqlMD5.js new file mode 100644 index 0000000..43626f5 --- /dev/null +++ b/app/sqlite/model/MysqlMD5.js @@ -0,0 +1,8 @@ +module.exports = { + doc : "mysql模型文件的m5记录", + model: { + fileName: {type: "STRING", comment: '文件名称'}, + md5 : {type: "TEXT", comment: 'md5记录'}, + md5json : {type: "JSON", comment: 'md5记录'}, + } +} diff --git a/config/database.js b/config/database.js index 0ebae4d..6f44e78 100644 --- a/config/database.js +++ b/config/database.js @@ -1 +1,57 @@ /*数据库配置*/ +module.exports = { + mysql: { + database: "bamboo", + username: "bamboo", + password: "bamboo", + options : { + dialect: 'mysql', + host : "192.168.1.26", + port : 3306, + // 禁用日志记录或提供自定义日志记录功能;默认值:console.log + // logging: false, + // model的全局配置 + define: { + // 添加create,update,delete时间戳 + timestamps: true, + // 添加软删除 + paranoid: false, + // 防止修改表名为复数 + freezeTableName: true, + // 防止驼峰式字段被默认转为下划线 + underscored: false, + }, + // 由于orm用的UTC时间,这里必须加上东八区,否则取出来的时间相差8小时 + timezone: '+08:00', + // 连接数 = ((核心数 * 2) + 有效磁盘数) + pool : {// 连接池 + max : require('os').cpus().length * 2 + 1, + min : 0, + acquire: 60000, + idle : 100000, + }, + dialectOptions: { + charset : "utf8mb4", + collate : "utf8mb4_general_ci", + supportBigNumbers: true, + bigNumberStrings : true, + dateStrings : true, + typeCast(field, next) {// 让读取date类型数据时返回字符串而不是UTC时间 + if (field.type === 'DATETIME') { + // console.log(field); + return field.string(); + } + return next(); + }, + }, + }, + }, + redis:{}, + sqlite:{ + dialect: 'sqlite', + storage: 'app/sqlite/database.sqlite', + logging: false, + }, + + +} diff --git a/config/redis.js b/config/redis.js deleted file mode 100644 index 7f6f0b2..0000000 --- a/config/redis.js +++ /dev/null @@ -1,4 +0,0 @@ -/*redis配置*/ -module.exports = { - port: 3000, -} diff --git a/lib/bamboo/db.js b/lib/bamboo/db.js new file mode 100644 index 0000000..3c2b934 --- /dev/null +++ b/lib/bamboo/db.js @@ -0,0 +1,8 @@ +const Bamboo = require("./index") + +module.exports = class DB extends Bamboo { + + constructor() {super();} + + +} diff --git a/lib/bamboo/index.js b/lib/bamboo/index.js index f49d89e..700c1b8 100644 --- a/lib/bamboo/index.js +++ b/lib/bamboo/index.js @@ -4,25 +4,66 @@ 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 error = require('./errorException') const requireDirectory = require("require-directory"); const Router = require("koa-router"); - +const EventEmitter = require('events'); +const schedule = require("node-schedule"); +const Sequelize = require("sequelize"); module.exports = class Bamboo extends Koa { constructor(agrs, options) { super(options) - super.on('error', (err, ctx) => this.serverError(err, ctx)); + this.asyncStatus = {} //异步启动状态 + this.fulfill = false //启动完成 + this.modelAmend = false //模型文件是否有改动 this.config = {} this.utils = this.registeredContextUtils(xe) this.logger = null + this.Sequelize = null + this.mysql = null + this.sqlite = 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) { @@ -42,26 +83,240 @@ module.exports = class Bamboo extends Koa { this.config = hash } + //注册 错误 + registeredError() { + console.log('注册 错误'); + this.errorException = {} + const hash = requireDirectory(module, this.path('app/err'), { + visit: (obj) => { + for (let key of Object.keys(obj)) { + this.errorException[key] = (message, code) => { + throw new obj[key](message, code) + } + } + return obj + } + }); + + } + + //注册 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: (c) => { - for (let methodElement of c.method) { - router[methodElement](c.path, async (ctx, next) => { + visit: (obj) => { + for (let methodElement of obj.method) { + router[methodElement](obj.path, async (ctx, next) => { ctx['logger'] = this.logger - await c.controller(ctx, this.application) + await obj.controller(ctx, this.application) next(); }); } - return c + 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}`, obj[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'); @@ -78,7 +333,7 @@ module.exports = class Bamboo extends Koa { return obj } }); - console.log(hash); + // console.log(hash); } //注册 utils @@ -119,11 +374,500 @@ module.exports = class Bamboo extends Koa { 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 + } + + /** + * 更新数据 + * @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)} + } + get application() { return { - utils : this.utils, - logger: this.logger, - err : error, + 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, + find : this.find, + findAll : this.findAll, + findOrCreate : this.findOrCreate, + findAndCountAll : this.findAndCountAll, + resetDbData : this.resetDbData, + //======= 简写函数 + // S: this.sqlite.models, + // M: this.mysql.models, + C: this.config, } } } diff --git a/package.json b/package.json index b60fd56..7661948 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "license": "ISC", "devDependencies": { "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-decorators": "^7.17.9", "@babel/plugin-transform-runtime": "^7.5.5", "@babel/preset-env": "^7.5.5", "@babel/register": "^7.5.5", @@ -44,13 +46,18 @@ "clean-webpack-plugin": "^4.0.0", "cross-env": "^7.0.3", "koa-bodyparser": "^4.3.0", + "koa-json-error": "^3.1.2", "koa-logger": "^2.0.1", "koa-redis": "^4.0.1", "log4js": "^6.4.4", + "mysql2": "^2.3.3", + "node-schedule": "^2.1.0", "pm2": "^5.1.2", "require-all": "^3.0.0", "require-directory": "^2.1.1", + "sequelize": "^6.18.0", "shelljs": "^0.8.5", + "sqlite3": "^5.0.2", "webpack": "^4.41.5", "webpack-cli": "3.3.10", "webpack-node-externals": "^3.0.0",