/** * 设备/用户忠诚度(粘性)统计模型 */ const BaseMod = require('./base') const Platform = require('./platform') const Channel = require('./channel') const Version = require('./version') const SessionLog = require('./sessionLog') const UserSessionLog = require('./userSessionLog') const { DateTime } = require('../lib') module.exports = class Loyalty extends BaseMod { constructor() { super() this.tableName = 'loyalty-result' this.platforms = [] this.channels = [] this.versions = [] } /** * 设备/用户忠诚度(粘性)统计 * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计 * @param {Date|Time} date 指定日期或时间戳 * @param {Boolean} reset 是否重置,为ture时会重置该批次数据 */ async stat(type, date, reset) { const allowedType = ['day'] if (!allowedType.includes(type)) { return { code: 1002, msg: 'This type is not allowed' } } this.fillType = type const dateTime = new DateTime() const dateDimension = dateTime.getTimeDimensionByType(type, -1, date) this.startTime = dateDimension.startTime this.endTime = dateDimension.endTime if (this.debug) { console.log('this time', dateTime.getTime()) console.log('dimension time', this.startTime + '--' + this.endTime) } // 查看当前时间段日志是否已存在,防止重复生成 if (!reset) { const checkRes = await this.getCollection(this.tableName).where({ start_time: this.startTime, end_time: this.endTime }).get() if (checkRes.data.length > 0) { console.log('loyalty log have existed') return { code: 1003, msg: 'This log have existed' } } } else { const delRes = await this.delete(this.tableName, { start_time: this.startTime, end_time: this.endTime }) console.log('delete old data result:', JSON.stringify(delRes)) } // 数据获取 this.sessionLog = new SessionLog() const statRes = await this.aggregate(this.sessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, page_count: 1, duration: 1, create_time: 1 }, match: { create_time: { $gte: this.startTime, $lte: this.endTime } }, group: { _id: { appid: '$appid', version: '$version', platform: '$platform', channel: '$channel' }, page_count_sum: { $sum: '$page_count' }, duration_sum: { $sum: '$duration' } }, sort: { page_count_sum: 1, duration_sum: 1 }, getAll: true }) let res = { code: 0, msg: 'success' } if (this.debug) { console.log('statRes', JSON.stringify(statRes)) } if (statRes.data.length > 0) { this.fillData = [] for (const i in statRes.data) { await this.fill(statRes.data[i]) } if (this.fillData.length > 0) { res = await this.batchInsert(this.tableName, this.fillData) } } return res } /** * 设备/用户忠诚度(粘性)数据填充 * @param {Object} data 数据集合 */ async fill(data) { // 平台信息 let platformInfo = null if (this.platforms && this.platforms[data._id.platform]) { platformInfo = this.platforms[data._id.platform] } else { const platform = new Platform() platformInfo = await platform.getPlatformAndCreate(data._id.platform, null) if (!platformInfo || platformInfo.length === 0) { platformInfo._id = '' } this.platforms[data._id.platform] = platformInfo if (this.debug) { console.log('platformInfo', JSON.stringify(platformInfo)) } } // 渠道信息 let channelInfo = null const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel if (this.channels && this.channels[channelKey]) { channelInfo = this.channels[channelKey] } else { const channel = new Channel() channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.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._id.appid + '_' + data._id.platform + '_' + data._id.version if (this.versions && this.versions[versionKey]) { versionInfo = this.versions[versionKey] } else { const version = new Version() versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version) if (!versionInfo || versionInfo.length === 0) { versionInfo._id = '' } this.versions[versionKey] = versionInfo if (this.debug) { console.log('versionInfo', JSON.stringify(versionInfo)) } } // 访问深度-用户数统计和访问次数 const pageMark = [1, 2, 3, 4, [5, 10], [10]] const matchCondition = Object.assign(data._id, { create_time: { $gte: this.startTime, $lte: this.endTime } }) const visitDepthData = { visit_devices: {}, visit_users: {}, visit_times: {} } const userSessionLog = new UserSessionLog() //根据各访问页面数区间统计 for (const pi in pageMark) { let pageMarkCondition = { page_count: pageMark[pi] } if (Array.isArray(pageMark[pi])) { if (pageMark[pi].length === 2) { pageMarkCondition = { page_count: { $gte: pageMark[pi][0], $lte: pageMark[pi][1] } } } else { pageMarkCondition = { page_count: { $gt: pageMark[pi][0] } } } } // 访问次数(会话次数)统计 const searchCondition = { ...matchCondition, ...pageMarkCondition } const vistRes = await this.aggregate(this.sessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, page_count: 1, create_time: 1 }, match: searchCondition, group: { _id: {}, total_visits: { $sum: 1 } } }) if (this.debug) { console.log('vistResCondtion', JSON.stringify(searchCondition)) console.log('vistRes', JSON.stringify(vistRes)) } let vistCount = 0 if (vistRes.data.length > 0) { vistCount = vistRes.data[0].total_visits } // 设备数统计 const deviceRes = await this.aggregate(this.sessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, page_count: 1, create_time: 1, device_id: 1 }, match: searchCondition, group: [{ _id: { device_id: '$device_id' } }, { _id: {}, total_devices: { $sum: 1 } }] }) if (this.debug) { console.log('searchCondition', JSON.stringify(searchCondition)) console.log('deviceRes', JSON.stringify(deviceRes)) } let deviceCount = 0 if (deviceRes.data.length > 0) { deviceCount = deviceRes.data[0].total_devices } // 用户数统计 const userRes = await this.aggregate(userSessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, page_count: 1, create_time: 1, uid: 1 }, match: searchCondition, group: [{ _id: { uid: '$uid' } }, { _id: {}, total_users: { $sum: 1 } }] }) if (this.debug) { console.log('userResCondtion', JSON.stringify(searchCondition)) console.log('userRes', JSON.stringify(userRes)) } let userCount = 0 if (userRes.data.length > 0) { userCount = userRes.data[0].total_users } const pageKey = 'p_' + (Array.isArray(pageMark[pi]) ? pageMark[pi][0] : pageMark[pi]) visitDepthData.visit_devices[pageKey] = deviceCount visitDepthData.visit_users[pageKey] = userCount visitDepthData.visit_times[pageKey] = vistCount } // 访问时长-用户数统计和访问次数 const durationMark = [ [0, 2], [3, 5], [6, 10], [11, 20], [21, 30], [31, 50], [51, 100], [100] ] const durationData = { visit_devices: {}, visit_users: {}, visit_times: {} } //根据各访问时长区间统计 for (const di in durationMark) { let durationMarkCondition = { duration: durationMark[di] } if (Array.isArray(durationMark[di])) { if (durationMark[di].length === 2) { durationMarkCondition = { duration: { $gte: durationMark[di][0], $lte: durationMark[di][1] } } } else { durationMarkCondition = { duration: { $gt: durationMark[di][0] } } } } // 访问次数(会话次数)统计 const searchCondition = { ...matchCondition, ...durationMarkCondition } if (this.debug) { console.log('searchCondition', JSON.stringify(searchCondition)) } const vistRes = await this.aggregate(this.sessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, duration: 1, create_time: 1 }, match: searchCondition, group: { _id: {}, total_visits: { $sum: 1 } } }) if (this.debug) { console.log('vistRes', JSON.stringify(vistRes)) } let vistCount = 0 if (vistRes.data.length > 0) { vistCount = vistRes.data[0].total_visits } // 设备数统计 const deviceRes = await this.aggregate(this.sessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, device_id: 1, duration: 1, create_time: 1 }, match: searchCondition, group: [{ _id: { device_id: '$device_id' } }, { _id: {}, total_devices: { $sum: 1 } }] }) if (this.debug) { console.log('userRes', JSON.stringify(deviceRes)) } let deviceCount = 0 if (deviceRes.data.length > 0) { deviceCount = deviceRes.data[0].total_devices } // 用户数统计 const userRes = await this.aggregate(userSessionLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, uid: 1, duration: 1, create_time: 1 }, match: searchCondition, group: [{ _id: { uid: '$uid' } }, { _id: {}, total_users: { $sum: 1 } }] }) if (this.debug) { console.log('userRes', JSON.stringify(userRes)) } let userCount = 0 if (userRes.data.length > 0) { userCount = userRes.data[0].total_users } const pageKey = 's_' + (Array.isArray(durationMark[di]) ? durationMark[di][0] : durationMark[di]) durationData.visit_devices[pageKey] = deviceCount durationData.visit_users[pageKey] = userCount durationData.visit_times[pageKey] = vistCount } // 数据填充 const datetime = new DateTime() const insertParams = { appid: data._id.appid, platform_id: platformInfo._id, channel_id: channelInfo._id, version_id: versionInfo._id, visit_depth_data: visitDepthData, duration_data: durationData, stat_date: datetime.getDate('Ymd', this.startTime), start_time: this.startTime, end_time: this.endTime } this.fillData.push(insertParams) return insertParams } }