loyalty.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /**
  2. * 设备/用户忠诚度(粘性)统计模型
  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 UserSessionLog = require('./userSessionLog')
  10. const {
  11. DateTime
  12. } = require('../lib')
  13. module.exports = class Loyalty extends BaseMod {
  14. constructor() {
  15. super()
  16. this.tableName = 'loyalty-result'
  17. this.platforms = []
  18. this.channels = []
  19. this.versions = []
  20. }
  21. /**
  22. * 设备/用户忠诚度(粘性)统计
  23. * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
  24. * @param {Date|Time} date 指定日期或时间戳
  25. * @param {Boolean} reset 是否重置,为ture时会重置该批次数据
  26. */
  27. async stat(type, date, reset) {
  28. const allowedType = ['day']
  29. if (!allowedType.includes(type)) {
  30. return {
  31. code: 1002,
  32. msg: 'This type is not allowed'
  33. }
  34. }
  35. this.fillType = type
  36. const dateTime = new DateTime()
  37. const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
  38. this.startTime = dateDimension.startTime
  39. this.endTime = dateDimension.endTime
  40. if (this.debug) {
  41. console.log('this time', dateTime.getTime())
  42. console.log('dimension time', this.startTime + '--' + this.endTime)
  43. }
  44. // 查看当前时间段日志是否已存在,防止重复生成
  45. if (!reset) {
  46. const checkRes = await this.getCollection(this.tableName).where({
  47. start_time: this.startTime,
  48. end_time: this.endTime
  49. }).get()
  50. if (checkRes.data.length > 0) {
  51. console.log('loyalty log have existed')
  52. return {
  53. code: 1003,
  54. msg: 'This log have existed'
  55. }
  56. }
  57. } else {
  58. const delRes = await this.delete(this.tableName, {
  59. start_time: this.startTime,
  60. end_time: this.endTime
  61. })
  62. console.log('delete old data result:', JSON.stringify(delRes))
  63. }
  64. // 数据获取
  65. this.sessionLog = new SessionLog()
  66. const statRes = await this.aggregate(this.sessionLog.tableName, {
  67. project: {
  68. appid: 1,
  69. version: 1,
  70. platform: 1,
  71. channel: 1,
  72. page_count: 1,
  73. duration: 1,
  74. create_time: 1
  75. },
  76. match: {
  77. create_time: {
  78. $gte: this.startTime,
  79. $lte: this.endTime
  80. }
  81. },
  82. group: {
  83. _id: {
  84. appid: '$appid',
  85. version: '$version',
  86. platform: '$platform',
  87. channel: '$channel'
  88. },
  89. page_count_sum: {
  90. $sum: '$page_count'
  91. },
  92. duration_sum: {
  93. $sum: '$duration'
  94. }
  95. },
  96. sort: {
  97. page_count_sum: 1,
  98. duration_sum: 1
  99. },
  100. getAll: true
  101. })
  102. let res = {
  103. code: 0,
  104. msg: 'success'
  105. }
  106. if (this.debug) {
  107. console.log('statRes', JSON.stringify(statRes))
  108. }
  109. if (statRes.data.length > 0) {
  110. this.fillData = []
  111. for (const i in statRes.data) {
  112. await this.fill(statRes.data[i])
  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. platformInfo = this.platforms[data._id.platform]
  129. } else {
  130. const platform = new Platform()
  131. platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
  132. if (!platformInfo || platformInfo.length === 0) {
  133. platformInfo._id = ''
  134. }
  135. this.platforms[data._id.platform] = platformInfo
  136. if (this.debug) {
  137. console.log('platformInfo', JSON.stringify(platformInfo))
  138. }
  139. }
  140. // 渠道信息
  141. let channelInfo = null
  142. const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
  143. if (this.channels && this.channels[channelKey]) {
  144. channelInfo = this.channels[channelKey]
  145. } else {
  146. const channel = new Channel()
  147. channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
  148. if (!channelInfo || channelInfo.length === 0) {
  149. channelInfo._id = ''
  150. }
  151. this.channels[channelKey] = channelInfo
  152. if (this.debug) {
  153. console.log('channelInfo', JSON.stringify(channelInfo))
  154. }
  155. }
  156. // 版本信息
  157. let versionInfo = null
  158. const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
  159. if (this.versions && this.versions[versionKey]) {
  160. versionInfo = this.versions[versionKey]
  161. } else {
  162. const version = new Version()
  163. versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
  164. if (!versionInfo || versionInfo.length === 0) {
  165. versionInfo._id = ''
  166. }
  167. this.versions[versionKey] = versionInfo
  168. if (this.debug) {
  169. console.log('versionInfo', JSON.stringify(versionInfo))
  170. }
  171. }
  172. // 访问深度-用户数统计和访问次数
  173. const pageMark = [1, 2, 3, 4, [5, 10], [10]]
  174. const matchCondition = Object.assign(data._id, {
  175. create_time: {
  176. $gte: this.startTime,
  177. $lte: this.endTime
  178. }
  179. })
  180. const visitDepthData = {
  181. visit_devices: {},
  182. visit_users: {},
  183. visit_times: {}
  184. }
  185. const userSessionLog = new UserSessionLog()
  186. //根据各访问页面数区间统计
  187. for (const pi in pageMark) {
  188. let pageMarkCondition = {
  189. page_count: pageMark[pi]
  190. }
  191. if (Array.isArray(pageMark[pi])) {
  192. if (pageMark[pi].length === 2) {
  193. pageMarkCondition = {
  194. page_count: {
  195. $gte: pageMark[pi][0],
  196. $lte: pageMark[pi][1]
  197. }
  198. }
  199. } else {
  200. pageMarkCondition = {
  201. page_count: {
  202. $gt: pageMark[pi][0]
  203. }
  204. }
  205. }
  206. }
  207. // 访问次数(会话次数)统计
  208. const searchCondition = {
  209. ...matchCondition,
  210. ...pageMarkCondition
  211. }
  212. const vistRes = await this.aggregate(this.sessionLog.tableName, {
  213. project: {
  214. appid: 1,
  215. version: 1,
  216. platform: 1,
  217. channel: 1,
  218. page_count: 1,
  219. create_time: 1
  220. },
  221. match: searchCondition,
  222. group: {
  223. _id: {},
  224. total_visits: {
  225. $sum: 1
  226. }
  227. }
  228. })
  229. if (this.debug) {
  230. console.log('vistResCondtion', JSON.stringify(searchCondition))
  231. console.log('vistRes', JSON.stringify(vistRes))
  232. }
  233. let vistCount = 0
  234. if (vistRes.data.length > 0) {
  235. vistCount = vistRes.data[0].total_visits
  236. }
  237. // 设备数统计
  238. const deviceRes = await this.aggregate(this.sessionLog.tableName, {
  239. project: {
  240. appid: 1,
  241. version: 1,
  242. platform: 1,
  243. channel: 1,
  244. page_count: 1,
  245. create_time: 1,
  246. device_id: 1
  247. },
  248. match: searchCondition,
  249. group: [{
  250. _id: {
  251. device_id: '$device_id'
  252. }
  253. }, {
  254. _id: {},
  255. total_devices: {
  256. $sum: 1
  257. }
  258. }]
  259. })
  260. if (this.debug) {
  261. console.log('searchCondition', JSON.stringify(searchCondition))
  262. console.log('deviceRes', JSON.stringify(deviceRes))
  263. }
  264. let deviceCount = 0
  265. if (deviceRes.data.length > 0) {
  266. deviceCount = deviceRes.data[0].total_devices
  267. }
  268. // 用户数统计
  269. const userRes = await this.aggregate(userSessionLog.tableName, {
  270. project: {
  271. appid: 1,
  272. version: 1,
  273. platform: 1,
  274. channel: 1,
  275. page_count: 1,
  276. create_time: 1,
  277. uid: 1
  278. },
  279. match: searchCondition,
  280. group: [{
  281. _id: {
  282. uid: '$uid'
  283. }
  284. }, {
  285. _id: {},
  286. total_users: {
  287. $sum: 1
  288. }
  289. }]
  290. })
  291. if (this.debug) {
  292. console.log('userResCondtion', JSON.stringify(searchCondition))
  293. console.log('userRes', JSON.stringify(userRes))
  294. }
  295. let userCount = 0
  296. if (userRes.data.length > 0) {
  297. userCount = userRes.data[0].total_users
  298. }
  299. const pageKey = 'p_' + (Array.isArray(pageMark[pi]) ? pageMark[pi][0] : pageMark[pi])
  300. visitDepthData.visit_devices[pageKey] = deviceCount
  301. visitDepthData.visit_users[pageKey] = userCount
  302. visitDepthData.visit_times[pageKey] = vistCount
  303. }
  304. // 访问时长-用户数统计和访问次数
  305. const durationMark = [
  306. [0, 2],
  307. [3, 5],
  308. [6, 10],
  309. [11, 20],
  310. [21, 30],
  311. [31, 50],
  312. [51, 100],
  313. [100]
  314. ]
  315. const durationData = {
  316. visit_devices: {},
  317. visit_users: {},
  318. visit_times: {}
  319. }
  320. //根据各访问时长区间统计
  321. for (const di in durationMark) {
  322. let durationMarkCondition = {
  323. duration: durationMark[di]
  324. }
  325. if (Array.isArray(durationMark[di])) {
  326. if (durationMark[di].length === 2) {
  327. durationMarkCondition = {
  328. duration: {
  329. $gte: durationMark[di][0],
  330. $lte: durationMark[di][1]
  331. }
  332. }
  333. } else {
  334. durationMarkCondition = {
  335. duration: {
  336. $gt: durationMark[di][0]
  337. }
  338. }
  339. }
  340. }
  341. // 访问次数(会话次数)统计
  342. const searchCondition = {
  343. ...matchCondition,
  344. ...durationMarkCondition
  345. }
  346. if (this.debug) {
  347. console.log('searchCondition', JSON.stringify(searchCondition))
  348. }
  349. const vistRes = await this.aggregate(this.sessionLog.tableName, {
  350. project: {
  351. appid: 1,
  352. version: 1,
  353. platform: 1,
  354. channel: 1,
  355. duration: 1,
  356. create_time: 1
  357. },
  358. match: searchCondition,
  359. group: {
  360. _id: {},
  361. total_visits: {
  362. $sum: 1
  363. }
  364. }
  365. })
  366. if (this.debug) {
  367. console.log('vistRes', JSON.stringify(vistRes))
  368. }
  369. let vistCount = 0
  370. if (vistRes.data.length > 0) {
  371. vistCount = vistRes.data[0].total_visits
  372. }
  373. // 设备数统计
  374. const deviceRes = await this.aggregate(this.sessionLog.tableName, {
  375. project: {
  376. appid: 1,
  377. version: 1,
  378. platform: 1,
  379. channel: 1,
  380. device_id: 1,
  381. duration: 1,
  382. create_time: 1
  383. },
  384. match: searchCondition,
  385. group: [{
  386. _id: {
  387. device_id: '$device_id'
  388. }
  389. }, {
  390. _id: {},
  391. total_devices: {
  392. $sum: 1
  393. }
  394. }]
  395. })
  396. if (this.debug) {
  397. console.log('userRes', JSON.stringify(deviceRes))
  398. }
  399. let deviceCount = 0
  400. if (deviceRes.data.length > 0) {
  401. deviceCount = deviceRes.data[0].total_devices
  402. }
  403. // 用户数统计
  404. const userRes = await this.aggregate(userSessionLog.tableName, {
  405. project: {
  406. appid: 1,
  407. version: 1,
  408. platform: 1,
  409. channel: 1,
  410. uid: 1,
  411. duration: 1,
  412. create_time: 1
  413. },
  414. match: searchCondition,
  415. group: [{
  416. _id: {
  417. uid: '$uid'
  418. }
  419. }, {
  420. _id: {},
  421. total_users: {
  422. $sum: 1
  423. }
  424. }]
  425. })
  426. if (this.debug) {
  427. console.log('userRes', JSON.stringify(userRes))
  428. }
  429. let userCount = 0
  430. if (userRes.data.length > 0) {
  431. userCount = userRes.data[0].total_users
  432. }
  433. const pageKey = 's_' + (Array.isArray(durationMark[di]) ? durationMark[di][0] : durationMark[di])
  434. durationData.visit_devices[pageKey] = deviceCount
  435. durationData.visit_users[pageKey] = userCount
  436. durationData.visit_times[pageKey] = vistCount
  437. }
  438. // 数据填充
  439. const datetime = new DateTime()
  440. const insertParams = {
  441. appid: data._id.appid,
  442. platform_id: platformInfo._id,
  443. channel_id: channelInfo._id,
  444. version_id: versionInfo._id,
  445. visit_depth_data: visitDepthData,
  446. duration_data: durationData,
  447. stat_date: datetime.getDate('Ymd', this.startTime),
  448. start_time: this.startTime,
  449. end_time: this.endTime
  450. }
  451. this.fillData.push(insertParams)
  452. return insertParams
  453. }
  454. }