/** * @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: [] } } } }