//封装了一些Sequelize的api const Sequelize = require("sequelize"); const xe = require("xe-utils") module.exports = class Api { constructor(sequelize, models) { this.sequelize = sequelize this.models = models this.init() } init() { this.tableName = null this.tableWhere = null this.tableData = null this.tablePage = null this.tableLimit = null this.tableOrder = null this.tableGroup = null this.tableAttributes = null this.t = null } /** * 指定表名 * @param {object} value 表名. */ table(value) { this.tableName = value return this } /** * 设置数据 * @param {any} value 数据. */ data(value) { this.tableData = value return this } /** * 设置筛选条件 * @param {object} value 筛选条件对象. */ where(value) { if (!this.tableWhere) { this.tableWhere = value } else { Object.assign(this.tableWhere, value) } return this } /** * 设置页数 * @param {int} value 页数从0开始. */ page(value) { this.tablePage = value return this } /** * 设置条数 * @param {int} value 条数. */ limit(value) { this.tableLimit = value return this } /** * 设置排序 * @param {array} value 排序(默认按更新时间排序). */ order(value) { this.tableOrder = value return this } /** * 设置数据分组 * @param {string|array} value 传需要分组的字段['createdAt']. */ group(value) { this.tableGroup = value return this } /** * 模糊查询 * @param {string} value 关键字:你好. * @param {array} fieldList 字段列表['createdAt']. */ search({value, fieldList}) { if (!value|| !fieldList) return this if (!this.tableWhere) this.tableWhere = {} this.tableWhere['$or'] = fieldList.map(item => { return {[item]: {'$like': `%${value || ''}%`}} }) return this } /** * 常用时间筛选 * @param {string|array|Number} value 时间内容:按时间段:['2000-1-1','2000-1-2'],按常用时间:day,按最近60分钟:60. * @param {string} field 时间字段(默认createdAt字段) */ time({value, field}) { if (!this.tableName) { throw "请传表名" } 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 } /** * 设置事务 */ async setTransaction() { this.t = await this.sequelize.transaction() return this.t } /** * 仅选择某些属性(只返回指定字段) * @param {array} value 仅选择某些属性['foo', ['bar', 'baz'], 'qux']. */ attributes(value) { this.tableAttributes = value return this } /** * 查询1条数据 * @param {Transaction} options.transaction 运行查询的事务. */ async find(options) { if (!this.tableName) { throw "请传表名" } const res = await this.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) this.init() return res && res.dataValues || null } /** * 查询所有符合条件的数据 * @return {array} dataValues 查询结果. */ async findAll() { if (!this.tableName) { throw "请传表名" } const res = await this.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.init() return res.map(item => item.dataValues) } /** * 如果数据不存在就创建数据,否则反回查询结果 * @return {object} dataValues 查询结果. */ async findOrCreate() { if (!this.tableName) { throw "请传表名" } if (!this.tableData) { throw "请设置data" } const res = await this.models[this.tableName].findOrCreate( { defaults : this.tableData, where : this.tableWhere, transaction: this.t } ) this.init() return res } /** * 分页查询数据 * @param {array} args.include 关联查询. * @return {int} count 总行数. * @return {array} rows 数据列表. */ async findAndCountAll(args = {}) { if (!this.tableName) { throw "请传表名" } const {count, rows} = await this.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.init() return {count, rows: rows.map(item => item.dataValues)} } /** * 字段值减小 * @param {string} data 要减小的字段和值{xxx:1,xxxx:2}. * @param {number} min 字段减小后的值不能小于最小值 * @param {number} setValue 如果减小后的字段小于min,设置字段值为n */ async setDec({min, setValue}) { if (!this.tableName) { throw "请传表名" } if (!this.tableData) { throw"请数据" } if (!this.tableWhere) { throw"请筛选值" } let res = await this.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.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.models[this.tableName].decrement( this.tableData, { where : this.tableWhere, transaction: this.t } ) res = await this.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) this.init() return {res: true, value: res.dataValues} } /** * 字段值增加 * @param {string} data 要增加的字段和值{xxx:1,xxxx:2}. * @param {number} max 字段增加后的值不能大于最大值 * @param {number} setValue 如果增加后的字段大于max,设置字段值为n */ async setInc({max, setValue}) { if (!this.tableName) { throw "请传表名" } if (!this.tableData) { throw"请数据" } if (!this.tableWhere) { throw"请筛选值" } let res = await this.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.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.models[this.tableName].increment( this.tableData, { where : this.tableWhere, transaction: this.t } ) res = await this.models[this.tableName].findOne( { where : this.tableWhere, transaction: this.t } ) this.init() return {res: true, value: res.dataValues} } /** * 统计查询结果数 */ async count() { if (!this.tableName) { throw "请传表名" } const res = await this.models[this.tableName].count( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.init() return res } /** * 求和 */ async sum() { if (!this.tableName) { throw "请传表名" } const res = await this.models[this.tableName].sum( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.init() return res } /** * 查询最大值 */ async max() { if (!this.tableName) { throw "请传表名" } const res = await this.models[this.tableName].max( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.init() return res } /** * 查询最小值 */ async min() { if (!this.tableName) { throw "请传表名" } const res = await this.models[this.tableName].min( this.tableData, { where : this.tableWhere, order : this.tableOrder || [['updatedAt', 'DESC']], // 时间排序 group : this.tableGroup, attributes : this.tableAttributes, transaction: this.t } ) this.init() 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) { throw "请传表名" } if (!this.tableData) { throw"请传数据" } if (!this.tableWhere) { throw"请传where" } const res = await this.models[this.tableName].update( this.tableData, { where: this.tableWhere, ...options } ) this.init() 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) { throw "请传表名" } if (!this.tableWhere) { throw"请传where" } const res = await this.models[this.tableName].destroy( { where: this.tableWhere, ...options } ) this.init() return res } /** * 保存数据,如果数据已存在就更新,否则创建数据,可以传对象或数组,如果是需要更新数据,必须包含id */ async save() { if (!this.tableName) { throw "请传表名" } if (!this.tableData) { throw"请传要保存的数据" } let data = this.tableData let updateOnDuplicate = [] let keyData = {} if (xe.isArray(data)) { if (!data.length) { throw"请传要保存的数据" } 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.models[this.tableName].bulkCreate(data, {returning: true, updateOnDuplicate: updateOnDuplicate, transaction: this.t} ) this.init() return res } /** * 通过json执行多个数据库操作任务 * @param {array} taskList json格式的任务列表. */ /*jsonTasks([ {"table": "User", "where": {id: 999}, "find": "", "as": "user_data"},//as 设置结果别名 { "table" : "UserInfo", "data" : { "phone": "13126000000", "uid": "$as.user_data.id",//根据别名 user_data 返回的结果中的id值作为数据保存 }, "save": "" }, { "as": "user_info", "table" : "UserInfo", "where" : { "uid": "$as.user_data.id",//根据别名 user_data 返回的结果中的id值作为条件查询 }, "find": "" }, {"table": "User", "data": "age", "min": ""}, { "as": "user_list", "jsonTasks": [ //整个任务设置别名 {"setTransaction": ""}, // 开始事务 {"table": "User", "data": "age", "sum": ""}, {"table": "User", "data": "age", "max": ""}, {"table": "User", "data": "age", "min": ""}, {"commitTransaction": ""}, //提交事务 ] }, ])*/ 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 } } } } }