Files
bamboo/extend/mysql/api.js
T
2022-06-01 06:42:50 +08:00

678 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//封装了一些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 = ""
this.tableWhere = ""
this.tableData = ""
this.tablePage = ""
this.tableLimit = ""
this.tableOrder = ""
this.tableGroup = ""
this.tableAttributes = ""
this.t = ""
}
/**
* 指定表名
* @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) {
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|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 <model columns> 以取回所有定义的值;如果是列名数组,则附加 RETURNING <columns> 以获取特定列(仅限 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
}
)
return res
}
/**
* 删除数据
* @param {object} options 参数.
* @param {boolean} options.hooks 在批量销毁挂钩之前运行.
* @param {boolean} options.individualHooks 如果设置为 truedestroy 将选择与 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
}
)
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
}
}
}
}
}