base.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /**
  2. * @class BaseMod 数据模型基类,提供基础服务支持
  3. */
  4. const {
  5. getConfig
  6. } = require('../../shared')
  7. //基类
  8. module.exports = class BaseMod {
  9. constructor() {
  10. //配置信息
  11. this.config = getConfig('config')
  12. //开启/关闭debug
  13. this.debug = this.config.debug
  14. //主键
  15. this.primaryKey = '_id'
  16. //单次查询最多返回 500 条数据(阿里云500,腾讯云1000,这里取最小值)
  17. this.selectMaxLimit = 500
  18. //数据表前缀
  19. this.tablePrefix = 'uni-stat'
  20. //数据表连接符
  21. this.tableConnectors = '-'
  22. //数据表名
  23. this.tableName = ''
  24. //参数
  25. this.params = {}
  26. //数据库连接
  27. this._dbConnection()
  28. //redis连接
  29. this._redisConnection()
  30. }
  31. /**
  32. * 建立uniCloud数据库连接
  33. */
  34. _dbConnection() {
  35. if (!this.db) {
  36. try {
  37. this.db = uniCloud.database()
  38. this.dbCmd = this.db.command
  39. this.dbAggregate = this.dbCmd.aggregate
  40. } catch (e) {
  41. console.error('database connection failed: ' + e)
  42. throw new Error('database connection failed: ' + e)
  43. }
  44. }
  45. }
  46. /**
  47. * 建立uniCloud redis连接
  48. */
  49. _redisConnection() {
  50. if (this.config.redis && !this.redis) {
  51. try {
  52. this.redis = uniCloud.redis()
  53. } catch (e) {
  54. console.log('redis server connection failed: ' + e)
  55. }
  56. }
  57. }
  58. /**
  59. * 获取uni统计配置项
  60. * @param {String} key
  61. */
  62. getConfig(key) {
  63. return this.config[key]
  64. }
  65. /**
  66. * 获取带前缀的数据表名称
  67. * @param {String} tab 表名
  68. * @param {Boolean} useDBPre 是否使用数据表前缀
  69. */
  70. getTableName(tab, useDBPre = true) {
  71. tab = tab || this.tableName
  72. const table = (useDBPre && this.tablePrefix && tab.indexOf(this.tablePrefix) !== 0) ? this.tablePrefix + this
  73. .tableConnectors + tab : tab
  74. return table
  75. }
  76. /**
  77. * 获取数据集
  78. * @param {String} tab表名
  79. * @param {Boolean} useDBPre 是否使用数据表前缀
  80. */
  81. getCollection(tab, useDBPre = true) {
  82. return this.db.collection(this.getTableName(tab, useDBPre))
  83. }
  84. /**
  85. * 获取reids缓存
  86. * @param {String} key reids缓存键值
  87. */
  88. async getCache(key) {
  89. if (!this.redis || !key) {
  90. return false
  91. }
  92. let cacheResult = await this.redis.get(key)
  93. if (this.debug) {
  94. console.log('get cache result by key:' + key, cacheResult)
  95. }
  96. if (cacheResult) {
  97. try {
  98. cacheResult = JSON.parse(cacheResult)
  99. } catch (e) {
  100. if (this.debug) {
  101. console.log('json parse error: ' + e)
  102. }
  103. }
  104. }
  105. return cacheResult
  106. }
  107. /**
  108. * 设置redis缓存
  109. * @param {String} key 键值
  110. * @param {String} val 值
  111. * @param {Number} expireTime 过期时间
  112. */
  113. async setCache(key, val, expireTime) {
  114. if (!this.redis || !key) {
  115. return false
  116. }
  117. if (val instanceof Object) {
  118. val = JSON.stringify(val)
  119. }
  120. if (this.debug) {
  121. console.log('set cache result by key:' + key, val)
  122. }
  123. return await this.redis.set(key, val, 'EX', expireTime || this.config.cachetime)
  124. }
  125. /**
  126. * 清除redis缓存
  127. * @param {String} key 键值
  128. */
  129. async clearCache(key) {
  130. if (!this.redis || !key) {
  131. return false
  132. }
  133. if (this.debug) {
  134. console.log('delete cache by key:' + key)
  135. }
  136. return await this.redis.del(key)
  137. }
  138. /**
  139. * 通过数据表主键(_id)获取数据
  140. * @param {String} tab 表名
  141. * @param {String} id 主键值
  142. * @param {Boolean} useDBPre 是否使用数据表前缀
  143. */
  144. async getById(tab, id, useDBPre = true) {
  145. const condition = {}
  146. condition[this.primaryKey] = id
  147. const info = await this.getCollection(tab, useDBPre).where(condition).get()
  148. return (info && info.data.length > 0) ? info.data[0] : []
  149. }
  150. /**
  151. * 插入数据到数据表
  152. * @param {String} tab 表名
  153. * @param {Object} params 字段参数
  154. * @param {Boolean} useDBPre 是否使用数据表前缀
  155. */
  156. async insert(tab, params, useDBPre = true) {
  157. params = params || this.params
  158. return await this.getCollection(tab, useDBPre).add(params)
  159. }
  160. /**
  161. * 修改数据表数据
  162. * @param {String} tab 表名
  163. * @param {Object} params 字段参数
  164. * @param {Object} condition 条件
  165. * @param {Boolean} useDBPre 是否使用数据表前缀
  166. */
  167. async update(tab, params, condition, useDBPre = true) {
  168. params = params || this.params
  169. return await this.getCollection(tab).where(condition).update(params)
  170. }
  171. /**
  172. * 删除数据表数据
  173. * @param {String} tab 表名
  174. * @param {Object} condition 条件
  175. * @param {Boolean} useDBPre 是否使用数据表前缀
  176. */
  177. async delete(tab, condition, useDBPre = true) {
  178. if (!condition) {
  179. return false
  180. }
  181. return await this.getCollection(tab, useDBPre).where(condition).remove()
  182. }
  183. /**
  184. * 批量插入 - 云服务空间对单条mongo语句执行时间有限制,所以批量插入需限制每次执行条数
  185. * @param {String} tab 表名
  186. * @param {Object} data 数据集合
  187. * @param {Boolean} useDBPre 是否使用数据表前缀
  188. */
  189. async batchInsert(tab, data, useDBPre = true) {
  190. let batchInsertNum = this.getConfig('batchInsertNum') || 3000
  191. batchInsertNum = Math.min(batchInsertNum, 5000)
  192. const insertNum = Math.ceil(data.length / batchInsertNum)
  193. let start;
  194. let end;
  195. let fillData;
  196. let insertRes;
  197. const res = {
  198. code: 0,
  199. msg: 'success',
  200. data: {
  201. inserted: 0
  202. }
  203. }
  204. for (let p = 0; p < insertNum; p++) {
  205. start = p * batchInsertNum
  206. end = Math.min(start + batchInsertNum, data.length)
  207. fillData = []
  208. for (let i = start; i < end; i++) {
  209. fillData.push(data[i])
  210. }
  211. if (fillData.length > 0) {
  212. insertRes = await this.insert(tab, fillData, useDBPre)
  213. if (insertRes && insertRes.inserted) {
  214. res.data.inserted += insertRes.inserted
  215. }
  216. }
  217. }
  218. return res
  219. }
  220. /**
  221. * 批量删除 - 云服务空间对单条mongo语句执行时间有限制,所以批量删除需限制每次执行条数
  222. * @param {String} tab 表名
  223. * @param {Object} condition 条件
  224. * @param {Boolean} useDBPre 是否使用数据表前缀
  225. */
  226. async batchDelete(tab, condition, useDBPre = true) {
  227. const batchDeletetNum = 5000;
  228. let deleteIds;
  229. let delRes;
  230. let thisCondition
  231. const res = {
  232. code: 0,
  233. msg: 'success',
  234. data: {
  235. deleted: 0
  236. }
  237. }
  238. let run = true
  239. while (run) {
  240. const dataRes = await this.getCollection(tab).where(condition).limit(batchDeletetNum).get()
  241. if (dataRes && dataRes.data.length > 0) {
  242. deleteIds = []
  243. for (let i = 0; i < dataRes.data.length; i++) {
  244. deleteIds.push(dataRes.data[i][this.primaryKey])
  245. }
  246. if (deleteIds.length > 0) {
  247. thisCondition = {}
  248. thisCondition[this.primaryKey] = {
  249. $in: deleteIds
  250. }
  251. delRes = await this.delete(tab, thisCondition, useDBPre)
  252. if (delRes && delRes.deleted) {
  253. res.data.deleted += delRes.deleted
  254. }
  255. }
  256. } else {
  257. run = false
  258. }
  259. }
  260. return res
  261. }
  262. /**
  263. * 基础查询
  264. * @param {String} tab 表名
  265. * @param {Object} params 查询参数 where:where条件,field:返回字段,skip:跳过的文档数,limit:返回的记录数,orderBy:排序,count:返回查询结果的数量
  266. * @param {Boolean} useDBPre 是否使用数据表前缀
  267. */
  268. async select(tab, params, useDBPre = true) {
  269. const {
  270. where,
  271. field,
  272. skip,
  273. limit,
  274. orderBy,
  275. count
  276. } = params
  277. const query = this.getCollection(tab, useDBPre)
  278. //拼接where条件
  279. if (where) {
  280. if (where.length > 0) {
  281. where.forEach(key => {
  282. query.where(where[key])
  283. })
  284. } else {
  285. query.where(where)
  286. }
  287. }
  288. //排序
  289. if (orderBy) {
  290. Object.keys(orderBy).forEach(key => {
  291. query.orderBy(key, orderBy[key])
  292. })
  293. }
  294. //指定跳过的文档数
  295. if (skip) {
  296. query.skip(skip)
  297. }
  298. //指定返回的记录数
  299. if (limit) {
  300. query.limit(limit)
  301. }
  302. //指定返回字段
  303. if (field) {
  304. query.field(field)
  305. }
  306. //指定返回查询结果数量
  307. if (count) {
  308. return await query.count()
  309. }
  310. //返回查询结果数据
  311. return await query.get()
  312. }
  313. /**
  314. * 查询并返回全部数据
  315. * @param {String} tab 表名
  316. * @param {Object} condition 条件
  317. * @param {Object} field 指定查询返回字段
  318. * @param {Boolean} useDBPre 是否使用数据表前缀
  319. */
  320. async selectAll(tab, condition, field = {}, useDBPre = true) {
  321. const countRes = await this.getCollection(tab, useDBPre).where(condition).count()
  322. if (countRes && countRes.total > 0) {
  323. const pageCount = Math.ceil(countRes.total / this.selectMaxLimit)
  324. let res, returnData
  325. for (let p = 0; p < pageCount; p++) {
  326. res = await this.getCollection(tab, useDBPre).where(condition).orderBy(this.primaryKey, 'asc').skip(p *
  327. this.selectMaxLimit).limit(this.selectMaxLimit).field(field).get()
  328. if (!returnData) {
  329. returnData = res
  330. } else {
  331. returnData.affectedDocs += res.affectedDocs
  332. for (const i in res.data) {
  333. returnData.data.push(res.data[i])
  334. }
  335. }
  336. }
  337. return returnData
  338. }
  339. return {
  340. affectedDocs: 0,
  341. data: []
  342. }
  343. }
  344. /**
  345. * 聚合查询
  346. * @param {String} tab 表名
  347. * @param {Object} params 聚合参数
  348. */
  349. async aggregate(tab, params) {
  350. let {
  351. project,
  352. match,
  353. lookup,
  354. group,
  355. skip,
  356. limit,
  357. sort,
  358. getAll,
  359. useDBPre
  360. } = params
  361. //useDBPre 是否使用数据表前缀
  362. useDBPre = (useDBPre !== null && useDBPre !== undefined) ? useDBPre : true
  363. const query = this.getCollection(tab, useDBPre).aggregate()
  364. //设置返回字段
  365. if (project) {
  366. query.project(project)
  367. }
  368. //设置匹配条件
  369. if (match) {
  370. query.match(match)
  371. }
  372. //数据表关联
  373. if (lookup) {
  374. query.lookup(lookup)
  375. }
  376. //分组
  377. if (group) {
  378. if (group.length > 0) {
  379. for (const gi in group) {
  380. query.group(group[gi])
  381. }
  382. } else {
  383. query.group(group)
  384. }
  385. }
  386. //排序
  387. if (sort) {
  388. query.sort(sort)
  389. }
  390. //分页
  391. if (skip) {
  392. query.skip(skip)
  393. }
  394. if (limit) {
  395. query.limit(limit)
  396. } else if (!getAll) {
  397. query.limit(this.selectMaxLimit)
  398. }
  399. //如果未指定全部返回则直接返回查询结果
  400. if (!getAll) {
  401. return await query.end()
  402. }
  403. //若指定了全部返回则分页查询全部结果后再返回
  404. const resCount = await query.group({
  405. _id: {},
  406. aggregate_count: {
  407. $sum: 1
  408. }
  409. }).end()
  410. if (resCount && resCount.data.length > 0 && resCount.data[0].aggregate_count > 0) {
  411. //分页查询
  412. const total = resCount.data[0].aggregate_count
  413. const pageCount = Math.ceil(total / this.selectMaxLimit)
  414. let res, returnData
  415. params.limit = this.selectMaxLimit
  416. params.getAll = false
  417. //结果合并
  418. for (let p = 0; p < pageCount; p++) {
  419. params.skip = p * params.limit
  420. res = await this.aggregate(tab, params)
  421. if (!returnData) {
  422. returnData = res
  423. } else {
  424. returnData.affectedDocs += res.affectedDocs
  425. for (const i in res.data) {
  426. returnData.data.push(res.data[i])
  427. }
  428. }
  429. }
  430. return returnData
  431. } else {
  432. return {
  433. affectedDocs: 0,
  434. data: []
  435. }
  436. }
  437. }
  438. }