|
|
/**
|
|
|
* @class BaseMod 数据模型基类,提供基础服务支持
|
|
|
*/
|
|
|
const {
|
|
|
getConfig
|
|
|
} = require('../../shared')
|
|
|
//基类
|
|
|
module.exports = class BaseMod {
|
|
|
constructor() {
|
|
|
//配置信息
|
|
|
this.config = getConfig('config')
|
|
|
//开启/关闭debug
|
|
|
this.debug = this.config.debug
|
|
|
//主键
|
|
|
this.primaryKey = '_id'
|
|
|
//单次查询最多返回 500 条数据(阿里云500,腾讯云1000,这里取最小值)
|
|
|
this.selectMaxLimit = 500
|
|
|
//数据表前缀
|
|
|
this.tablePrefix = 'uni-stat'
|
|
|
//数据表连接符
|
|
|
this.tableConnectors = '-'
|
|
|
//数据表名
|
|
|
this.tableName = ''
|
|
|
//参数
|
|
|
this.params = {}
|
|
|
//数据库连接
|
|
|
this._dbConnection()
|
|
|
//redis连接
|
|
|
this._redisConnection()
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 建立uniCloud数据库连接
|
|
|
*/
|
|
|
_dbConnection() {
|
|
|
if (!this.db) {
|
|
|
try {
|
|
|
this.db = uniCloud.database()
|
|
|
this.dbCmd = this.db.command
|
|
|
this.dbAggregate = this.dbCmd.aggregate
|
|
|
} catch (e) {
|
|
|
console.error('database connection failed: ' + e)
|
|
|
throw new Error('database connection failed: ' + e)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 建立uniCloud redis连接
|
|
|
*/
|
|
|
_redisConnection() {
|
|
|
if (this.config.redis && !this.redis) {
|
|
|
try {
|
|
|
this.redis = uniCloud.redis()
|
|
|
} catch (e) {
|
|
|
console.log('redis server connection failed: ' + e)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取uni统计配置项
|
|
|
* @param {String} key
|
|
|
*/
|
|
|
getConfig(key) {
|
|
|
return this.config[key]
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取带前缀的数据表名称
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
getTableName(tab, useDBPre = true) {
|
|
|
tab = tab || this.tableName
|
|
|
const table = (useDBPre && this.tablePrefix && tab.indexOf(this.tablePrefix) !== 0) ? this.tablePrefix + this
|
|
|
.tableConnectors + tab : tab
|
|
|
return table
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取数据集
|
|
|
* @param {String} tab表名
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
getCollection(tab, useDBPre = true) {
|
|
|
return this.db.collection(this.getTableName(tab, useDBPre))
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取reids缓存
|
|
|
* @param {String} key reids缓存键值
|
|
|
*/
|
|
|
async getCache(key) {
|
|
|
if (!this.redis || !key) {
|
|
|
return false
|
|
|
}
|
|
|
let cacheResult = await this.redis.get(key)
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('get cache result by key:' + key, cacheResult)
|
|
|
}
|
|
|
|
|
|
if (cacheResult) {
|
|
|
try {
|
|
|
cacheResult = JSON.parse(cacheResult)
|
|
|
} catch (e) {
|
|
|
if (this.debug) {
|
|
|
console.log('json parse error: ' + e)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return cacheResult
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置redis缓存
|
|
|
* @param {String} key 键值
|
|
|
* @param {String} val 值
|
|
|
* @param {Number} expireTime 过期时间
|
|
|
*/
|
|
|
async setCache(key, val, expireTime) {
|
|
|
if (!this.redis || !key) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
if (val instanceof Object) {
|
|
|
val = JSON.stringify(val)
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('set cache result by key:' + key, val)
|
|
|
}
|
|
|
|
|
|
return await this.redis.set(key, val, 'EX', expireTime || this.config.cachetime)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 清除redis缓存
|
|
|
* @param {String} key 键值
|
|
|
*/
|
|
|
async clearCache(key) {
|
|
|
if (!this.redis || !key) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('delete cache by key:' + key)
|
|
|
}
|
|
|
|
|
|
return await this.redis.del(key)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 通过数据表主键(_id)获取数据
|
|
|
* @param {String} tab 表名
|
|
|
* @param {String} id 主键值
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async getById(tab, id, useDBPre = true) {
|
|
|
const condition = {}
|
|
|
condition[this.primaryKey] = id
|
|
|
const info = await this.getCollection(tab, useDBPre).where(condition).get()
|
|
|
return (info && info.data.length > 0) ? info.data[0] : []
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 插入数据到数据表
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} params 字段参数
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async insert(tab, params, useDBPre = true) {
|
|
|
params = params || this.params
|
|
|
return await this.getCollection(tab, useDBPre).add(params)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 修改数据表数据
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} params 字段参数
|
|
|
* @param {Object} condition 条件
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async update(tab, params, condition, useDBPre = true) {
|
|
|
params = params || this.params
|
|
|
return await this.getCollection(tab).where(condition).update(params)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 删除数据表数据
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} condition 条件
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async delete(tab, condition, useDBPre = true) {
|
|
|
if (!condition) {
|
|
|
return false
|
|
|
}
|
|
|
return await this.getCollection(tab, useDBPre).where(condition).remove()
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 批量插入 - 云服务空间对单条mongo语句执行时间有限制,所以批量插入需限制每次执行条数
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} data 数据集合
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async batchInsert(tab, data, useDBPre = true) {
|
|
|
let batchInsertNum = this.getConfig('batchInsertNum') || 3000
|
|
|
batchInsertNum = Math.min(batchInsertNum, 5000)
|
|
|
const insertNum = Math.ceil(data.length / batchInsertNum)
|
|
|
let start;
|
|
|
let end;
|
|
|
let fillData;
|
|
|
let insertRes;
|
|
|
const res = {
|
|
|
code: 0,
|
|
|
msg: 'success',
|
|
|
data: {
|
|
|
inserted: 0
|
|
|
}
|
|
|
}
|
|
|
for (let p = 0; p < insertNum; p++) {
|
|
|
start = p * batchInsertNum
|
|
|
end = Math.min(start + batchInsertNum, data.length)
|
|
|
fillData = []
|
|
|
for (let i = start; i < end; i++) {
|
|
|
fillData.push(data[i])
|
|
|
}
|
|
|
if (fillData.length > 0) {
|
|
|
insertRes = await this.insert(tab, fillData, useDBPre)
|
|
|
if (insertRes && insertRes.inserted) {
|
|
|
res.data.inserted += insertRes.inserted
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 批量删除 - 云服务空间对单条mongo语句执行时间有限制,所以批量删除需限制每次执行条数
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} condition 条件
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async batchDelete(tab, condition, useDBPre = true) {
|
|
|
const batchDeletetNum = 5000;
|
|
|
let deleteIds;
|
|
|
let delRes;
|
|
|
let thisCondition
|
|
|
const res = {
|
|
|
code: 0,
|
|
|
msg: 'success',
|
|
|
data: {
|
|
|
deleted: 0
|
|
|
}
|
|
|
}
|
|
|
let run = true
|
|
|
while (run) {
|
|
|
const dataRes = await this.getCollection(tab).where(condition).limit(batchDeletetNum).get()
|
|
|
if (dataRes && dataRes.data.length > 0) {
|
|
|
deleteIds = []
|
|
|
for (let i = 0; i < dataRes.data.length; i++) {
|
|
|
deleteIds.push(dataRes.data[i][this.primaryKey])
|
|
|
}
|
|
|
if (deleteIds.length > 0) {
|
|
|
thisCondition = {}
|
|
|
thisCondition[this.primaryKey] = {
|
|
|
$in: deleteIds
|
|
|
}
|
|
|
delRes = await this.delete(tab, thisCondition, useDBPre)
|
|
|
if (delRes && delRes.deleted) {
|
|
|
res.data.deleted += delRes.deleted
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
run = false
|
|
|
}
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 基础查询
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} params 查询参数 where:where条件,field:返回字段,skip:跳过的文档数,limit:返回的记录数,orderBy:排序,count:返回查询结果的数量
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async select(tab, params, useDBPre = true) {
|
|
|
const {
|
|
|
where,
|
|
|
field,
|
|
|
skip,
|
|
|
limit,
|
|
|
orderBy,
|
|
|
count
|
|
|
} = params
|
|
|
|
|
|
const query = this.getCollection(tab, useDBPre)
|
|
|
|
|
|
//拼接where条件
|
|
|
if (where) {
|
|
|
if (where.length > 0) {
|
|
|
where.forEach(key => {
|
|
|
query.where(where[key])
|
|
|
})
|
|
|
} else {
|
|
|
query.where(where)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//排序
|
|
|
if (orderBy) {
|
|
|
Object.keys(orderBy).forEach(key => {
|
|
|
query.orderBy(key, orderBy[key])
|
|
|
})
|
|
|
}
|
|
|
|
|
|
//指定跳过的文档数
|
|
|
if (skip) {
|
|
|
query.skip(skip)
|
|
|
}
|
|
|
|
|
|
//指定返回的记录数
|
|
|
if (limit) {
|
|
|
query.limit(limit)
|
|
|
}
|
|
|
|
|
|
//指定返回字段
|
|
|
if (field) {
|
|
|
query.field(field)
|
|
|
}
|
|
|
|
|
|
//指定返回查询结果数量
|
|
|
if (count) {
|
|
|
return await query.count()
|
|
|
}
|
|
|
|
|
|
//返回查询结果数据
|
|
|
return await query.get()
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查询并返回全部数据
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} condition 条件
|
|
|
* @param {Object} field 指定查询返回字段
|
|
|
* @param {Boolean} useDBPre 是否使用数据表前缀
|
|
|
*/
|
|
|
async selectAll(tab, condition, field = {}, useDBPre = true) {
|
|
|
const countRes = await this.getCollection(tab, useDBPre).where(condition).count()
|
|
|
if (countRes && countRes.total > 0) {
|
|
|
const pageCount = Math.ceil(countRes.total / this.selectMaxLimit)
|
|
|
let res, returnData
|
|
|
for (let p = 0; p < pageCount; p++) {
|
|
|
res = await this.getCollection(tab, useDBPre).where(condition).orderBy(this.primaryKey, 'asc').skip(p *
|
|
|
this.selectMaxLimit).limit(this.selectMaxLimit).field(field).get()
|
|
|
if (!returnData) {
|
|
|
returnData = res
|
|
|
} else {
|
|
|
returnData.affectedDocs += res.affectedDocs
|
|
|
for (const i in res.data) {
|
|
|
returnData.data.push(res.data[i])
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return returnData
|
|
|
}
|
|
|
return {
|
|
|
affectedDocs: 0,
|
|
|
data: []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 聚合查询
|
|
|
* @param {String} tab 表名
|
|
|
* @param {Object} params 聚合参数
|
|
|
*/
|
|
|
async aggregate(tab, params) {
|
|
|
let {
|
|
|
project,
|
|
|
match,
|
|
|
lookup,
|
|
|
group,
|
|
|
skip,
|
|
|
limit,
|
|
|
sort,
|
|
|
getAll,
|
|
|
useDBPre,
|
|
|
addFields
|
|
|
} = params
|
|
|
//useDBPre 是否使用数据表前缀
|
|
|
useDBPre = (useDBPre !== null && useDBPre !== undefined) ? useDBPre : true
|
|
|
const query = this.getCollection(tab, useDBPre).aggregate()
|
|
|
|
|
|
//设置返回字段
|
|
|
if (project) {
|
|
|
query.project(project)
|
|
|
}
|
|
|
|
|
|
//设置匹配条件
|
|
|
if (match) {
|
|
|
query.match(match)
|
|
|
}
|
|
|
|
|
|
//数据表关联
|
|
|
if (lookup) {
|
|
|
query.lookup(lookup)
|
|
|
}
|
|
|
|
|
|
//分组
|
|
|
if (group) {
|
|
|
if (group.length > 0) {
|
|
|
for (const gi in group) {
|
|
|
query.group(group[gi])
|
|
|
}
|
|
|
} else {
|
|
|
query.group(group)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//添加字段
|
|
|
if (addFields) {
|
|
|
query.addFields(addFields)
|
|
|
}
|
|
|
|
|
|
//排序
|
|
|
if (sort) {
|
|
|
query.sort(sort)
|
|
|
}
|
|
|
|
|
|
//分页
|
|
|
if (skip) {
|
|
|
query.skip(skip)
|
|
|
}
|
|
|
if (limit) {
|
|
|
query.limit(limit)
|
|
|
} else if (!getAll) {
|
|
|
query.limit(this.selectMaxLimit)
|
|
|
}
|
|
|
|
|
|
//如果未指定全部返回则直接返回查询结果
|
|
|
if (!getAll) {
|
|
|
return await query.end()
|
|
|
}
|
|
|
|
|
|
//若指定了全部返回则分页查询全部结果后再返回
|
|
|
const resCount = await query.group({
|
|
|
_id: {},
|
|
|
aggregate_count: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}).end()
|
|
|
|
|
|
if (resCount && resCount.data.length > 0 && resCount.data[0].aggregate_count > 0) {
|
|
|
//分页查询
|
|
|
const total = resCount.data[0].aggregate_count
|
|
|
const pageCount = Math.ceil(total / this.selectMaxLimit)
|
|
|
let res, returnData
|
|
|
params.limit = this.selectMaxLimit
|
|
|
params.getAll = false
|
|
|
//结果合并
|
|
|
for (let p = 0; p < pageCount; p++) {
|
|
|
params.skip = p * params.limit
|
|
|
res = await this.aggregate(tab, params)
|
|
|
if (!returnData) {
|
|
|
returnData = res
|
|
|
} else {
|
|
|
returnData.affectedDocs += res.affectedDocs
|
|
|
for (const i in res.data) {
|
|
|
returnData.data.push(res.data[i])
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return returnData
|
|
|
} else {
|
|
|
return {
|
|
|
affectedDocs: 0,
|
|
|
data: []
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|