activeUsers.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /**
  2. * @class ActiveUsers 活跃用户模型 - 每日跑批合并,仅添加本周/本月首次访问的用户。
  3. */
  4. const BaseMod = require('./base')
  5. const Platform = require('./platform')
  6. const Channel = require('./channel')
  7. const Version = require('./version')
  8. const UserSessionLog = require('./userSessionLog')
  9. const {
  10. DateTime,
  11. UniCrypto
  12. } = require('../lib')
  13. module.exports = class ActiveUsers extends BaseMod {
  14. constructor() {
  15. super()
  16. this.tableName = 'active-users'
  17. this.platforms = []
  18. this.channels = []
  19. this.versions = []
  20. }
  21. async stat(date, reset) {
  22. const dateTime = new DateTime()
  23. const dateDimension = dateTime.getTimeDimensionByType('day', -1, date)
  24. this.startTime = dateDimension.startTime
  25. // 查看当前时间段数据是否已存在,防止重复生成
  26. if (!reset) {
  27. const checkRes = await this.getCollection(this.tableName).where({
  28. create_time: {
  29. $gte: dateDimension.startTime,
  30. $lte: dateDimension.endTime
  31. }
  32. }).get()
  33. if (checkRes.data.length > 0) {
  34. console.log('data have exists')
  35. return {
  36. code: 1003,
  37. msg: 'Users data in this time have already existed'
  38. }
  39. }
  40. } else {
  41. const delRes = await this.delete(this.tableName, {
  42. create_time: {
  43. $gte: dateDimension.startTime,
  44. $lte: dateDimension.endTime
  45. }
  46. })
  47. console.log('Delete old data result:', JSON.stringify(delRes))
  48. }
  49. const userSessionLog = new UserSessionLog()
  50. const statRes = await this.aggregate(userSessionLog.tableName, {
  51. project: {
  52. appid: 1,
  53. version: 1,
  54. platform: 1,
  55. channel: 1,
  56. create_time: 1,
  57. uid: 1
  58. },
  59. match: {
  60. create_time: {
  61. $gte: dateDimension.startTime,
  62. $lte: dateDimension.endTime
  63. }
  64. },
  65. group: {
  66. _id: {
  67. appid: '$appid',
  68. version: '$version',
  69. platform: '$platform',
  70. channel: '$channel',
  71. uid: '$uid'
  72. },
  73. create_time: {
  74. $min: '$create_time'
  75. }
  76. },
  77. sort: {
  78. create_time: 1
  79. },
  80. getAll: true
  81. })
  82. let res = {
  83. code: 0,
  84. msg: 'success'
  85. }
  86. // if (this.debug) {
  87. // console.log('statRes', JSON.stringify(statRes))
  88. // }
  89. if (statRes.data.length > 0) {
  90. const uniCrypto = new UniCrypto()
  91. // 同应用、平台、渠道、版本的数据合并
  92. const statData = [];
  93. let statKey;
  94. let data
  95. for (const sti in statRes.data) {
  96. data = statRes.data[sti]
  97. statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
  98. .channel)
  99. if (!statData[statKey]) {
  100. statData[statKey] = {
  101. appid: data._id.appid,
  102. platform: data._id.platform,
  103. version: data._id.version,
  104. channel: data._id.channel,
  105. uids: [],
  106. info: []
  107. }
  108. statData[statKey].uids.push(data._id.uid)
  109. statData[statKey].info[data._id.uid] = {
  110. create_time: data.create_time
  111. }
  112. } else {
  113. statData[statKey].uids.push(data._id.uid)
  114. statData[statKey].info[data._id.uid] = {
  115. create_time: data.create_time
  116. }
  117. }
  118. }
  119. this.fillData = []
  120. for (const sk in statData) {
  121. await this.getFillData(statData[sk])
  122. }
  123. if (this.fillData.length > 0) {
  124. res = await this.batchInsert(this.tableName, this.fillData)
  125. }
  126. }
  127. return res
  128. }
  129. async getFillData(data) {
  130. // 平台信息
  131. let platformInfo = null
  132. if (this.platforms && this.platforms[data.platform]) {
  133. platformInfo = this.platforms[data.platform]
  134. } else {
  135. const platform = new Platform()
  136. platformInfo = await platform.getPlatformAndCreate(data.platform, null)
  137. if (!platformInfo || platformInfo.length === 0) {
  138. platformInfo._id = ''
  139. }
  140. this.platforms[data.platform] = platformInfo
  141. if (this.debug) {
  142. console.log('platformInfo', JSON.stringify(platformInfo))
  143. }
  144. }
  145. // 渠道信息
  146. let channelInfo = null
  147. const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel
  148. if (this.channels && this.channels[channelKey]) {
  149. channelInfo = this.channels[channelKey]
  150. } else {
  151. const channel = new Channel()
  152. channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel)
  153. if (!channelInfo || channelInfo.length === 0) {
  154. channelInfo._id = ''
  155. }
  156. this.channels[channelKey] = channelInfo
  157. if (this.debug) {
  158. console.log('channelInfo', JSON.stringify(channelInfo))
  159. }
  160. }
  161. // 版本信息
  162. let versionInfo = null
  163. const versionKey = data.appid + '_' + data.platform + '_' + data.version
  164. if (this.versions && this.versions[versionKey]) {
  165. versionInfo = this.versions[versionKey]
  166. } else {
  167. const version = new Version()
  168. versionInfo = await version.getVersionAndCreate(data.appid, data.platform, data.version)
  169. if (!versionInfo || versionInfo.length === 0) {
  170. versionInfo._id = ''
  171. }
  172. this.versions[versionKey] = versionInfo
  173. if (this.debug) {
  174. console.log('versionInfo', JSON.stringify(versionInfo))
  175. }
  176. }
  177. // 是否在本周内已存在
  178. const datetime = new DateTime()
  179. const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime)
  180. // 取出本周已经存储的uid
  181. const weekHaveUserList = []
  182. const haveWeekList = await this.selectAll(this.tableName, {
  183. appid: data.appid,
  184. version_id: versionInfo._id,
  185. platform_id: platformInfo._id,
  186. channel_id: channelInfo._id,
  187. uid: {
  188. $in: data.uids
  189. },
  190. dimension: 'week',
  191. create_time: {
  192. $gte: dateDimension.startTime,
  193. $lte: dateDimension.endTime
  194. }
  195. }, {
  196. uid: 1
  197. })
  198. if (this.debug) {
  199. console.log('haveWeekList', JSON.stringify(haveWeekList))
  200. }
  201. if (haveWeekList.data.length > 0) {
  202. for (const hui in haveWeekList.data) {
  203. weekHaveUserList.push(haveWeekList.data[hui].uid)
  204. }
  205. }
  206. // 取出本月已经存储的uid
  207. const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime)
  208. const monthHaveUserList = []
  209. const haveMonthList = await this.selectAll(this.tableName, {
  210. appid: data.appid,
  211. version_id: versionInfo._id,
  212. platform_id: platformInfo._id,
  213. channel_id: channelInfo._id,
  214. uid: {
  215. $in: data.uids
  216. },
  217. dimension: 'month',
  218. create_time: {
  219. $gte: dateMonthDimension.startTime,
  220. $lte: dateMonthDimension.endTime
  221. }
  222. }, {
  223. uid: 1
  224. })
  225. if (this.debug) {
  226. console.log('haveMonthList', JSON.stringify(haveMonthList))
  227. }
  228. if (haveMonthList.data.length > 0) {
  229. for (const hui in haveMonthList.data) {
  230. monthHaveUserList.push(haveMonthList.data[hui].uid)
  231. }
  232. }
  233. for (const ui in data.uids) {
  234. if (!weekHaveUserList.includes(data.uids[ui])) {
  235. this.fillData.push({
  236. appid: data.appid,
  237. platform_id: platformInfo._id,
  238. channel_id: channelInfo._id,
  239. version_id: versionInfo._id,
  240. uid: data.uids[ui],
  241. dimension: 'week',
  242. create_time: data.info[data.uids[ui]].create_time
  243. })
  244. }
  245. if (!monthHaveUserList.includes(data.uids[ui])) {
  246. this.fillData.push({
  247. appid: data.appid,
  248. platform_id: platformInfo._id,
  249. channel_id: channelInfo._id,
  250. version_id: versionInfo._id,
  251. uid: data.uids[ui],
  252. dimension: 'month',
  253. create_time: data.info[data.uids[ui]].create_time
  254. })
  255. }
  256. }
  257. return true
  258. }
  259. /**
  260. * 日志清理,此处日志为临时数据并不需要自定义清理,默认为固定值即可
  261. */
  262. async clean() {
  263. // 清除周数据,周留存统计最高需要10周数据,多余的为无用数据
  264. const weeks = 10
  265. console.log('Clean user\'s weekly logs - week:', weeks)
  266. const dateTime = new DateTime()
  267. const res = await this.delete(this.tableName, {
  268. dimension: 'week',
  269. create_time: {
  270. $lt: dateTime.getTimeBySetWeek(0 - weeks)
  271. }
  272. })
  273. if (!res.code) {
  274. console.log('Clean user\'s weekly logs - res:', res)
  275. }
  276. // 清除月数据,月留存统计最高需要10个月数据,多余的为无用数据
  277. const monthes = 10
  278. console.log('Clean user\'s monthly logs - month:', monthes)
  279. const monthRes = await this.delete(this.tableName, {
  280. dimension: 'month',
  281. create_time: {
  282. $lt: dateTime.getTimeBySetMonth(0 - monthes)
  283. }
  284. })
  285. if (!monthRes.code) {
  286. console.log('Clean user\'s monthly logs - res:', res)
  287. }
  288. return monthRes
  289. }
  290. }