/** * @class ActiveUsers 活跃用户模型 - 每日跑批合并,仅添加本周/本月首次访问的用户。 */ const BaseMod = require('./base') const Platform = require('./platform') const Channel = require('./channel') const Version = require('./version') const UserSessionLog = require('./userSessionLog') const { DateTime, UniCrypto } = require('../lib') module.exports = class ActiveUsers extends BaseMod { constructor() { super() this.tableName = 'active-users' this.platforms = [] this.channels = [] this.versions = [] } async stat(date, reset) { const dateTime = new DateTime() const dateDimension = dateTime.getTimeDimensionByType('day', -1, date) this.startTime = dateDimension.startTime // 查看当前时间段数据是否已存在,防止重复生成 if (!reset) { const checkRes = await this.getCollection(this.tableName).where({ create_time: { $gte: dateDimension.startTime, $lte: dateDimension.endTime } }).get() if (checkRes.data.length > 0) { console.log('data have exists') return { code: 1003, msg: 'Users data in this time have already existed' } } } else { const delRes = await this.delete(this.tableName, { create_time: { $gte: dateDimension.startTime, $lte: dateDimension.endTime } }) console.log('Delete old data result:', JSON.stringify(delRes)) } const userSessionLog = new UserSessionLog() const statRes = await this.aggregate(userSessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, create_time: 1, uid: 1 }, match: { create_time: { $gte: dateDimension.startTime, $lte: dateDimension.endTime } }, group: { _id: { appid: '$appid', version: '$version', platform: '$platform', channel: '$channel', uid: '$uid' }, create_time: { $min: '$create_time' } }, sort: { create_time: 1 }, getAll: true }) let res = { code: 0, msg: 'success' } // if (this.debug) { // console.log('statRes', JSON.stringify(statRes)) // } if (statRes.data.length > 0) { const uniCrypto = new UniCrypto() // 同应用、平台、渠道、版本的数据合并 const statData = []; let statKey; let data for (const sti in statRes.data) { data = statRes.data[sti] statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id .channel) if (!statData[statKey]) { statData[statKey] = { appid: data._id.appid, platform: data._id.platform, version: data._id.version, channel: data._id.channel, uids: [], info: [] } statData[statKey].uids.push(data._id.uid) statData[statKey].info[data._id.uid] = { create_time: data.create_time } } else { statData[statKey].uids.push(data._id.uid) statData[statKey].info[data._id.uid] = { create_time: data.create_time } } } this.fillData = [] for (const sk in statData) { await this.getFillData(statData[sk]) } if (this.fillData.length > 0) { res = await this.batchInsert(this.tableName, this.fillData) } } return res } async getFillData(data) { // 平台信息 let platformInfo = null if (this.platforms && this.platforms[data.platform]) { platformInfo = this.platforms[data.platform] } else { const platform = new Platform() platformInfo = await platform.getPlatformAndCreate(data.platform, null) if (!platformInfo || platformInfo.length === 0) { platformInfo._id = '' } this.platforms[data.platform] = platformInfo if (this.debug) { console.log('platformInfo', JSON.stringify(platformInfo)) } } // 渠道信息 let channelInfo = null const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel if (this.channels && this.channels[channelKey]) { channelInfo = this.channels[channelKey] } else { const channel = new Channel() channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel) if (!channelInfo || channelInfo.length === 0) { channelInfo._id = '' } this.channels[channelKey] = channelInfo if (this.debug) { console.log('channelInfo', JSON.stringify(channelInfo)) } } // 版本信息 let versionInfo = null const versionKey = data.appid + '_' + data.platform + '_' + data.version if (this.versions && this.versions[versionKey]) { versionInfo = this.versions[versionKey] } else { const version = new Version() versionInfo = await version.getVersionAndCreate(data.appid, data.platform, data.version) if (!versionInfo || versionInfo.length === 0) { versionInfo._id = '' } this.versions[versionKey] = versionInfo if (this.debug) { console.log('versionInfo', JSON.stringify(versionInfo)) } } // 是否在本周内已存在 const datetime = new DateTime() const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime) // 取出本周已经存储的uid const weekHaveUserList = [] const haveWeekList = await this.selectAll(this.tableName, { appid: data.appid, version_id: versionInfo._id, platform_id: platformInfo._id, channel_id: channelInfo._id, uid: { $in: data.uids }, dimension: 'week', create_time: { $gte: dateDimension.startTime, $lte: dateDimension.endTime } }, { uid: 1 }) if (this.debug) { console.log('haveWeekList', JSON.stringify(haveWeekList)) } if (haveWeekList.data.length > 0) { for (const hui in haveWeekList.data) { weekHaveUserList.push(haveWeekList.data[hui].uid) } } // 取出本月已经存储的uid const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime) const monthHaveUserList = [] const haveMonthList = await this.selectAll(this.tableName, { appid: data.appid, version_id: versionInfo._id, platform_id: platformInfo._id, channel_id: channelInfo._id, uid: { $in: data.uids }, dimension: 'month', create_time: { $gte: dateMonthDimension.startTime, $lte: dateMonthDimension.endTime } }, { uid: 1 }) if (this.debug) { console.log('haveMonthList', JSON.stringify(haveMonthList)) } if (haveMonthList.data.length > 0) { for (const hui in haveMonthList.data) { monthHaveUserList.push(haveMonthList.data[hui].uid) } } for (const ui in data.uids) { if (!weekHaveUserList.includes(data.uids[ui])) { this.fillData.push({ appid: data.appid, platform_id: platformInfo._id, channel_id: channelInfo._id, version_id: versionInfo._id, uid: data.uids[ui], dimension: 'week', create_time: data.info[data.uids[ui]].create_time }) } if (!monthHaveUserList.includes(data.uids[ui])) { this.fillData.push({ appid: data.appid, platform_id: platformInfo._id, channel_id: channelInfo._id, version_id: versionInfo._id, uid: data.uids[ui], dimension: 'month', create_time: data.info[data.uids[ui]].create_time }) } } return true } /** * 日志清理,此处日志为临时数据并不需要自定义清理,默认为固定值即可 */ async clean() { // 清除周数据,周留存统计最高需要10周数据,多余的为无用数据 const weeks = 10 console.log('Clean user\'s weekly logs - week:', weeks) const dateTime = new DateTime() const res = await this.delete(this.tableName, { dimension: 'week', create_time: { $lt: dateTime.getTimeBySetWeek(0 - weeks) } }) if (!res.code) { console.log('Clean user\'s weekly logs - res:', res) } // 清除月数据,月留存统计最高需要10个月数据,多余的为无用数据 const monthes = 10 console.log('Clean user\'s monthly logs - month:', monthes) const monthRes = await this.delete(this.tableName, { dimension: 'month', create_time: { $lt: dateTime.getTimeBySetMonth(0 - monthes) } }) if (!monthRes.code) { console.log('Clean user\'s monthly logs - res:', res) } return monthRes } }