base.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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. getCacheKeyByParams(params) {
  139. return Object.keys(params).map((key) => key + ':' + params[key]).join('-')
  140. }
  141. /**
  142. * 通过数据表主键(_id)获取数据
  143. * @param {String} tab 表名
  144. * @param {String} id 主键值
  145. * @param {Boolean} useDBPre 是否使用数据表前缀
  146. */
  147. async getById(tab, id, useDBPre = true) {
  148. const condition = {}
  149. condition[this.primaryKey] = id
  150. const info = await this.getCollection(tab, useDBPre).where(condition).get()
  151. return (info && info.data.length > 0) ? info.data[0] : []
  152. }
  153. /**
  154. * 插入数据到数据表
  155. * @param {String} tab 表名
  156. * @param {Object} params 字段参数
  157. * @param {Boolean} useDBPre 是否使用数据表前缀
  158. */
  159. async insert(tab, params, useDBPre = true) {
  160. params = params || this.params
  161. return await this.getCollection(tab, useDBPre).add(params)
  162. }
  163. /**
  164. * 修改数据表数据
  165. * @param {String} tab 表名
  166. * @param {Object} params 字段参数
  167. * @param {Object} condition 条件
  168. * @param {Boolean} useDBPre 是否使用数据表前缀
  169. */
  170. async update(tab, params, condition, useDBPre = true) {
  171. params = params || this.params
  172. return await this.getCollection(tab).where(condition).update(params)
  173. }
  174. /**
  175. * 删除数据表数据
  176. * @param {String} tab 表名
  177. * @param {Object} condition 条件
  178. * @param {Boolean} useDBPre 是否使用数据表前缀
  179. */
  180. async delete(tab, condition, useDBPre = true) {
  181. if (!condition) {
  182. return false
  183. }
  184. return await this.getCollection(tab, useDBPre).where(condition).remove()
  185. }
  186. /**
  187. * 批量插入 - 云服务空间对单条mongo语句执行时间有限制,所以批量插入需限制每次执行条数
  188. * @param {String} tab 表名
  189. * @param {Object} data 数据集合
  190. * @param {Boolean} useDBPre 是否使用数据表前缀
  191. */
  192. async batchInsert(tab, data, useDBPre = true) {
  193. let batchInsertNum = this.getConfig('batchInsertNum') || 3000
  194. batchInsertNum = Math.min(batchInsertNum, 5000)
  195. const insertNum = Math.ceil(data.length / batchInsertNum)
  196. let start;
  197. let end;
  198. let fillData;
  199. let insertRes;
  200. const res = {
  201. code: 0,
  202. msg: 'success',
  203. data: {
  204. inserted: 0
  205. }
  206. }
  207. for (let p = 0; p < insertNum; p++) {
  208. start = p * batchInsertNum
  209. end = Math.min(start + batchInsertNum, data.length)
  210. fillData = []
  211. for (let i = start; i < end; i++) {
  212. fillData.push(data[i])
  213. }
  214. if (fillData.length > 0) {
  215. insertRes = await this.insert(tab, fillData, useDBPre)
  216. if (insertRes && insertRes.inserted) {
  217. res.data.inserted += insertRes.inserted
  218. }
  219. }
  220. }
  221. return res
  222. }
  223. /**
  224. * 批量删除 - 云服务空间对单条mongo语句执行时间有限制,所以批量删除需限制每次执行条数
  225. * @param {String} tab 表名
  226. * @param {Object} condition 条件
  227. * @param {Boolean} useDBPre 是否使用数据表前缀
  228. */
  229. async batchDelete(tab, condition, useDBPre = true) {
  230. const batchDeletetNum = 5000;
  231. let deleteIds;
  232. let delRes;
  233. let thisCondition
  234. const res = {
  235. code: 0,
  236. msg: 'success',
  237. data: {
  238. deleted: 0
  239. }
  240. }
  241. let run = true
  242. while (run) {
  243. const dataRes = await this.getCollection(tab).where(condition).limit(batchDeletetNum).get()
  244. if (dataRes && dataRes.data.length > 0) {
  245. deleteIds = []
  246. for (let i = 0; i < dataRes.data.length; i++) {
  247. deleteIds.push(dataRes.data[i][this.primaryKey])
  248. }
  249. if (deleteIds.length > 0) {
  250. thisCondition = {}
  251. thisCondition[this.primaryKey] = {
  252. $in: deleteIds
  253. }
  254. delRes = await this.delete(tab, thisCondition, useDBPre)
  255. if (delRes && delRes.deleted) {
  256. res.data.deleted += delRes.deleted
  257. }
  258. }
  259. } else {
  260. run = false
  261. }
  262. }
  263. return res
  264. }
  265. /**
  266. * 基础查询
  267. * @param {String} tab 表名
  268. * @param {Object} params 查询参数 where:where条件,field:返回字段,skip:跳过的文档数,limit:返回的记录数,orderBy:排序,count:返回查询结果的数量
  269. * @param {Boolean} useDBPre 是否使用数据表前缀
  270. */
  271. async select(tab, params, useDBPre = true) {
  272. const {
  273. where,
  274. field,
  275. skip,
  276. limit,
  277. orderBy,
  278. count
  279. } = params
  280. const query = this.getCollection(tab, useDBPre)
  281. //拼接where条件
  282. if (where) {
  283. if (where.length > 0) {
  284. where.forEach(key => {
  285. query.where(where[key])
  286. })
  287. } else {
  288. query.where(where)
  289. }
  290. }
  291. //排序
  292. if (orderBy) {
  293. Object.keys(orderBy).forEach(key => {
  294. query.orderBy(key, orderBy[key])
  295. })
  296. }
  297. //指定跳过的文档数
  298. if (skip) {
  299. query.skip(skip)
  300. }
  301. //指定返回的记录数
  302. if (limit) {
  303. query.limit(limit)
  304. }
  305. //指定返回字段
  306. if (field) {
  307. query.field(field)
  308. }
  309. //指定返回查询结果数量
  310. if (count) {
  311. return await query.count()
  312. }
  313. //返回查询结果数据
  314. return await query.get()
  315. }
  316. /**
  317. * 查询并返回全部数据
  318. * @param {String} tab 表名
  319. * @param {Object} condition 条件
  320. * @param {Object} field 指定查询返回字段
  321. * @param {Boolean} useDBPre 是否使用数据表前缀
  322. */
  323. async selectAll(tab, condition, field = {}, useDBPre = true) {
  324. const countRes = await this.getCollection(tab, useDBPre).where(condition).count()
  325. if (countRes && countRes.total > 0) {
  326. const pageCount = Math.ceil(countRes.total / this.selectMaxLimit)
  327. let res, returnData
  328. for (let p = 0; p < pageCount; p++) {
  329. res = await this.getCollection(tab, useDBPre).where(condition).orderBy(this.primaryKey, 'asc').skip(p *
  330. this.selectMaxLimit).limit(this.selectMaxLimit).field(field).get()
  331. if (!returnData) {
  332. returnData = res
  333. } else {
  334. returnData.affectedDocs += res.affectedDocs
  335. for (const i in res.data) {
  336. returnData.data.push(res.data[i])
  337. }
  338. }
  339. }
  340. return returnData
  341. }
  342. return {
  343. affectedDocs: 0,
  344. data: []
  345. }
  346. }
  347. /**
  348. * 聚合查询
  349. * @param {String} tab 表名
  350. * @param {Object} params 聚合参数
  351. */
  352. async aggregate(tab, params) {
  353. let {
  354. project,
  355. match,
  356. lookup,
  357. group,
  358. skip,
  359. limit,
  360. sort,
  361. getAll,
  362. useDBPre,
  363. addFields
  364. } = params
  365. //useDBPre 是否使用数据表前缀
  366. useDBPre = (useDBPre !== null && useDBPre !== undefined) ? useDBPre : true
  367. const query = this.getCollection(tab, useDBPre).aggregate()
  368. //设置返回字段
  369. if (project) {
  370. query.project(project)
  371. }
  372. //设置匹配条件
  373. if (match) {
  374. query.match(match)
  375. }
  376. //数据表关联
  377. if (lookup) {
  378. query.lookup(lookup)
  379. }
  380. //分组
  381. if (group) {
  382. if (group.length > 0) {
  383. for (const gi in group) {
  384. query.group(group[gi])
  385. }
  386. } else {
  387. query.group(group)
  388. }
  389. }
  390. //添加字段
  391. if (addFields) {
  392. query.addFields(addFields)
  393. }
  394. //排序
  395. if (sort) {
  396. query.sort(sort)
  397. }
  398. //分页
  399. if (skip) {
  400. query.skip(skip)
  401. }
  402. if (limit) {
  403. query.limit(limit)
  404. } else if (!getAll) {
  405. query.limit(this.selectMaxLimit)
  406. }
  407. //如果未指定全部返回则直接返回查询结果
  408. if (!getAll) {
  409. return await query.end()
  410. }
  411. //若指定了全部返回则分页查询全部结果后再返回
  412. const resCount = await query.group({
  413. _id: {},
  414. aggregate_count: {
  415. $sum: 1
  416. }
  417. }).end()
  418. if (resCount && resCount.data.length > 0 && resCount.data[0].aggregate_count > 0) {
  419. //分页查询
  420. const total = resCount.data[0].aggregate_count
  421. const pageCount = Math.ceil(total / this.selectMaxLimit)
  422. let res, returnData
  423. params.limit = this.selectMaxLimit
  424. params.getAll = false
  425. //结果合并
  426. for (let p = 0; p < pageCount; p++) {
  427. params.skip = p * params.limit
  428. res = await this.aggregate(tab, params)
  429. if (!returnData) {
  430. returnData = res
  431. } else {
  432. returnData.affectedDocs += res.affectedDocs
  433. for (const i in res.data) {
  434. returnData.data.push(res.data[i])
  435. }
  436. }
  437. }
  438. return returnData
  439. } else {
  440. return {
  441. affectedDocs: 0,
  442. data: []
  443. }
  444. }
  445. }
  446. }