pageResult.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /**
  2. * @class PageResult 页面结果统计模型
  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 PageLog = require('./pageLog')
  10. const ShareLog = require('./shareLog')
  11. const {
  12. DateTime
  13. } = require('../lib')
  14. module.exports = class PageResult extends BaseMod {
  15. constructor() {
  16. super()
  17. this.tableName = 'page-result'
  18. this.platforms = []
  19. this.channels = []
  20. this.versions = []
  21. }
  22. /**
  23. * 数据统计
  24. * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
  25. * @param {Date|Time} date 指定日期或时间戳
  26. * @param {Boolean} reset 是否重置,为ture时会重置该批次数据
  27. */
  28. async stat(type, date, reset) {
  29. //允许的类型
  30. const allowedType = ['day']
  31. if (!allowedType.includes(type)) {
  32. return {
  33. code: 1002,
  34. msg: 'This type is not allowed'
  35. }
  36. }
  37. this.fillType = type
  38. //获取当前统计的时间范围
  39. const dateTime = new DateTime()
  40. const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
  41. this.startTime = dateDimension.startTime
  42. this.endTime = dateDimension.endTime
  43. if (this.debug) {
  44. console.log('dimension time', this.startTime + '--' + this.endTime)
  45. }
  46. // 查看当前时间段日志是否已存在,防止重复执行
  47. if (!reset) {
  48. const checkRes = await this.getCollection(this.tableName).where({
  49. start_time: this.startTime,
  50. end_time: this.endTime
  51. }).get()
  52. if (checkRes.data.length > 0) {
  53. console.error('This page stat log have exists')
  54. return {
  55. code: 1003,
  56. msg: 'This page stat log have existed'
  57. }
  58. }
  59. } else {
  60. const delRes = await this.delete(this.tableName, {
  61. start_time: this.startTime,
  62. end_time: this.endTime
  63. })
  64. console.log('Delete old data result:', JSON.stringify(delRes))
  65. }
  66. // 数据获取
  67. this.pageLog = new PageLog()
  68. const countRes = await this.getCollection(this.pageLog.tableName).where({
  69. create_time: {
  70. $gte: this.startTime,
  71. $lte: this.endTime
  72. }
  73. }).count()
  74. let pageLogData = []
  75. //临时处理-数据量过大按小时分片组合
  76. if (countRes.total > 1000000) {
  77. const list = {}
  78. for (let start = 0; start < 24; start++) {
  79. const getRes = await this.aggregate(this.pageLog.tableName, {
  80. project: {
  81. appid: 1,
  82. version: 1,
  83. platform: 1,
  84. channel: 1,
  85. page_id: 1,
  86. create_time: 1
  87. },
  88. match: {
  89. create_time: {
  90. $gte: this.startTime + start * 3600000,
  91. $lte: this.startTime + (start + 1) * 3600000 - 1
  92. }
  93. },
  94. group: {
  95. _id: {
  96. appid: '$appid',
  97. version: '$version',
  98. platform: '$platform',
  99. channel: '$channel',
  100. page_id: '$page_id'
  101. },
  102. visit_times: {
  103. $sum: 1
  104. }
  105. },
  106. sort: {
  107. visit_times: 1
  108. },
  109. getAll: true
  110. })
  111. if (getRes.data.length) {
  112. for (let resData of getRes.data) {
  113. const resKey = Object.values(resData._id).join('_')
  114. if (!list[resKey]) {
  115. list[resKey] = resData
  116. } else {
  117. list[resKey].visit_times += resData.visit_times
  118. }
  119. }
  120. }
  121. }
  122. pageLogData = Object.values(list)
  123. } else {
  124. const statRes = await this.aggregate(this.pageLog.tableName, {
  125. project: {
  126. appid: 1,
  127. version: 1,
  128. platform: 1,
  129. channel: 1,
  130. page_id: 1,
  131. create_time: 1
  132. },
  133. match: {
  134. create_time: {
  135. $gte: this.startTime,
  136. $lte: this.endTime
  137. }
  138. },
  139. group: {
  140. _id: {
  141. appid: '$appid',
  142. version: '$version',
  143. platform: '$platform',
  144. channel: '$channel',
  145. page_id: '$page_id'
  146. },
  147. visit_times: {
  148. $sum: 1
  149. }
  150. },
  151. sort: {
  152. visit_times: 1
  153. },
  154. getAll: true
  155. })
  156. pageLogData = statRes.data
  157. }
  158. let res = {
  159. code: 0,
  160. msg: 'success'
  161. }
  162. if (this.debug) {
  163. console.log('Page statRes', JSON.stringify(pageLogData))
  164. }
  165. if (pageLogData.length > 0) {
  166. this.fillData = []
  167. //获取填充数据
  168. for (const i in pageLogData) {
  169. try{
  170. await this.fill(pageLogData[i])
  171. }catch(e){
  172. console.error('page result data filled error', e)
  173. }
  174. }
  175. //数据批量入库
  176. if (this.fillData.length > 0) {
  177. res = await this.batchInsert(this.tableName, this.fillData)
  178. }
  179. }
  180. return res
  181. }
  182. /**
  183. * 页面统计数据填充
  184. * @param {Object} data 统计数据
  185. */
  186. async fill(data) {
  187. // 平台信息
  188. let platformInfo = null
  189. if (this.platforms && this.platforms[data._id.platform]) {
  190. //暂存下数据,减少读库
  191. platformInfo = this.platforms[data._id.platform]
  192. } else {
  193. const platform = new Platform()
  194. platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
  195. if (!platformInfo || platformInfo.length === 0) {
  196. platformInfo._id = ''
  197. }
  198. this.platforms[data._id.platform] = platformInfo
  199. if (this.debug) {
  200. console.log('platformInfo', JSON.stringify(platformInfo))
  201. }
  202. }
  203. // 渠道信息
  204. let channelInfo = null
  205. const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
  206. if (this.channels && this.channels[channelKey]) {
  207. channelInfo = this.channels[channelKey]
  208. } else {
  209. const channel = new Channel()
  210. channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
  211. if (!channelInfo || channelInfo.length === 0) {
  212. channelInfo._id = ''
  213. }
  214. this.channels[channelKey] = channelInfo
  215. if (this.debug) {
  216. console.log('channelInfo', JSON.stringify(channelInfo))
  217. }
  218. }
  219. // 版本信息
  220. let versionInfo = null
  221. const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
  222. if (this.versions && this.versions[versionKey]) {
  223. versionInfo = this.versions[versionKey]
  224. } else {
  225. const version = new Version()
  226. versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
  227. if (!versionInfo || versionInfo.length === 0) {
  228. versionInfo._id = ''
  229. }
  230. this.versions[versionKey] = versionInfo
  231. if (this.debug) {
  232. console.log('versionInfo', JSON.stringify(versionInfo))
  233. }
  234. }
  235. const matchCondition = data._id
  236. Object.assign(matchCondition, {
  237. create_time: {
  238. $gte: this.startTime,
  239. $lte: this.endTime
  240. }
  241. })
  242. if (this.debug) {
  243. console.log('matchCondition', JSON.stringify(matchCondition))
  244. }
  245. // 当前页面访问设备数
  246. const statPageDeviceRes = await this.aggregate(this.pageLog.tableName, {
  247. project: {
  248. appid: 1,
  249. version: 1,
  250. platform: 1,
  251. channel: 1,
  252. device_id: 1,
  253. page_id: 1,
  254. create_time: 1
  255. },
  256. match: matchCondition,
  257. group: [{
  258. _id: {
  259. device_id: '$device_id'
  260. }
  261. }, {
  262. _id: {},
  263. total_devices: {
  264. $sum: 1
  265. }
  266. }]
  267. })
  268. let pageVisitDevices = 0
  269. if (statPageDeviceRes.data.length > 0) {
  270. pageVisitDevices = statPageDeviceRes.data[0].total_devices
  271. }
  272. //当前页面访问人数
  273. const statPageUserRes = await this.aggregate(this.pageLog.tableName, {
  274. project: {
  275. appid: 1,
  276. version: 1,
  277. platform: 1,
  278. channel: 1,
  279. uid: 1,
  280. page_id: 1,
  281. create_time: 1
  282. },
  283. match: {
  284. ...matchCondition,
  285. uid: {
  286. $ne: ''
  287. }
  288. },
  289. group: [{
  290. _id: {
  291. uid: '$uid'
  292. }
  293. }, {
  294. _id: {},
  295. total_users: {
  296. $sum: 1
  297. }
  298. }]
  299. })
  300. let pageVisitUsers = 0
  301. if (statPageUserRes.data.length > 0) {
  302. pageVisitUsers = statPageUserRes.data[0].total_users
  303. }
  304. // 退出次数
  305. const sessionLog = new SessionLog()
  306. let existTimes = 0
  307. const existRes = await this.getCollection(sessionLog.tableName).where({
  308. appid: data._id.appid,
  309. version: data._id.version,
  310. platform: data._id.platform,
  311. channel: data._id.channel,
  312. exit_page_id: data._id.page_id,
  313. create_time: {
  314. $gte: this.startTime,
  315. $lte: this.endTime
  316. }
  317. }).count()
  318. if (existRes && existRes.total > 0) {
  319. existTimes = existRes.total
  320. }
  321. // 访问时长
  322. const statPageDurationRes = await this.aggregate(this.pageLog.tableName, {
  323. project: {
  324. appid: 1,
  325. version: 1,
  326. platform: 1,
  327. channel: 1,
  328. previous_page_id: 1,
  329. previous_page_duration: 1,
  330. create_time: 1
  331. },
  332. match: {
  333. appid: data._id.appid,
  334. version: data._id.version,
  335. platform: data._id.platform,
  336. channel: data._id.channel,
  337. previous_page_id: data._id.page_id,
  338. create_time: {
  339. $gte: this.startTime,
  340. $lte: this.endTime
  341. }
  342. },
  343. group: {
  344. _id: {},
  345. total_duration: {
  346. $sum: '$previous_page_duration'
  347. }
  348. }
  349. })
  350. let totalDuration = 0
  351. if (statPageDurationRes.data.length > 0) {
  352. totalDuration = statPageDurationRes.data[0].total_duration
  353. }
  354. // 分享次数
  355. const shareLog = new ShareLog()
  356. const statShareRes = await this.aggregate(shareLog.tableName, {
  357. project: {
  358. appid: 1,
  359. version: 1,
  360. platform: 1,
  361. channel: 1,
  362. page_id: 1,
  363. create_time: 1
  364. },
  365. match: {
  366. appid: data._id.appid,
  367. version: data._id.version,
  368. platform: data._id.platform,
  369. channel: data._id.channel,
  370. page_id: data._id.page_id,
  371. create_time: {
  372. $gte: this.startTime,
  373. $lte: this.endTime
  374. }
  375. },
  376. group: {
  377. _id: {},
  378. share_count: {
  379. $sum: 1
  380. }
  381. }
  382. })
  383. let shareCount = 0
  384. if (statShareRes.data.length > 0) {
  385. shareCount = statShareRes.data[0].share_count
  386. }
  387. // 作为入口页的总次数和总访问时长
  388. const statPageEntryCountRes = await this.aggregate(this.pageLog.tableName, {
  389. project: {
  390. appid: 1,
  391. version: 1,
  392. platform: 1,
  393. channel: 1,
  394. previous_page_id: 1,
  395. previous_page_duration: 1,
  396. previous_page_is_entry: 1,
  397. create_time: 1
  398. },
  399. match: {
  400. appid: data._id.appid,
  401. version: data._id.version,
  402. platform: data._id.platform,
  403. channel: data._id.channel,
  404. previous_page_id: data._id.page_id,
  405. previous_page_is_entry: 1,
  406. create_time: {
  407. $gte: this.startTime,
  408. $lte: this.endTime
  409. }
  410. },
  411. group: {
  412. _id: {},
  413. entry_count: {
  414. $sum: 1
  415. },
  416. entry_duration: {
  417. $sum: '$previous_page_duration'
  418. }
  419. }
  420. })
  421. let entryCount = 0
  422. let entryDuration = 0
  423. if (statPageEntryCountRes.data.length > 0) {
  424. entryCount = statPageEntryCountRes.data[0].entry_count
  425. entryDuration = statPageEntryCountRes.data[0].entry_duration
  426. }
  427. // 作为入口页的总设备数
  428. const statPageEntryDevicesRes = await this.aggregate(this.pageLog.tableName, {
  429. project: {
  430. appid: 1,
  431. version: 1,
  432. platform: 1,
  433. channel: 1,
  434. device_id: 1,
  435. previous_page_id: 1,
  436. previous_page_is_entry: 1,
  437. create_time: 1
  438. },
  439. match: {
  440. appid: data._id.appid,
  441. version: data._id.version,
  442. platform: data._id.platform,
  443. channel: data._id.channel,
  444. previous_page_id: data._id.page_id,
  445. previous_page_is_entry: 1,
  446. create_time: {
  447. $gte: this.startTime,
  448. $lte: this.endTime
  449. }
  450. },
  451. group: [{
  452. _id: {
  453. device_id: '$device_id'
  454. }
  455. }, {
  456. _id: {},
  457. entry_devices: {
  458. $sum: 1
  459. }
  460. }]
  461. })
  462. let entryDevices = 0
  463. if (statPageEntryDevicesRes.data.length > 0) {
  464. entryDevices = statPageEntryDevicesRes.data[0].entry_devices
  465. }
  466. // 作为入口页的总人数
  467. const statPageEntryUsersRes = await this.aggregate(this.pageLog.tableName, {
  468. project: {
  469. appid: 1,
  470. version: 1,
  471. platform: 1,
  472. channel: 1,
  473. uid: 1,
  474. previous_page_id: 1,
  475. previous_page_is_entry: 1,
  476. create_time: 1
  477. },
  478. match: {
  479. appid: data._id.appid,
  480. version: data._id.version,
  481. platform: data._id.platform,
  482. channel: data._id.channel,
  483. previous_page_id: data._id.page_id,
  484. previous_page_is_entry: 1,
  485. uid: {
  486. $ne: ''
  487. },
  488. create_time: {
  489. $gte: this.startTime,
  490. $lte: this.endTime
  491. }
  492. },
  493. group: [{
  494. _id: {
  495. uid: '$uid'
  496. }
  497. }, {
  498. _id: {},
  499. entry_users: {
  500. $sum: 1
  501. }
  502. }]
  503. })
  504. let entryUsers = 0
  505. if (statPageEntryUsersRes.data.length > 0) {
  506. entryUsers = statPageEntryUsersRes.data[0].entry_users
  507. }
  508. // 跳出率
  509. let bounceTimes = 0
  510. const bounceRes = await this.getCollection(sessionLog.tableName).where({
  511. appid: data._id.appid,
  512. version: data._id.version,
  513. platform: data._id.platform,
  514. channel: data._id.channel,
  515. entry_page_id: data._id.page_id,
  516. page_count: 1,
  517. create_time: {
  518. $gte: this.startTime,
  519. $lte: this.endTime
  520. }
  521. }).count()
  522. if (bounceRes && bounceRes.total > 0) {
  523. bounceTimes = bounceRes.total
  524. }
  525. let bounceRate = 0
  526. if (bounceTimes > 0 && data.visit_times > 0) {
  527. bounceRate = bounceTimes * 100 / data.visit_times
  528. bounceRate = parseFloat(bounceRate.toFixed(2))
  529. }
  530. // 数据填充
  531. const datetime = new DateTime()
  532. const insertParams = {
  533. appid: data._id.appid,
  534. platform_id: platformInfo._id,
  535. channel_id: channelInfo._id,
  536. version_id: versionInfo._id,
  537. page_id: data._id.page_id,
  538. visit_times: data.visit_times,
  539. visit_devices: pageVisitDevices,
  540. visit_users: pageVisitUsers,
  541. exit_times: existTimes,
  542. duration: totalDuration > 0 ? totalDuration : 1,
  543. share_count: shareCount,
  544. entry_users: entryUsers,
  545. entry_devices: entryDevices,
  546. entry_count: entryCount,
  547. entry_duration: entryDuration,
  548. bounce_rate: bounceRate,
  549. dimension: this.fillType,
  550. stat_date: datetime.getDate('Ymd', this.startTime),
  551. start_time: this.startTime,
  552. end_time: this.endTime
  553. }
  554. this.fillData.push(insertParams)
  555. return insertParams
  556. }
  557. }