activeDevices.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /**
  2. * @class ActiveDevices 活跃设备模型 - 每日跑批合并,仅添加本周/本月首次访问的设备。
  3. */
  4. const BaseMod = require('./base')
  5. const Platform = require('./platform')
  6. const Channel = require('./channel')
  7. const Version = require('./version')
  8. const SessionLog = require('./sessionLog')
  9. const {
  10. DateTime,
  11. UniCrypto
  12. } = require('../lib')
  13. module.exports = class ActiveDevices extends BaseMod {
  14. constructor() {
  15. super()
  16. this.tableName = 'active-devices'
  17. this.platforms = []
  18. this.channels = []
  19. this.versions = []
  20. }
  21. /**
  22. * @desc 活跃设备统计 - 为周统计/月统计提供周活/月活数据
  23. * @param {date|time} date
  24. * @param {bool} reset
  25. */
  26. async stat(date, reset) {
  27. const dateTime = new DateTime()
  28. const dateDimension = dateTime.getTimeDimensionByType('day', -1, date)
  29. this.startTime = dateDimension.startTime
  30. // 查看当前时间段数据是否已存在,防止重复生成
  31. if (!reset) {
  32. const checkRes = await this.getCollection(this.tableName).where({
  33. create_time: {
  34. $gte: dateDimension.startTime,
  35. $lte: dateDimension.endTime
  36. }
  37. }).get()
  38. if (checkRes.data.length > 0) {
  39. console.log('data have exists')
  40. return {
  41. code: 1003,
  42. msg: 'Devices data in this time have already existed'
  43. }
  44. }
  45. } else {
  46. const delRes = await this.delete(this.tableName, {
  47. create_time: {
  48. $gte: dateDimension.startTime,
  49. $lte: dateDimension.endTime
  50. }
  51. })
  52. console.log('Delete old data result:', JSON.stringify(delRes))
  53. }
  54. const sessionLog = new SessionLog()
  55. const statRes = await this.aggregate(sessionLog.tableName, {
  56. project: {
  57. appid: 1,
  58. version: 1,
  59. platform: 1,
  60. channel: 1,
  61. is_first_visit: 1,
  62. create_time: 1,
  63. device_id: 1
  64. },
  65. match: {
  66. create_time: {
  67. $gte: dateDimension.startTime,
  68. $lte: dateDimension.endTime
  69. }
  70. },
  71. group: {
  72. _id: {
  73. appid: '$appid',
  74. version: '$version',
  75. platform: '$platform',
  76. channel: '$channel',
  77. device_id: '$device_id'
  78. },
  79. is_new: {
  80. $max: '$is_first_visit'
  81. },
  82. create_time: {
  83. $min: '$create_time'
  84. }
  85. },
  86. sort: {
  87. create_time: 1
  88. },
  89. getAll: true
  90. })
  91. let res = {
  92. code: 0,
  93. msg: 'success'
  94. }
  95. // if (this.debug) {
  96. // console.log('statRes', JSON.stringify(statRes))
  97. // }
  98. if (statRes.data.length > 0) {
  99. const uniCrypto = new UniCrypto()
  100. // 同应用、平台、渠道、版本的数据合并
  101. const statData = [];
  102. let statKey;
  103. let data
  104. const statOldRes = await this.aggregate(sessionLog.tableName, {
  105. project: {
  106. appid: 1,
  107. version: 1,
  108. platform: 1,
  109. channel: 1,
  110. is_first_visit: 1,
  111. create_time: 1,
  112. old_device_id: 1
  113. },
  114. match: {
  115. create_time: {
  116. $gte: dateDimension.startTime,
  117. $lte: dateDimension.endTime
  118. },
  119. old_device_id: {$exists: true}
  120. },
  121. group: {
  122. _id: {
  123. appid: '$appid',
  124. version: '$version',
  125. platform: '$platform',
  126. channel: '$channel',
  127. old_device_id: '$old_device_id'
  128. },
  129. create_time: {
  130. $min: '$create_time'
  131. }
  132. },
  133. sort: {
  134. create_time: 1
  135. },
  136. getAll: true
  137. })
  138. if (this.debug) {
  139. console.log('statOldRes', JSON.stringify(statOldRes))
  140. }
  141. for (const sti in statRes.data) {
  142. data = statRes.data[sti]
  143. statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
  144. .channel)
  145. if (!statData[statKey]) {
  146. statData[statKey] = {
  147. appid: data._id.appid,
  148. platform: data._id.platform,
  149. version: data._id.version,
  150. channel: data._id.channel,
  151. device_ids: [],
  152. old_device_ids: [],
  153. info: [],
  154. old_info: []
  155. }
  156. statData[statKey].device_ids.push(data._id.device_id)
  157. statData[statKey].info[data._id.device_id] = {
  158. is_new: data.is_new,
  159. create_time: data.create_time
  160. }
  161. } else {
  162. statData[statKey].device_ids.push(data._id.device_id)
  163. statData[statKey].info[data._id.device_id] = {
  164. is_new: data.is_new,
  165. create_time: data.create_time
  166. }
  167. }
  168. }
  169. if(statOldRes.data.length) {
  170. const oldDeviceIds = []
  171. for(const osti in statOldRes.data) {
  172. if(!statOldRes.data[osti]._id.old_device_id) {
  173. continue
  174. }
  175. oldDeviceIds.push(statOldRes.data[osti]._id.old_device_id)
  176. }
  177. if(oldDeviceIds.length) {
  178. const statOldDidRes = await this.aggregate(sessionLog.tableName, {
  179. project: {
  180. appid: 1,
  181. version: 1,
  182. platform: 1,
  183. channel: 1,
  184. is_first_visit: 1,
  185. create_time: 1,
  186. device_id: 1
  187. },
  188. match: {
  189. create_time: {
  190. $gte: dateDimension.startTime,
  191. $lte: dateDimension.endTime
  192. },
  193. device_id: {$in: oldDeviceIds}
  194. },
  195. group: {
  196. _id: {
  197. appid: '$appid',
  198. version: '$version',
  199. platform: '$platform',
  200. channel: '$channel',
  201. old_device_id: '$device_id'
  202. },
  203. create_time: {
  204. $min: '$create_time'
  205. }
  206. },
  207. sort: {
  208. create_time: 1
  209. },
  210. getAll: true
  211. })
  212. if(statOldDidRes.data.length){
  213. for(const osti in statOldDidRes.data) {
  214. data = statOldDidRes.data[osti]
  215. statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
  216. .channel)
  217. if(!data._id.old_device_id) {
  218. continue
  219. }
  220. if (!statData[statKey]) {
  221. statData[statKey] = {
  222. appid: data._id.appid,
  223. platform: data._id.platform,
  224. version: data._id.version,
  225. channel: data._id.channel,
  226. device_ids: [],
  227. old_device_ids: [],
  228. old_info: []
  229. }
  230. statData[statKey].old_device_ids.push(data._id.old_device_id)
  231. } else {
  232. statData[statKey].old_device_ids.push(data._id.old_device_id)
  233. }
  234. if(!statData[statKey].old_info[data._id.old_device_id]) {
  235. statData[statKey].old_info[data._id.old_device_id] = {
  236. create_time: data.create_time
  237. }
  238. }
  239. }
  240. }
  241. }
  242. }
  243. this.fillData = []
  244. for (const sk in statData) {
  245. await this.getFillData(statData[sk])
  246. }
  247. if (this.fillData.length > 0) {
  248. res = await this.batchInsert(this.tableName, this.fillData)
  249. }
  250. }
  251. return res
  252. }
  253. /**
  254. * 获取填充数据
  255. * @param {Object} data
  256. */
  257. async getFillData(data) {
  258. // 平台信息
  259. let platformInfo = null
  260. if (this.platforms && this.platforms[data.platform]) {
  261. platformInfo = this.platforms[data.platform]
  262. } else {
  263. const platform = new Platform()
  264. platformInfo = await platform.getPlatformAndCreate(data.platform, null)
  265. if (!platformInfo || platformInfo.length === 0) {
  266. platformInfo._id = ''
  267. }
  268. this.platforms[data.platform] = platformInfo
  269. if (this.debug) {
  270. console.log('platformInfo', JSON.stringify(platformInfo))
  271. }
  272. }
  273. // 渠道信息
  274. let channelInfo = null
  275. const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel
  276. if (this.channels && this.channels[channelKey]) {
  277. channelInfo = this.channels[channelKey]
  278. } else {
  279. const channel = new Channel()
  280. channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel)
  281. if (!channelInfo || channelInfo.length === 0) {
  282. channelInfo._id = ''
  283. }
  284. this.channels[channelKey] = channelInfo
  285. if (this.debug) {
  286. console.log('channelInfo', JSON.stringify(channelInfo))
  287. }
  288. }
  289. // 版本信息
  290. let versionInfo = null
  291. const versionKey = data.appid + '_' + data.platform + '_' + data.version
  292. if (this.versions && this.versions[versionKey]) {
  293. versionInfo = this.versions[versionKey]
  294. } else {
  295. const version = new Version()
  296. versionInfo = await version.getVersionAndCreate(data.appid, data.platform, data.version)
  297. if (!versionInfo || versionInfo.length === 0) {
  298. versionInfo._id = ''
  299. }
  300. this.versions[versionKey] = versionInfo
  301. if (this.debug) {
  302. console.log('versionInfo', JSON.stringify(versionInfo))
  303. }
  304. }
  305. const datetime = new DateTime()
  306. const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime)
  307. const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime)
  308. if(data.device_ids) {
  309. // 取出本周已经存储的device_id
  310. const weekHaveDeviceList = []
  311. const haveWeekList = await this.selectAll(this.tableName, {
  312. appid: data.appid,
  313. version_id: versionInfo._id,
  314. platform_id: platformInfo._id,
  315. channel_id: channelInfo._id,
  316. device_id: {
  317. $in: data.device_ids
  318. },
  319. dimension: 'week',
  320. create_time: {
  321. $gte: dateDimension.startTime,
  322. $lte: dateDimension.endTime
  323. }
  324. }, {
  325. device_id: 1
  326. })
  327. if (haveWeekList.data.length > 0) {
  328. for (const hui in haveWeekList.data) {
  329. weekHaveDeviceList.push(haveWeekList.data[hui].device_id)
  330. }
  331. }
  332. if (this.debug) {
  333. console.log('weekHaveDeviceList', JSON.stringify(weekHaveDeviceList))
  334. }
  335. // 取出本月已经存储的device_id
  336. const monthHaveDeviceList = []
  337. const haveMonthList = await this.selectAll(this.tableName, {
  338. appid: data.appid,
  339. version_id: versionInfo._id,
  340. platform_id: platformInfo._id,
  341. channel_id: channelInfo._id,
  342. device_id: {
  343. $in: data.device_ids
  344. },
  345. dimension: 'month',
  346. create_time: {
  347. $gte: dateMonthDimension.startTime,
  348. $lte: dateMonthDimension.endTime
  349. }
  350. }, {
  351. device_id: 1
  352. })
  353. if (haveMonthList.data.length > 0) {
  354. for (const hui in haveMonthList.data) {
  355. monthHaveDeviceList.push(haveMonthList.data[hui].device_id)
  356. }
  357. }
  358. if (this.debug) {
  359. console.log('monthHaveDeviceList', JSON.stringify(monthHaveDeviceList))
  360. }
  361. //数据填充
  362. for (const ui in data.device_ids) {
  363. //周活跃数据填充
  364. if (!weekHaveDeviceList.includes(data.device_ids[ui])) {
  365. this.fillData.push({
  366. appid: data.appid,
  367. platform_id: platformInfo._id,
  368. channel_id: channelInfo._id,
  369. version_id: versionInfo._id,
  370. is_new: data.info[data.device_ids[ui]].is_new,
  371. device_id: data.device_ids[ui],
  372. dimension: 'week',
  373. create_time: data.info[data.device_ids[ui]].create_time
  374. })
  375. }
  376. //月活跃数据填充
  377. if (!monthHaveDeviceList.includes(data.device_ids[ui])) {
  378. this.fillData.push({
  379. appid: data.appid,
  380. platform_id: platformInfo._id,
  381. channel_id: channelInfo._id,
  382. version_id: versionInfo._id,
  383. is_new: data.info[data.device_ids[ui]].is_new,
  384. device_id: data.device_ids[ui],
  385. dimension: 'month',
  386. create_time: data.info[data.device_ids[ui]].create_time
  387. })
  388. }
  389. }
  390. }
  391. if(data.old_device_ids) {
  392. // 取出本周已经存储的old_device_id
  393. const weekHaveOldDeviceList = []
  394. const haveOldWeekList = await this.selectAll(this.tableName, {
  395. appid: data.appid,
  396. version_id: versionInfo._id,
  397. platform_id: platformInfo._id,
  398. channel_id: channelInfo._id,
  399. device_id: {
  400. $in: data.old_device_ids
  401. },
  402. dimension: 'week-old',
  403. create_time: {
  404. $gte: dateDimension.startTime,
  405. $lte: dateDimension.endTime
  406. }
  407. }, {
  408. device_id: 1
  409. })
  410. if (haveOldWeekList.data.length > 0) {
  411. for (const hui in haveOldWeekList.data) {
  412. weekHaveOldDeviceList.push(haveOldWeekList.data[hui].device_id)
  413. }
  414. }
  415. if (this.debug) {
  416. console.log('weekHaveOldDeviceList', JSON.stringify(weekHaveOldDeviceList))
  417. }
  418. // 取出本月已经存储的old_device_id
  419. const monthHaveOldDeviceList = []
  420. const haveOldMonthList = await this.selectAll(this.tableName, {
  421. appid: data.appid,
  422. version_id: versionInfo._id,
  423. platform_id: platformInfo._id,
  424. channel_id: channelInfo._id,
  425. device_id: {
  426. $in: data.old_device_ids
  427. },
  428. dimension: 'month-old',
  429. create_time: {
  430. $gte: dateMonthDimension.startTime,
  431. $lte: dateMonthDimension.endTime
  432. }
  433. }, {
  434. device_id: 1
  435. })
  436. if (haveOldMonthList.data.length > 0) {
  437. for (const hui in haveOldMonthList.data) {
  438. monthHaveOldDeviceList.push(haveOldMonthList.data[hui].device_id)
  439. }
  440. }
  441. if (this.debug) {
  442. console.log('monthHaveOldDeviceList', JSON.stringify(monthHaveOldDeviceList))
  443. }
  444. //数据填充
  445. for (const ui in data.old_device_ids) {
  446. //周活跃数据填充
  447. if (!weekHaveOldDeviceList.includes(data.old_device_ids[ui])) {
  448. this.fillData.push({
  449. appid: data.appid,
  450. platform_id: platformInfo._id,
  451. channel_id: channelInfo._id,
  452. version_id: versionInfo._id,
  453. is_new: 0,
  454. device_id: data.old_device_ids[ui],
  455. dimension: 'week-old',
  456. create_time: data.old_info[data.old_device_ids[ui]].create_time
  457. })
  458. }
  459. //月活跃数据填充
  460. if (!monthHaveOldDeviceList.includes(data.old_device_ids[ui])) {
  461. this.fillData.push({
  462. appid: data.appid,
  463. platform_id: platformInfo._id,
  464. channel_id: channelInfo._id,
  465. version_id: versionInfo._id,
  466. is_new: 0,
  467. device_id: data.old_device_ids[ui],
  468. dimension: 'month-old',
  469. create_time: data.old_info[data.old_device_ids[ui]].create_time
  470. })
  471. }
  472. }
  473. }
  474. return true
  475. }
  476. /**
  477. * 日志清理,此处日志为临时数据并不需要自定义清理,默认为固定值即可
  478. */
  479. async clean() {
  480. // 清除周数据,周留存统计最高需要10周数据,多余的为无用数据
  481. const weeks = 10
  482. console.log('Clean device\'s weekly logs - week:', weeks)
  483. const dateTime = new DateTime()
  484. const res = await this.delete(this.tableName, {
  485. dimension: 'week',
  486. create_time: {
  487. $lt: dateTime.getTimeBySetWeek(0 - weeks)
  488. }
  489. })
  490. if (!res.code) {
  491. console.log('Clean device\'s weekly logs - res:', res)
  492. }
  493. // 清除月数据,月留存统计最高需要10个月数据,多余的为无用数据
  494. const monthes = 10
  495. console.log('Clean device\'s monthly logs - month:', monthes)
  496. const monthRes = await this.delete(this.tableName, {
  497. dimension: 'month',
  498. create_time: {
  499. $lt: dateTime.getTimeBySetMonth(0 - monthes)
  500. }
  501. })
  502. if (!monthRes.code) {
  503. console.log('Clean device\'s monthly logs - res:', res)
  504. }
  505. return monthRes
  506. }
  507. }