pageResult.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  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 statRes = await this.aggregate(this.pageLog.tableName, {
  69. project: {
  70. appid: 1,
  71. version: 1,
  72. platform: 1,
  73. channel: 1,
  74. page_id: 1,
  75. create_time: 1
  76. },
  77. match: {
  78. create_time: {
  79. $gte: this.startTime,
  80. $lte: this.endTime
  81. }
  82. },
  83. group: {
  84. _id: {
  85. appid: '$appid',
  86. version: '$version',
  87. platform: '$platform',
  88. channel: '$channel',
  89. page_id: '$page_id'
  90. },
  91. visit_times: {
  92. $sum: 1
  93. }
  94. },
  95. sort: {
  96. visit_times: 1
  97. },
  98. getAll: true
  99. })
  100. let res = {
  101. code: 0,
  102. msg: 'success'
  103. }
  104. if (this.debug) {
  105. console.log('Page statRes', JSON.stringify(statRes))
  106. }
  107. if (statRes.data.length > 0) {
  108. this.fillData = []
  109. //获取填充数据
  110. for (const i in statRes.data) {
  111. await this.fill(statRes.data[i])
  112. }
  113. //数据批量入库
  114. if (this.fillData.length > 0) {
  115. res = await this.batchInsert(this.tableName, this.fillData)
  116. }
  117. }
  118. return res
  119. }
  120. /**
  121. * 页面统计数据填充
  122. * @param {Object} data 统计数据
  123. */
  124. async fill(data) {
  125. // 平台信息
  126. let platformInfo = null
  127. if (this.platforms && this.platforms[data._id.platform]) {
  128. //暂存下数据,减少读库
  129. platformInfo = this.platforms[data._id.platform]
  130. } else {
  131. const platform = new Platform()
  132. platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
  133. if (!platformInfo || platformInfo.length === 0) {
  134. platformInfo._id = ''
  135. }
  136. this.platforms[data._id.platform] = platformInfo
  137. if (this.debug) {
  138. console.log('platformInfo', JSON.stringify(platformInfo))
  139. }
  140. }
  141. // 渠道信息
  142. let channelInfo = null
  143. const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
  144. if (this.channels && this.channels[channelKey]) {
  145. channelInfo = this.channels[channelKey]
  146. } else {
  147. const channel = new Channel()
  148. channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
  149. if (!channelInfo || channelInfo.length === 0) {
  150. channelInfo._id = ''
  151. }
  152. this.channels[channelKey] = channelInfo
  153. if (this.debug) {
  154. console.log('channelInfo', JSON.stringify(channelInfo))
  155. }
  156. }
  157. // 版本信息
  158. let versionInfo = null
  159. const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
  160. if (this.versions && this.versions[versionKey]) {
  161. versionInfo = this.versions[versionKey]
  162. } else {
  163. const version = new Version()
  164. versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
  165. if (!versionInfo || versionInfo.length === 0) {
  166. versionInfo._id = ''
  167. }
  168. this.versions[versionKey] = versionInfo
  169. if (this.debug) {
  170. console.log('versionInfo', JSON.stringify(versionInfo))
  171. }
  172. }
  173. const matchCondition = data._id
  174. Object.assign(matchCondition, {
  175. create_time: {
  176. $gte: this.startTime,
  177. $lte: this.endTime
  178. }
  179. })
  180. if (this.debug) {
  181. console.log('matchCondition', JSON.stringify(matchCondition))
  182. }
  183. // 当前页面访问设备数
  184. const statPageDeviceRes = await this.aggregate(this.pageLog.tableName, {
  185. project: {
  186. appid: 1,
  187. version: 1,
  188. platform: 1,
  189. channel: 1,
  190. device_id: 1,
  191. page_id: 1,
  192. create_time: 1
  193. },
  194. match: matchCondition,
  195. group: [{
  196. _id: {
  197. device_id: '$device_id'
  198. }
  199. }, {
  200. _id: {},
  201. total_devices: {
  202. $sum: 1
  203. }
  204. }]
  205. })
  206. let pageVisitDevices = 0
  207. if (statPageDeviceRes.data.length > 0) {
  208. pageVisitDevices = statPageDeviceRes.data[0].total_devices
  209. }
  210. // 当前页面访问人数
  211. const statPageUserRes = await this.aggregate(this.pageLog.tableName, {
  212. project: {
  213. appid: 1,
  214. version: 1,
  215. platform: 1,
  216. channel: 1,
  217. uid: 1,
  218. page_id: 1,
  219. create_time: 1
  220. },
  221. match: {
  222. ...matchCondition,
  223. uid: {
  224. $ne: ''
  225. }
  226. },
  227. group: [{
  228. _id: {
  229. uid: '$uid'
  230. }
  231. }, {
  232. _id: {},
  233. total_users: {
  234. $sum: 1
  235. }
  236. }]
  237. })
  238. let pageVisitUsers = 0
  239. if (statPageUserRes.data.length > 0) {
  240. pageVisitUsers = statPageUserRes.data[0].total_users
  241. }
  242. // 退出次数
  243. const sessionLog = new SessionLog()
  244. let existTimes = 0
  245. const existRes = await this.getCollection(sessionLog.tableName).where({
  246. appid: data._id.appid,
  247. version: data._id.version,
  248. platform: data._id.platform,
  249. channel: data._id.channel,
  250. exit_page_id: data._id.page_id,
  251. create_time: {
  252. $gte: this.startTime,
  253. $lte: this.endTime
  254. }
  255. }).count()
  256. if (existRes && existRes.total > 0) {
  257. existTimes = existRes.total
  258. }
  259. // 访问时长
  260. const statPageDurationRes = await this.aggregate(this.pageLog.tableName, {
  261. project: {
  262. appid: 1,
  263. version: 1,
  264. platform: 1,
  265. channel: 1,
  266. previous_page_id: 1,
  267. previous_page_duration: 1,
  268. create_time: 1
  269. },
  270. match: {
  271. appid: data._id.appid,
  272. version: data._id.version,
  273. platform: data._id.platform,
  274. channel: data._id.channel,
  275. previous_page_id: data._id.page_id,
  276. create_time: {
  277. $gte: this.startTime,
  278. $lte: this.endTime
  279. }
  280. },
  281. group: {
  282. _id: {},
  283. total_duration: {
  284. $sum: '$previous_page_duration'
  285. }
  286. }
  287. })
  288. let totalDuration = 0
  289. if (statPageDurationRes.data.length > 0) {
  290. totalDuration = statPageDurationRes.data[0].total_duration
  291. }
  292. // 分享次数
  293. const shareLog = new ShareLog()
  294. const statShareRes = await this.aggregate(shareLog.tableName, {
  295. project: {
  296. appid: 1,
  297. version: 1,
  298. platform: 1,
  299. channel: 1,
  300. page_id: 1,
  301. create_time: 1
  302. },
  303. match: {
  304. appid: data._id.appid,
  305. version: data._id.version,
  306. platform: data._id.platform,
  307. channel: data._id.channel,
  308. page_id: data._id.page_id,
  309. create_time: {
  310. $gte: this.startTime,
  311. $lte: this.endTime
  312. }
  313. },
  314. group: {
  315. _id: {},
  316. share_count: {
  317. $sum: 1
  318. }
  319. }
  320. })
  321. let shareCount = 0
  322. if (statShareRes.data.length > 0) {
  323. shareCount = statShareRes.data[0].share_count
  324. }
  325. // 作为入口页的总次数和总访问时长
  326. const statPageEntryCountRes = await this.aggregate(this.pageLog.tableName, {
  327. project: {
  328. appid: 1,
  329. version: 1,
  330. platform: 1,
  331. channel: 1,
  332. previous_page_id: 1,
  333. previous_page_duration: 1,
  334. previous_page_is_entry: 1,
  335. create_time: 1
  336. },
  337. match: {
  338. appid: data._id.appid,
  339. version: data._id.version,
  340. platform: data._id.platform,
  341. channel: data._id.channel,
  342. previous_page_id: data._id.page_id,
  343. previous_page_is_entry: 1,
  344. create_time: {
  345. $gte: this.startTime,
  346. $lte: this.endTime
  347. }
  348. },
  349. group: {
  350. _id: {},
  351. entry_count: {
  352. $sum: 1
  353. },
  354. entry_duration: {
  355. $sum: '$previous_page_duration'
  356. }
  357. }
  358. })
  359. let entryCount = 0
  360. let entryDuration = 0
  361. if (statPageEntryCountRes.data.length > 0) {
  362. entryCount = statPageEntryCountRes.data[0].entry_count
  363. entryDuration = statPageEntryCountRes.data[0].entry_duration
  364. }
  365. // 作为入口页的总设备数
  366. const statPageEntryDevicesRes = await this.aggregate(this.pageLog.tableName, {
  367. project: {
  368. appid: 1,
  369. version: 1,
  370. platform: 1,
  371. channel: 1,
  372. device_id: 1,
  373. previous_page_id: 1,
  374. previous_page_is_entry: 1,
  375. create_time: 1
  376. },
  377. match: {
  378. appid: data._id.appid,
  379. version: data._id.version,
  380. platform: data._id.platform,
  381. channel: data._id.channel,
  382. previous_page_id: data._id.page_id,
  383. previous_page_is_entry: 1,
  384. create_time: {
  385. $gte: this.startTime,
  386. $lte: this.endTime
  387. }
  388. },
  389. group: [{
  390. _id: {
  391. device_id: '$device_id'
  392. }
  393. }, {
  394. _id: {},
  395. entry_devices: {
  396. $sum: 1
  397. }
  398. }]
  399. })
  400. let entryDevices = 0
  401. if (statPageEntryDevicesRes.data.length > 0) {
  402. entryDevices = statPageEntryDevicesRes.data[0].entry_devices
  403. }
  404. // 作为入口页的总人数
  405. const statPageEntryUsersRes = await this.aggregate(this.pageLog.tableName, {
  406. project: {
  407. appid: 1,
  408. version: 1,
  409. platform: 1,
  410. channel: 1,
  411. uid: 1,
  412. previous_page_id: 1,
  413. previous_page_is_entry: 1,
  414. create_time: 1
  415. },
  416. match: {
  417. appid: data._id.appid,
  418. version: data._id.version,
  419. platform: data._id.platform,
  420. channel: data._id.channel,
  421. previous_page_id: data._id.page_id,
  422. previous_page_is_entry: 1,
  423. uid: {
  424. $ne: ''
  425. },
  426. create_time: {
  427. $gte: this.startTime,
  428. $lte: this.endTime
  429. }
  430. },
  431. group: [{
  432. _id: {
  433. uid: '$uid'
  434. }
  435. }, {
  436. _id: {},
  437. entry_users: {
  438. $sum: 1
  439. }
  440. }]
  441. })
  442. let entryUsers = 0
  443. if (statPageEntryUsersRes.data.length > 0) {
  444. entryUsers = statPageEntryUsersRes.data[0].entry_users
  445. }
  446. // 跳出率
  447. let bounceTimes = 0
  448. const bounceRes = await this.getCollection(sessionLog.tableName).where({
  449. appid: data._id.appid,
  450. version: data._id.version,
  451. platform: data._id.platform,
  452. channel: data._id.channel,
  453. entry_page_id: data._id.page_id,
  454. page_count: 1,
  455. create_time: {
  456. $gte: this.startTime,
  457. $lte: this.endTime
  458. }
  459. }).count()
  460. if (bounceRes && bounceRes.total > 0) {
  461. bounceTimes = bounceRes.total
  462. }
  463. let bounceRate = 0
  464. if (bounceTimes > 0 && data.visit_times > 0) {
  465. bounceRate = bounceTimes * 100 / data.visit_times
  466. bounceRate = parseFloat(bounceRate.toFixed(2))
  467. }
  468. // 数据填充
  469. const datetime = new DateTime()
  470. const insertParams = {
  471. appid: data._id.appid,
  472. platform_id: platformInfo._id,
  473. channel_id: channelInfo._id,
  474. version_id: versionInfo._id,
  475. page_id: data._id.page_id,
  476. visit_times: data.visit_times,
  477. visit_devices: pageVisitDevices,
  478. visit_users: pageVisitUsers,
  479. exit_times: existTimes,
  480. duration: totalDuration > 0 ? totalDuration : 1,
  481. share_count: shareCount,
  482. entry_users: entryUsers,
  483. entry_devices: entryDevices,
  484. entry_count: entryCount,
  485. entry_duration: entryDuration,
  486. bounce_rate: bounceRate,
  487. dimension: this.fillType,
  488. stat_date: datetime.getDate('Ymd', this.startTime),
  489. start_time: this.startTime,
  490. end_time: this.endTime
  491. }
  492. this.fillData.push(insertParams)
  493. return insertParams
  494. }
  495. }