|
|
/**
|
|
|
* @class StatResult 基础数据结果统计模型
|
|
|
*/
|
|
|
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 ErrorLog = require('./errorLog')
|
|
|
const ActiveDevices = require('./activeDevices')
|
|
|
const ActiveUsers = require('./activeUsers')
|
|
|
const UniIDUsers = require('./uniIDUsers')
|
|
|
const {
|
|
|
DateTime
|
|
|
} = require('../lib')
|
|
|
module.exports = class StatResult extends BaseMod {
|
|
|
constructor() {
|
|
|
super()
|
|
|
this.tableName = '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 = ['hour', 'day', 'week', 'month']
|
|
|
if (!allowedType.includes(type)) {
|
|
|
return {
|
|
|
code: 1002,
|
|
|
msg: 'This type is not allowed'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('result --type:' + type + ', date:' + date + ', reset:' + reset)
|
|
|
}
|
|
|
|
|
|
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('dimension time', this.startTime + '--' + this.endTime)
|
|
|
}
|
|
|
|
|
|
// 查看当前时间段日志是否已存在,防止重复生成
|
|
|
if (!reset) {
|
|
|
const checkRes = await this.getCollection(this.tableName).where({
|
|
|
dimension: this.fillType,
|
|
|
start_time: this.startTime,
|
|
|
end_time: this.endTime
|
|
|
}).get()
|
|
|
if (checkRes.data.length > 0) {
|
|
|
console.log('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))
|
|
|
}
|
|
|
|
|
|
// 周月数据单独统计
|
|
|
if (['week', 'month'].includes(this.fillType)) {
|
|
|
return await this.statWeekOrMonth()
|
|
|
}
|
|
|
|
|
|
// 数据获取
|
|
|
this.sessionLog = new SessionLog()
|
|
|
const statRes = await this.aggregate(this.sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
is_first_visit: 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'
|
|
|
},
|
|
|
new_device_count: {
|
|
|
$sum: '$is_first_visit'
|
|
|
},
|
|
|
page_view_count: {
|
|
|
$sum: '$page_count'
|
|
|
},
|
|
|
total_duration: {
|
|
|
$sum: '$duration'
|
|
|
},
|
|
|
session_times: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
new_device_count: 1,
|
|
|
page_view_count: 1,
|
|
|
session_times: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
let res = {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
if (this.debug) {
|
|
|
console.log('statRes', JSON.stringify(statRes))
|
|
|
}
|
|
|
|
|
|
this.fillData = []
|
|
|
this.composes = []
|
|
|
if (statRes.data.length > 0) {
|
|
|
for (const i in statRes.data) {
|
|
|
await this.fill(statRes.data[i])
|
|
|
}
|
|
|
}
|
|
|
//补充数据
|
|
|
await this.replenishStat()
|
|
|
if (this.fillData.length > 0) {
|
|
|
res = await this.batchInsert(this.tableName, this.fillData)
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 按周/月统计
|
|
|
*/
|
|
|
async statWeekOrMonth() {
|
|
|
const statRes = await this.aggregate(this.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version_id: 1,
|
|
|
platform_id: 1,
|
|
|
channel_id: 1,
|
|
|
new_device_count: 1,
|
|
|
new_user_count: 1,
|
|
|
page_visit_count: 1,
|
|
|
user_visit_times: 1,
|
|
|
app_launch_count: 1,
|
|
|
error_count: 1,
|
|
|
bounce_times: 1,
|
|
|
duration: 1,
|
|
|
user_duration: 1,
|
|
|
dimension: 1,
|
|
|
start_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
dimension: 'day',
|
|
|
start_time: {
|
|
|
$gte: this.startTime,
|
|
|
$lte: this.endTime
|
|
|
}
|
|
|
},
|
|
|
group: {
|
|
|
_id: {
|
|
|
appid: '$appid',
|
|
|
version_id: '$version_id',
|
|
|
platform_id: '$platform_id',
|
|
|
channel_id: '$channel_id'
|
|
|
},
|
|
|
new_device_count: {
|
|
|
$sum: '$new_device_count'
|
|
|
},
|
|
|
new_user_count: {
|
|
|
$sum: '$new_user_count'
|
|
|
},
|
|
|
error_count: {
|
|
|
$sum: '$error_count'
|
|
|
},
|
|
|
page_count: {
|
|
|
$sum: '$page_visit_count'
|
|
|
},
|
|
|
total_duration: {
|
|
|
$sum: '$duration'
|
|
|
},
|
|
|
total_user_duration: {
|
|
|
$sum: '$user_duration'
|
|
|
},
|
|
|
total_user_session_times: {
|
|
|
$sum: '$user_session_times'
|
|
|
},
|
|
|
session_times: {
|
|
|
$sum: '$app_launch_count'
|
|
|
},
|
|
|
total_bounce_times: {
|
|
|
$sum: '$bounce_times'
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
new_device_count: 1,
|
|
|
error_count: 1,
|
|
|
page_count: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
let res = {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
if (this.debug) {
|
|
|
console.log('statRes', JSON.stringify(statRes))
|
|
|
}
|
|
|
|
|
|
this.activeDevices = new ActiveDevices()
|
|
|
this.activeUsers = new ActiveUsers()
|
|
|
this.fillData = []
|
|
|
this.composes = []
|
|
|
if (statRes.data.length > 0) {
|
|
|
for (const i in statRes.data) {
|
|
|
await this.getWeekOrMonthData(statRes.data[i])
|
|
|
}
|
|
|
}
|
|
|
//补充数据
|
|
|
await this.replenishStat()
|
|
|
if (this.fillData.length > 0) {
|
|
|
res = await this.batchInsert(this.tableName, this.fillData)
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取周/月维度的填充数据
|
|
|
* @param {Object} data 统计数据
|
|
|
*/
|
|
|
async getWeekOrMonthData(data) {
|
|
|
|
|
|
const matchCondition = {
|
|
|
...data._id,
|
|
|
create_time: {
|
|
|
$gte: this.startTime,
|
|
|
$lte: this.endTime
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 查询活跃设备数
|
|
|
const statVisitDeviceRes = await this.getCollection(this.activeDevices.tableName).where({
|
|
|
...matchCondition,
|
|
|
dimension: this.fillType
|
|
|
}).count()
|
|
|
|
|
|
let activeDeviceCount = 0
|
|
|
if (statVisitDeviceRes && statVisitDeviceRes.total > 0) {
|
|
|
activeDeviceCount = statVisitDeviceRes.total
|
|
|
}
|
|
|
|
|
|
// 设备次均停留时长
|
|
|
let avgSessionTime = 0
|
|
|
if (data.total_duration > 0 && data.session_times > 0) {
|
|
|
avgSessionTime = Math.round(data.total_duration / data.session_times)
|
|
|
}
|
|
|
|
|
|
// 设均停留时长
|
|
|
let avgDeviceTime = 0
|
|
|
if (data.total_duration > 0 && activeDeviceCount > 0) {
|
|
|
avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
|
|
|
}
|
|
|
|
|
|
// 跳出率
|
|
|
let bounceRate = 0
|
|
|
if (data.total_bounce_times > 0 && data.session_times > 0) {
|
|
|
bounceRate = data.total_bounce_times * 100 / data.session_times
|
|
|
bounceRate = parseFloat(bounceRate.toFixed(2))
|
|
|
}
|
|
|
|
|
|
// 累计设备数
|
|
|
let totalDevices = data.new_device_count
|
|
|
const totalDeviceRes = await this.getCollection(this.tableName).where({
|
|
|
...matchCondition,
|
|
|
dimension: this.fillType,
|
|
|
start_time: {
|
|
|
$lt: this.startTime
|
|
|
}
|
|
|
}).orderBy('start_time', 'desc').limit(1).get()
|
|
|
if (totalDeviceRes && totalDeviceRes.data.length > 0) {
|
|
|
totalDevices += totalDeviceRes.data[0].total_devices
|
|
|
}
|
|
|
|
|
|
//活跃用户数
|
|
|
const statVisitUserRes = await this.getCollection(this.activeUsers.tableName).where({
|
|
|
...matchCondition,
|
|
|
dimension: this.fillType
|
|
|
}).count()
|
|
|
let activeUserCount = 0
|
|
|
if (statVisitUserRes && statVisitUserRes.total > 0) {
|
|
|
activeUserCount = statVisitUserRes.total
|
|
|
}
|
|
|
|
|
|
// 平台信息
|
|
|
let platformInfo = null
|
|
|
if (this.platforms && this.platforms[data._id.platform_id]) {
|
|
|
platformInfo = this.platforms[data._id.platform_id]
|
|
|
} else {
|
|
|
const platform = new Platform()
|
|
|
platformInfo = await this.getById(platform.tableName, data._id.platform_id)
|
|
|
if (!platformInfo || platformInfo.length === 0) {
|
|
|
platformInfo.code = ''
|
|
|
}
|
|
|
this.platforms[data._id.platform_id] = platformInfo
|
|
|
}
|
|
|
|
|
|
// 渠道信息
|
|
|
let channelInfo = null
|
|
|
if (this.channels && this.channels[data._id.channel_id]) {
|
|
|
channelInfo = this.channels[data._id.channel_id]
|
|
|
} else {
|
|
|
const channel = new Channel()
|
|
|
channelInfo = await this.getById(channel.tableName, data._id.channel_id)
|
|
|
if (!channelInfo || channelInfo.length === 0) {
|
|
|
channelInfo.channel_code = ''
|
|
|
}
|
|
|
this.channels[data._id.channel_id] = channelInfo
|
|
|
}
|
|
|
|
|
|
// 版本信息
|
|
|
let versionInfo = null
|
|
|
if (this.versions && this.versions[data._id.version_id]) {
|
|
|
versionInfo = this.versions[data._id.version_id]
|
|
|
} else {
|
|
|
const version = new Version()
|
|
|
versionInfo = await this.getById(version.tableName, data._id.version_id, false)
|
|
|
if (!versionInfo || versionInfo.length === 0) {
|
|
|
versionInfo.version = ''
|
|
|
}
|
|
|
this.versions[data._id.version_id] = versionInfo
|
|
|
}
|
|
|
|
|
|
//总用户数
|
|
|
const uniIDUsers = new UniIDUsers()
|
|
|
let totalUserCount = await uniIDUsers.getUserCount(data._id.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
|
|
|
$lte: this.endTime
|
|
|
})
|
|
|
|
|
|
//人均停留时长
|
|
|
let avgUserTime = 0
|
|
|
if (data.total_user_duration > 0 && activeUserCount > 0) {
|
|
|
avgUserTime = Math.round(data.total_user_duration / activeUserCount)
|
|
|
}
|
|
|
|
|
|
//用户次均访问时长
|
|
|
let avgUserSessionTime = 0
|
|
|
if (data.total_user_duration > 0 && data.total_user_session_times > 0) {
|
|
|
avgUserSessionTime = Math.round(data.total_user_duration / data.total_user_session_times)
|
|
|
}
|
|
|
|
|
|
//安卓平台活跃设备数需要减去sdk更新后device_id发生变更的设备数
|
|
|
if(platformInfo.code === 'android') {
|
|
|
try{
|
|
|
const statVisitOldDeviceRes = await this.getCollection(this.activeDevices.tableName).where({
|
|
|
...data._id,
|
|
|
create_time: {
|
|
|
$gte: this.startTime,
|
|
|
$lte: this.endTime
|
|
|
},
|
|
|
dimension: this.fillType + '-old'
|
|
|
}).count()
|
|
|
if (statVisitOldDeviceRes && statVisitOldDeviceRes.total > 0) {
|
|
|
// 活跃设备留存数
|
|
|
activeDeviceCount -= statVisitOldDeviceRes.total
|
|
|
//平均设备停留时长
|
|
|
avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.log('server error: ' + e)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const insertParam = {
|
|
|
appid: data._id.appid,
|
|
|
platform_id: data._id.platform_id,
|
|
|
channel_id: data._id.channel_id,
|
|
|
version_id: data._id.version_id,
|
|
|
//总设备数
|
|
|
total_devices: totalDevices,
|
|
|
//本时间段新增设备数
|
|
|
new_device_count: data.new_device_count,
|
|
|
//登录用户会话次数
|
|
|
user_session_times: data.total_user_session_times,
|
|
|
//活跃设备数
|
|
|
active_device_count: activeDeviceCount,
|
|
|
//总用户数
|
|
|
total_users: totalUserCount,
|
|
|
//新增用户数
|
|
|
new_user_count: data.new_user_count,
|
|
|
//活跃用户数
|
|
|
active_user_count: activeUserCount,
|
|
|
//应用启动次数 = 设备会话次数
|
|
|
app_launch_count: data.session_times,
|
|
|
//页面访问次数
|
|
|
page_visit_count: data.page_view_count,
|
|
|
//错误次数
|
|
|
error_count: data.error_count,
|
|
|
//会话总访问时长
|
|
|
duration: data.total_duration,
|
|
|
//用户会话总访问时长
|
|
|
user_duration: data.total_user_duration,
|
|
|
avg_device_session_time: avgSessionTime,
|
|
|
avg_user_session_time: avgUserSessionTime,
|
|
|
avg_device_time: avgDeviceTime,
|
|
|
avg_user_time: avgUserTime,
|
|
|
bounce_times: data.total_bounce_times,
|
|
|
bounce_rate: bounceRate,
|
|
|
retention: {},
|
|
|
dimension: this.fillType,
|
|
|
stat_date: new DateTime().getDate('Ymd', this.startTime),
|
|
|
start_time: this.startTime,
|
|
|
end_time: this.endTime
|
|
|
}
|
|
|
|
|
|
this.fillData.push(insertParam)
|
|
|
this.composes.push(data._id.appid + '_' + data._id.platform_id + '_' + data._id.channel_id + '_' + data
|
|
|
._id.version_id)
|
|
|
|
|
|
return insertParam
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 基础填充数据-目前只有小时和天维度的数据
|
|
|
* @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 matchCondition = data._id
|
|
|
Object.assign(matchCondition, {
|
|
|
create_time: {
|
|
|
$gte: this.startTime,
|
|
|
$lte: this.endTime
|
|
|
}
|
|
|
})
|
|
|
const statVisitDeviceRes = await this.aggregate(this.sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: matchCondition,
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
let activeDeviceCount = 0
|
|
|
if (statVisitDeviceRes.data.length > 0) {
|
|
|
activeDeviceCount = statVisitDeviceRes.data[0].total_devices
|
|
|
}
|
|
|
|
|
|
//安卓平台活跃设备数需要减去sdk更新后device_id发生变更的设备数
|
|
|
if(platformInfo.code === 'android') {
|
|
|
const oldDeviceRes = await this.aggregate(this.sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
old_device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: matchCondition,
|
|
|
group: {
|
|
|
_id: {
|
|
|
device_id: '$old_device_id'
|
|
|
},
|
|
|
create_time: {
|
|
|
$min: '$create_time'
|
|
|
},
|
|
|
sessionCount: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
create_time: 1,
|
|
|
sessionCount: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
if(oldDeviceRes.data.length) {
|
|
|
const thisOldDeviceIds = []
|
|
|
for (const tau in oldDeviceRes.data) {
|
|
|
if(oldDeviceRes.data[tau]._id.device_id) {
|
|
|
thisOldDeviceIds.push(oldDeviceRes.data[tau]._id.device_id)
|
|
|
}
|
|
|
}
|
|
|
const statVisitOldDeviceRes = await this.aggregate(this.sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
...matchCondition,
|
|
|
device_id: {
|
|
|
$in: thisOldDeviceIds
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('statVisitOldDeviceRes', JSON.stringify(statVisitOldDeviceRes))
|
|
|
}
|
|
|
if (statVisitOldDeviceRes && statVisitOldDeviceRes.data.length > 0) {
|
|
|
// 活跃设备留存数
|
|
|
activeDeviceCount -= statVisitOldDeviceRes.data[0].total_devices
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 错误数量统计
|
|
|
const errorLog = new ErrorLog()
|
|
|
const statErrorRes = await this.getCollection(errorLog.tableName).where(matchCondition).count()
|
|
|
let errorCount = 0
|
|
|
if (statErrorRes && statErrorRes.total > 0) {
|
|
|
errorCount = statErrorRes.total
|
|
|
}
|
|
|
|
|
|
// 设备的次均停留时长,设备访问总时长/设备会话次数
|
|
|
let avgSessionTime = 0
|
|
|
if (data.total_duration > 0 && data.session_times > 0) {
|
|
|
avgSessionTime = Math.round(data.total_duration / data.session_times)
|
|
|
}
|
|
|
|
|
|
// 设均停留时长
|
|
|
let avgDeviceTime = 0
|
|
|
if (data.total_duration > 0 && activeDeviceCount > 0) {
|
|
|
avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
|
|
|
}
|
|
|
|
|
|
// 跳出率
|
|
|
let bounceTimes = 0
|
|
|
const bounceRes = await this.getCollection(this.sessionLog.tableName).where({
|
|
|
...matchCondition,
|
|
|
page_count: 1
|
|
|
}).count()
|
|
|
if (bounceRes && bounceRes.total > 0) {
|
|
|
bounceTimes = bounceRes.total
|
|
|
}
|
|
|
let bounceRate = 0
|
|
|
if (bounceTimes > 0 && data.session_times > 0) {
|
|
|
bounceRate = bounceTimes * 100 / data.session_times
|
|
|
bounceRate = parseFloat(bounceRate.toFixed(2))
|
|
|
}
|
|
|
|
|
|
// 应用启动次数 = 会话次数
|
|
|
const launchCount = data.session_times
|
|
|
|
|
|
// 累计设备数
|
|
|
let totalDevices = data.new_device_count
|
|
|
const totalDeviceRes = await this.getCollection(this.tableName).where({
|
|
|
appid: data._id.appid,
|
|
|
platform_id: platformInfo._id,
|
|
|
channel_id: channelInfo._id,
|
|
|
version_id: versionInfo._id,
|
|
|
dimension: this.fillType,
|
|
|
start_time: {
|
|
|
$lt: this.startTime
|
|
|
}
|
|
|
}).orderBy('start_time', 'desc').limit(1).get()
|
|
|
|
|
|
if (totalDeviceRes && totalDeviceRes.data.length > 0) {
|
|
|
totalDevices += totalDeviceRes.data[0].total_devices
|
|
|
}
|
|
|
|
|
|
//活跃用户数
|
|
|
const userSessionLog = new UserSessionLog()
|
|
|
let activeUserCount = 0
|
|
|
const activeUserCountRes = await this.aggregate(userSessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
uid: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: matchCondition,
|
|
|
group: [{
|
|
|
_id: {
|
|
|
uid: '$uid'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_users: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (activeUserCountRes && activeUserCountRes.data.length > 0) {
|
|
|
activeUserCount = activeUserCountRes.data[0].total_users
|
|
|
}
|
|
|
|
|
|
//新增用户数
|
|
|
const uniIDUsers = new UniIDUsers()
|
|
|
let newUserCount = await uniIDUsers.getUserCount(matchCondition.appid, matchCondition.platform,
|
|
|
matchCondition.channel, matchCondition.version, {
|
|
|
$gte: this.startTime,
|
|
|
$lte: this.endTime
|
|
|
})
|
|
|
|
|
|
//总用户数
|
|
|
let totalUserCount = await uniIDUsers.getUserCount(matchCondition.appid, matchCondition.platform,
|
|
|
matchCondition.channel, matchCondition.version, {
|
|
|
$lte: this.endTime
|
|
|
})
|
|
|
|
|
|
|
|
|
//用户停留总时长及总会话次数
|
|
|
let totalUserDuration = 0
|
|
|
let totalUserSessionTimes = 0
|
|
|
const totalUserDurationRes = await this.aggregate(userSessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
duration: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: matchCondition,
|
|
|
group: [{
|
|
|
_id: {},
|
|
|
total_duration: {
|
|
|
$sum: "$duration"
|
|
|
},
|
|
|
total_session_times: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
if (totalUserDurationRes && totalUserDurationRes.data.length > 0) {
|
|
|
totalUserDuration = totalUserDurationRes.data[0].total_duration
|
|
|
totalUserSessionTimes = totalUserDurationRes.data[0].total_session_times
|
|
|
}
|
|
|
|
|
|
//人均停留时长
|
|
|
let avgUserTime = 0
|
|
|
//用户次均访问时长
|
|
|
let avgUserSessionTime = 0
|
|
|
if (totalUserDuration > 0 && activeUserCount > 0) {
|
|
|
avgUserTime = Math.round(totalUserDuration / activeUserCount)
|
|
|
avgUserSessionTime = Math.round(totalUserDuration / totalUserSessionTimes)
|
|
|
}
|
|
|
|
|
|
//设置填充数据
|
|
|
const datetime = new DateTime()
|
|
|
const insertParam = {
|
|
|
appid: data._id.appid,
|
|
|
platform_id: platformInfo._id,
|
|
|
channel_id: channelInfo._id,
|
|
|
version_id: versionInfo._id,
|
|
|
total_devices: totalDevices,
|
|
|
new_device_count: data.new_device_count,
|
|
|
active_device_count: activeDeviceCount,
|
|
|
total_users: totalUserCount,
|
|
|
new_user_count: newUserCount,
|
|
|
active_user_count: activeUserCount,
|
|
|
user_session_times: totalUserSessionTimes,
|
|
|
app_launch_count: launchCount,
|
|
|
page_visit_count: data.page_view_count,
|
|
|
error_count: errorCount,
|
|
|
duration: data.total_duration,
|
|
|
user_duration: totalUserDuration,
|
|
|
avg_device_session_time: avgSessionTime,
|
|
|
avg_user_session_time: avgUserSessionTime,
|
|
|
avg_device_time: avgDeviceTime,
|
|
|
avg_user_time: avgUserTime,
|
|
|
bounce_times: bounceTimes,
|
|
|
bounce_rate: bounceRate,
|
|
|
retention: {},
|
|
|
dimension: this.fillType,
|
|
|
stat_date: datetime.getDate('Ymd', this.startTime),
|
|
|
start_time: this.startTime,
|
|
|
end_time: this.endTime
|
|
|
}
|
|
|
|
|
|
//数据填充
|
|
|
this.fillData.push(insertParam)
|
|
|
this.composes.push(data._id.appid + '_' + platformInfo._id + '_' + channelInfo._id + '_' + versionInfo
|
|
|
._id)
|
|
|
return insertParam
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 基础统计数据补充,防止累计数据丢失
|
|
|
*/
|
|
|
async replenishStat() {
|
|
|
if (this.debug) {
|
|
|
console.log('composes data', this.composes)
|
|
|
}
|
|
|
|
|
|
const datetime = new DateTime()
|
|
|
// const {
|
|
|
// startTime
|
|
|
// } = datetime.getTimeDimensionByType(this.fillType, -1, this.startTime)
|
|
|
|
|
|
//上一次统计时间
|
|
|
let preStatRes = await this.getCollection(this.tableName).where({
|
|
|
start_time: {$lt: this.startTime},
|
|
|
dimension: this.fillType
|
|
|
}).orderBy('start_time', 'desc').limit(1).get()
|
|
|
let preStartTime = 0
|
|
|
if(preStatRes && preStatRes.data.length > 0) {
|
|
|
preStartTime = preStatRes.data[0].start_time
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('replenishStat-preStartTime', preStartTime)
|
|
|
}
|
|
|
|
|
|
if(!preStartTime) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
// 上一阶段数据
|
|
|
const preStatData = await this.selectAll(this.tableName, {
|
|
|
start_time: preStartTime,
|
|
|
dimension: this.fillType
|
|
|
}, {
|
|
|
appid: 1,
|
|
|
platform_id: 1,
|
|
|
channel_id: 1,
|
|
|
version_id: 1,
|
|
|
total_devices: 1,
|
|
|
total_users: 1
|
|
|
})
|
|
|
|
|
|
if (!preStatData || preStatData.data.length === 0) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('preStatData', JSON.stringify(preStatData))
|
|
|
}
|
|
|
|
|
|
let preKey
|
|
|
const preKeyArr = []
|
|
|
for (const pi in preStatData.data) {
|
|
|
preKey = preStatData.data[pi].appid + '_' + preStatData.data[pi].platform_id + '_' + preStatData
|
|
|
.data[pi].channel_id + '_' + preStatData.data[pi].version_id
|
|
|
if (!this.composes.includes(preKey) && !preKeyArr.includes(preKey)) {
|
|
|
preKeyArr.push(preKey)
|
|
|
if (this.debug) {
|
|
|
console.log('preKey -add', preKey)
|
|
|
}
|
|
|
this.fillData.push({
|
|
|
appid: preStatData.data[pi].appid,
|
|
|
platform_id: preStatData.data[pi].platform_id,
|
|
|
channel_id: preStatData.data[pi].channel_id,
|
|
|
version_id: preStatData.data[pi].version_id,
|
|
|
total_devices: preStatData.data[pi].total_devices,
|
|
|
new_device_count: 0,
|
|
|
active_device_count: 0,
|
|
|
total_users: preStatData.data[pi].total_users,
|
|
|
new_user_count: 0,
|
|
|
active_user_count: 0,
|
|
|
user_session_times: 0,
|
|
|
app_launch_count: 0,
|
|
|
page_visit_count: 0,
|
|
|
error_count: 0,
|
|
|
duration: 0,
|
|
|
user_duration: 0,
|
|
|
avg_device_session_time: 0,
|
|
|
avg_user_session_time: 0,
|
|
|
avg_device_time: 0,
|
|
|
avg_user_time: 0,
|
|
|
bounce_times: 0,
|
|
|
bounce_rate: 0,
|
|
|
retention: {},
|
|
|
dimension: this.fillType,
|
|
|
stat_date: datetime.getDate('Ymd', this.startTime),
|
|
|
start_time: this.startTime,
|
|
|
end_time: this.endTime
|
|
|
})
|
|
|
} else if (this.debug) {
|
|
|
console.log('preKey -have', preKey)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 留存数据统计
|
|
|
* @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
|
|
|
* @param {Date|Time} date 指定日期或时间戳
|
|
|
* @param {String} mod 统计模块 device:设备,user:用户
|
|
|
*/
|
|
|
async retentionStat(type, date, mod = 'device') {
|
|
|
date = date ? date : new DateTime().getTimeBySetDays(-1, date)
|
|
|
const allowedType = ['day', 'week', 'month']
|
|
|
if (!allowedType.includes(type)) {
|
|
|
return {
|
|
|
code: 1002,
|
|
|
msg: 'This type is not allowed'
|
|
|
}
|
|
|
}
|
|
|
let days = []
|
|
|
switch (type) {
|
|
|
case 'week':
|
|
|
case 'month':
|
|
|
days = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
|
break
|
|
|
default:
|
|
|
days = [1, 2, 3, 4, 5, 6, 7, 14, 30]
|
|
|
break
|
|
|
}
|
|
|
|
|
|
let res = {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
|
|
|
for (const day in days) {
|
|
|
//留存统计数据库查询较为复杂,因此这里拆分为多个维度
|
|
|
if (mod && mod === 'user') {
|
|
|
//用户留存统计
|
|
|
if (type === 'day') {
|
|
|
res = await this.userRetentionFillDayly(type, days[day], date)
|
|
|
} else {
|
|
|
res = await this.userRetentionFillWeekOrMonth(type, days[day], date)
|
|
|
}
|
|
|
} else {
|
|
|
//设备留存统计
|
|
|
if (type === 'day') {
|
|
|
res = await this.deviceRetentionFillDayly(type, days[day], date)
|
|
|
} else {
|
|
|
res = await this.deviceRetentionFillWeekOrMonth(type, days[day], date)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设备日留存统计数据填充
|
|
|
* @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
|
|
|
* @param {Date|Time} date 指定日期或时间戳
|
|
|
* @param {Boolean} reset 是否重置,为ture时会重置该批次数据
|
|
|
*/
|
|
|
async deviceRetentionFillDayly(type, day, date) {
|
|
|
if (type !== 'day') {
|
|
|
return {
|
|
|
code: 301,
|
|
|
msg: 'Type error:' + type
|
|
|
}
|
|
|
}
|
|
|
const dateTime = new DateTime()
|
|
|
const {
|
|
|
startTime,
|
|
|
endTime
|
|
|
} = dateTime.getTimeDimensionByType(type, 0 - day, date)
|
|
|
|
|
|
if (!startTime || !endTime) {
|
|
|
return {
|
|
|
code: 1001,
|
|
|
msg: 'The statistic time get failed'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 截止时间范围
|
|
|
const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)
|
|
|
|
|
|
// 获取当时批次的统计日志
|
|
|
const resultLogRes = await this.selectAll(this.tableName, {
|
|
|
dimension: type,
|
|
|
start_time: startTime,
|
|
|
end_time: endTime
|
|
|
})
|
|
|
// const resultLogRes = await this.getCollection(this.tableName).where({
|
|
|
// dimension: type,
|
|
|
// start_time: startTime,
|
|
|
// end_time: endTime
|
|
|
// }).get()
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('resultLogRes', JSON.stringify(resultLogRes))
|
|
|
}
|
|
|
|
|
|
if (!resultLogRes || resultLogRes.data.length === 0) {
|
|
|
if (this.debug) {
|
|
|
console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' +
|
|
|
endTime)
|
|
|
}
|
|
|
return {
|
|
|
code: 1000,
|
|
|
msg: 'Not found this log'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const sessionLog = new SessionLog()
|
|
|
const platform = new Platform()
|
|
|
const channel = new Channel()
|
|
|
const version = new Version()
|
|
|
let res = null
|
|
|
for (const resultIndex in resultLogRes.data) {
|
|
|
const resultLog = resultLogRes.data[resultIndex]
|
|
|
// 平台信息
|
|
|
let platformInfo = null
|
|
|
if (this.platforms && this.platforms[resultLog.platform_id]) {
|
|
|
platformInfo = this.platforms[resultLog.platform_id]
|
|
|
} else {
|
|
|
platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
|
|
|
if (!platformInfo || platformInfo.length === 0) {
|
|
|
platformInfo.code = ''
|
|
|
}
|
|
|
this.platforms[resultLog.platform_id] = platformInfo
|
|
|
if (this.debug) {
|
|
|
console.log('platformInfo', JSON.stringify(platformInfo))
|
|
|
}
|
|
|
}
|
|
|
// 渠道信息
|
|
|
let channelInfo = null
|
|
|
if (this.channels && this.channels[resultLog.channel_id]) {
|
|
|
channelInfo = this.channels[resultLog.channel_id]
|
|
|
} else {
|
|
|
channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
|
|
|
if (!channelInfo || channelInfo.length === 0) {
|
|
|
channelInfo.channel_code = ''
|
|
|
}
|
|
|
this.channels[resultLog.channel_id] = channelInfo
|
|
|
if (this.debug) {
|
|
|
console.log('channelInfo', JSON.stringify(channelInfo))
|
|
|
}
|
|
|
}
|
|
|
// 版本信息
|
|
|
let versionInfo = null
|
|
|
if (this.versions && this.versions[resultLog.version_id]) {
|
|
|
versionInfo = this.versions[resultLog.version_id]
|
|
|
} else {
|
|
|
versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
|
|
|
if (!versionInfo || versionInfo.length === 0) {
|
|
|
versionInfo.version = ''
|
|
|
}
|
|
|
this.versions[resultLog.version_id] = versionInfo
|
|
|
if (this.debug) {
|
|
|
console.log('versionInfo', JSON.stringify(versionInfo))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取该批次的活跃设备数
|
|
|
const activeDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
},
|
|
|
group: {
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
},
|
|
|
create_time: {
|
|
|
$min: '$create_time'
|
|
|
},
|
|
|
sessionCount: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
create_time: 1,
|
|
|
sessionCount: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('activeDeviceRes', JSON.stringify(activeDeviceRes))
|
|
|
}
|
|
|
let activeDeviceRate = 0
|
|
|
let activeDevices = 0
|
|
|
if (activeDeviceRes && activeDeviceRes.data.length > 0) {
|
|
|
const thisDayActiveDevices = activeDeviceRes.data.length
|
|
|
const thisDayActiveDeviceIds = []
|
|
|
for (const tau in activeDeviceRes.data) {
|
|
|
thisDayActiveDeviceIds.push(activeDeviceRes.data[tau]._id.device_id)
|
|
|
}
|
|
|
if (this.debug) {
|
|
|
console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
|
|
|
}
|
|
|
|
|
|
// 留存活跃设备查询
|
|
|
const retentionActiveDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
device_id: {
|
|
|
$in: thisDayActiveDeviceIds
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionActiveDeviceRes', JSON.stringify(retentionActiveDeviceRes))
|
|
|
}
|
|
|
if (retentionActiveDeviceRes && retentionActiveDeviceRes.data.length > 0) {
|
|
|
// 活跃设备留存数
|
|
|
activeDevices = retentionActiveDeviceRes.data[0].total_devices
|
|
|
// 活跃设备留存率
|
|
|
activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
|
|
|
}
|
|
|
|
|
|
//安卓平台留存需要增加sdk更新后device_id发生变更的设备数
|
|
|
if(platformInfo.code === 'android') {
|
|
|
const retentionActiveOldDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
old_device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
old_device_id: {
|
|
|
$in: thisDayActiveDeviceIds
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$old_device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionActiveOldDeviceRes', JSON.stringify(retentionActiveOldDeviceRes))
|
|
|
}
|
|
|
if (retentionActiveOldDeviceRes && retentionActiveOldDeviceRes.data.length > 0) {
|
|
|
// 活跃设备留存数
|
|
|
activeDevices += retentionActiveOldDeviceRes.data[0].total_devices
|
|
|
// 活跃设备留存率
|
|
|
activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取该批次新增设备数
|
|
|
const newDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
is_first_visit: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
is_first_visit: 1,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
},
|
|
|
group: {
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
},
|
|
|
create_time: {
|
|
|
$min: '$create_time'
|
|
|
},
|
|
|
sessionCount: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
create_time: 1,
|
|
|
sessionCount: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
let newDeviceRate = 0
|
|
|
let newDevices = 0
|
|
|
if (newDeviceRes && newDeviceRes.data.length > 0) {
|
|
|
const thisDayNewDevices = newDeviceRes.data.length
|
|
|
const thisDayNewDeviceIds = []
|
|
|
for (const tau in newDeviceRes.data) {
|
|
|
thisDayNewDeviceIds.push(newDeviceRes.data[tau]._id.device_id)
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('thisDayNewDeviceIds', JSON.stringify(thisDayNewDeviceIds))
|
|
|
}
|
|
|
|
|
|
// 留存的设备查询
|
|
|
const retentionNewDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
device_id: {
|
|
|
$in: thisDayNewDeviceIds
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (retentionNewDeviceRes && retentionNewDeviceRes.data.length > 0) {
|
|
|
// 新增设备留存数
|
|
|
newDevices = retentionNewDeviceRes.data[0].total_devices
|
|
|
// 新增设备留存率
|
|
|
newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
|
|
|
}
|
|
|
|
|
|
//安卓平台留存需要增加sdk更新后device_id发生变更的设备数
|
|
|
if(platformInfo.code === 'android') {
|
|
|
const retentionNewOldDeviceRes = await this.aggregate(sessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
old_device_id: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
old_device_id: {
|
|
|
$in: thisDayNewDeviceIds
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
device_id: '$old_device_id'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_devices: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionNewOldDeviceRes', JSON.stringify(retentionNewOldDeviceRes))
|
|
|
}
|
|
|
|
|
|
if (retentionNewOldDeviceRes && retentionNewOldDeviceRes.data.length > 0) {
|
|
|
// 新增设备留存数
|
|
|
newDevices += retentionNewOldDeviceRes.data[0].total_devices
|
|
|
// 新增设备留存率
|
|
|
newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 数据更新
|
|
|
const retentionData = resultLog.retention
|
|
|
const dataKey = type.substr(0, 1) + '_' + day
|
|
|
if (!retentionData.active_device) {
|
|
|
retentionData.active_device = {}
|
|
|
}
|
|
|
retentionData.active_device[dataKey] = {
|
|
|
device_count: activeDevices,
|
|
|
device_rate: activeDeviceRate
|
|
|
}
|
|
|
if (!retentionData.new_device) {
|
|
|
retentionData.new_device = {}
|
|
|
}
|
|
|
retentionData.new_device[dataKey] = {
|
|
|
device_count: newDevices,
|
|
|
device_rate: newDeviceRate
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionData', JSON.stringify(retentionData))
|
|
|
}
|
|
|
|
|
|
res = await this.update(this.tableName, {
|
|
|
retention: retentionData
|
|
|
}, {
|
|
|
_id: resultLog._id
|
|
|
})
|
|
|
}
|
|
|
|
|
|
if (res && res.updated) {
|
|
|
return {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
} else {
|
|
|
return {
|
|
|
code: 500,
|
|
|
msg: 'retention data update failed'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设备周/月留存数据填充
|
|
|
* @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
|
|
|
* @param {Date|Time} date 指定日期或时间戳
|
|
|
* @param {Boolean} reset 是否重置,为ture时会重置该批次数据
|
|
|
*/
|
|
|
async deviceRetentionFillWeekOrMonth(type, day, date) {
|
|
|
if (!['week', 'month'].includes(type)) {
|
|
|
return {
|
|
|
code: 301,
|
|
|
msg: 'Type error:' + type
|
|
|
}
|
|
|
}
|
|
|
const dateTime = new DateTime()
|
|
|
const {
|
|
|
startTime,
|
|
|
endTime
|
|
|
} = dateTime.getTimeDimensionByType(type, 0 - day, date)
|
|
|
|
|
|
if (!startTime || !endTime) {
|
|
|
return {
|
|
|
code: 1001,
|
|
|
msg: 'The statistic time get failed'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 截止时间范围
|
|
|
const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)
|
|
|
|
|
|
// 获取当时批次的统计日志
|
|
|
const resultLogRes = await this.selectAll(this.tableName, {
|
|
|
dimension: type,
|
|
|
start_time: startTime,
|
|
|
end_time: endTime
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('resultLogRes', JSON.stringify(resultLogRes))
|
|
|
}
|
|
|
|
|
|
if (!resultLogRes || resultLogRes.data.length === 0) {
|
|
|
if (this.debug) {
|
|
|
console.log('Not found this session log --' + type + ':' + day + ', start:' + startTime +
|
|
|
',endTime:' + endTime)
|
|
|
}
|
|
|
return {
|
|
|
code: 1000,
|
|
|
msg: 'Not found this session log'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const activeDevicesObj = new ActiveDevices()
|
|
|
let res = null;
|
|
|
let activeDeviceRes;
|
|
|
let activeDeviceRate;
|
|
|
let activeDevices;
|
|
|
let newDeviceRate;
|
|
|
let newDevices
|
|
|
for (const resultIndex in resultLogRes.data) {
|
|
|
const resultLog = resultLogRes.data[resultIndex]
|
|
|
// 获取该批次的活跃设备数
|
|
|
activeDeviceRes = await this.selectAll(activeDevicesObj.tableName, {
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
}, {
|
|
|
device_id: 1
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('activeDeviceRes', JSON.stringify(activeDeviceRes))
|
|
|
}
|
|
|
|
|
|
activeDeviceRate = 0
|
|
|
activeDevices = 0
|
|
|
if (activeDeviceRes && activeDeviceRes.data.length > 0) {
|
|
|
const thisDayActiveDevices = activeDeviceRes.data.length
|
|
|
const thisDayActiveDeviceIds = []
|
|
|
for (const tau in activeDeviceRes.data) {
|
|
|
thisDayActiveDeviceIds.push(activeDeviceRes.data[tau].device_id)
|
|
|
}
|
|
|
if (this.debug) {
|
|
|
console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
|
|
|
}
|
|
|
// 留存活跃设备数
|
|
|
const retentionActiveDeviceRes = await this.getCollection(activeDevicesObj.tableName).where({
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
device_id: {
|
|
|
$in: thisDayActiveDeviceIds
|
|
|
},
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
}).count()
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionActiveDeviceRes', JSON.stringify(retentionActiveDeviceRes))
|
|
|
}
|
|
|
if (retentionActiveDeviceRes && retentionActiveDeviceRes.total > 0) {
|
|
|
// 活跃设备留存数
|
|
|
activeDevices = retentionActiveDeviceRes.total
|
|
|
// 活跃设备留存率
|
|
|
activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取该批次的新增设备数
|
|
|
const newDeviceRes = await this.selectAll(activeDevicesObj.tableName, {
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
is_new: 1,
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
}, {
|
|
|
device_id: 1
|
|
|
})
|
|
|
|
|
|
newDeviceRate = 0
|
|
|
newDevices = 0
|
|
|
if (newDeviceRes && newDeviceRes.data.length > 0) {
|
|
|
const thisDayNewDevices = newDeviceRes.data.length
|
|
|
const thisDayNewDeviceIds = []
|
|
|
for (const tau in newDeviceRes.data) {
|
|
|
thisDayNewDeviceIds.push(newDeviceRes.data[tau].device_id)
|
|
|
}
|
|
|
|
|
|
// 新增设备留存数
|
|
|
const retentionNewDeviceRes = await this.getCollection(activeDevicesObj.tableName).where({
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
device_id: {
|
|
|
$in: thisDayNewDeviceIds
|
|
|
},
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
}).count()
|
|
|
|
|
|
if (retentionNewDeviceRes && retentionNewDeviceRes.total > 0) {
|
|
|
// 新增设备留存数
|
|
|
newDevices = retentionNewDeviceRes.total
|
|
|
// 新增设备留存率
|
|
|
newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 数据更新
|
|
|
const retentionData = resultLog.retention
|
|
|
const dataKey = type.substr(0, 1) + '_' + day
|
|
|
if (!retentionData.active_device) {
|
|
|
retentionData.active_device = {}
|
|
|
}
|
|
|
retentionData.active_device[dataKey] = {
|
|
|
device_count: activeDevices,
|
|
|
device_rate: activeDeviceRate
|
|
|
}
|
|
|
if (!retentionData.new_device) {
|
|
|
retentionData.new_device = {}
|
|
|
}
|
|
|
retentionData.new_device[dataKey] = {
|
|
|
device_count: newDevices,
|
|
|
device_rate: newDeviceRate
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionData', JSON.stringify(retentionData))
|
|
|
}
|
|
|
|
|
|
res = await this.update(this.tableName, {
|
|
|
retention: retentionData
|
|
|
}, {
|
|
|
_id: resultLog._id
|
|
|
})
|
|
|
}
|
|
|
|
|
|
if (res && res.updated) {
|
|
|
return {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
} else {
|
|
|
return {
|
|
|
code: 500,
|
|
|
msg: 'retention data update failed'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 用户日留存数据填充
|
|
|
* @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
|
|
|
* @param {Date|Time} date 指定日期或时间戳
|
|
|
* @param {Boolean} reset 是否重置,为ture时会重置该批次数据
|
|
|
*/
|
|
|
async userRetentionFillDayly(type, day, date) {
|
|
|
if (type !== 'day') {
|
|
|
return {
|
|
|
code: 301,
|
|
|
msg: 'Type error:' + type
|
|
|
}
|
|
|
}
|
|
|
const dateTime = new DateTime()
|
|
|
const {
|
|
|
startTime,
|
|
|
endTime
|
|
|
} = dateTime.getTimeDimensionByType(type, 0 - day, date)
|
|
|
|
|
|
if (!startTime || !endTime) {
|
|
|
return {
|
|
|
code: 1001,
|
|
|
msg: 'The statistic time get failed'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 截止时间范围
|
|
|
const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)
|
|
|
|
|
|
// 获取当时批次的统计日志
|
|
|
const resultLogRes = await this.selectAll(this.tableName, {
|
|
|
dimension: type,
|
|
|
start_time: startTime,
|
|
|
end_time: endTime
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('resultLogRes', JSON.stringify(resultLogRes))
|
|
|
}
|
|
|
|
|
|
if (!resultLogRes || resultLogRes.data.length === 0) {
|
|
|
if (this.debug) {
|
|
|
console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' +
|
|
|
endTime)
|
|
|
}
|
|
|
return {
|
|
|
code: 1000,
|
|
|
msg: 'Not found this log'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const userSessionLog = new UserSessionLog()
|
|
|
const uniIDUsers = new UniIDUsers()
|
|
|
const platform = new Platform()
|
|
|
const channel = new Channel()
|
|
|
const version = new Version()
|
|
|
let res = null
|
|
|
for (const resultIndex in resultLogRes.data) {
|
|
|
const resultLog = resultLogRes.data[resultIndex]
|
|
|
// 平台信息
|
|
|
let platformInfo = null
|
|
|
if (this.platforms && this.platforms[resultLog.platform_id]) {
|
|
|
platformInfo = this.platforms[resultLog.platform_id]
|
|
|
} else {
|
|
|
platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
|
|
|
if (!platformInfo || platformInfo.length === 0) {
|
|
|
platformInfo.code = ''
|
|
|
}
|
|
|
this.platforms[resultLog.platform_id] = platformInfo
|
|
|
if (this.debug) {
|
|
|
console.log('platformInfo', JSON.stringify(platformInfo))
|
|
|
}
|
|
|
}
|
|
|
// 渠道信息
|
|
|
let channelInfo = null
|
|
|
if (this.channels && this.channels[resultLog.channel_id]) {
|
|
|
channelInfo = this.channels[resultLog.channel_id]
|
|
|
} else {
|
|
|
channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
|
|
|
if (!channelInfo || channelInfo.length === 0) {
|
|
|
channelInfo.channel_code = ''
|
|
|
}
|
|
|
this.channels[resultLog.channel_id] = channelInfo
|
|
|
if (this.debug) {
|
|
|
console.log('channelInfo', JSON.stringify(channelInfo))
|
|
|
}
|
|
|
}
|
|
|
// 版本信息
|
|
|
let versionInfo = null
|
|
|
if (this.versions && this.versions[resultLog.version_id]) {
|
|
|
versionInfo = this.versions[resultLog.version_id]
|
|
|
} else {
|
|
|
versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
|
|
|
if (!versionInfo || versionInfo.length === 0) {
|
|
|
versionInfo.version = ''
|
|
|
}
|
|
|
this.versions[resultLog.version_id] = versionInfo
|
|
|
if (this.debug) {
|
|
|
console.log('versionInfo', JSON.stringify(versionInfo))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取该时间段内的活跃用户
|
|
|
const activeUserRes = await this.aggregate(userSessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
uid: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
},
|
|
|
group: {
|
|
|
_id: {
|
|
|
uid: '$uid'
|
|
|
},
|
|
|
create_time: {
|
|
|
$min: '$create_time'
|
|
|
},
|
|
|
sessionCount: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
},
|
|
|
sort: {
|
|
|
create_time: 1,
|
|
|
sessionCount: 1
|
|
|
},
|
|
|
getAll: true
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('activeUserRes', JSON.stringify(activeUserRes))
|
|
|
}
|
|
|
|
|
|
//活跃用户留存率
|
|
|
let activeUserRate = 0
|
|
|
//活跃用户留存数
|
|
|
let activeUsers = 0
|
|
|
if (activeUserRes && activeUserRes.data.length > 0) {
|
|
|
const thisDayActiveUsers = activeUserRes.data.length
|
|
|
//获取该时间段内的活跃用户编号,这里没用lookup联查是因为数据量较大时lookup效率很低
|
|
|
const thisDayActiveUids = []
|
|
|
for (let tau in activeUserRes.data) {
|
|
|
thisDayActiveUids.push(activeUserRes.data[tau]._id.uid)
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('thisDayActiveUids', JSON.stringify(thisDayActiveUids))
|
|
|
}
|
|
|
|
|
|
// 留存活跃用户数
|
|
|
const retentionActiveUserRes = await this.aggregate(userSessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
uid: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
uid: {
|
|
|
$in: thisDayActiveUids
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
uid: '$uid'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_users: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionActiveUserRes', JSON.stringify(retentionActiveUserRes))
|
|
|
}
|
|
|
if (retentionActiveUserRes && retentionActiveUserRes.data.length > 0) {
|
|
|
// 活跃用户留存数
|
|
|
activeUsers = retentionActiveUserRes.data[0].total_users
|
|
|
// 活跃用户留存率
|
|
|
activeUserRate = parseFloat((activeUsers * 100 / thisDayActiveUsers).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//新增用户编号
|
|
|
const thisDayNewUids = await uniIDUsers.getUserIds(resultLog.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
})
|
|
|
//新增用户留存率
|
|
|
let newUserRate = 0
|
|
|
//新增用户留存数
|
|
|
let newUsers = 0
|
|
|
if (thisDayNewUids.length > 0) {
|
|
|
// 现在依然活跃的用户数
|
|
|
const retentionNewUserRes = await this.aggregate(userSessionLog.tableName, {
|
|
|
project: {
|
|
|
appid: 1,
|
|
|
version: 1,
|
|
|
platform: 1,
|
|
|
channel: 1,
|
|
|
uid: 1,
|
|
|
create_time: 1
|
|
|
},
|
|
|
match: {
|
|
|
appid: resultLog.appid,
|
|
|
version: versionInfo.version,
|
|
|
platform: platformInfo.code,
|
|
|
channel: channelInfo.channel_code,
|
|
|
uid: {
|
|
|
$in: thisDayNewUids
|
|
|
},
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
},
|
|
|
group: [{
|
|
|
_id: {
|
|
|
uid: '$uid'
|
|
|
}
|
|
|
}, {
|
|
|
_id: {},
|
|
|
total_users: {
|
|
|
$sum: 1
|
|
|
}
|
|
|
}]
|
|
|
})
|
|
|
|
|
|
if (retentionNewUserRes && retentionNewUserRes.data.length > 0) {
|
|
|
// 新增用户留存数
|
|
|
newUsers = retentionNewUserRes.data[0].total_users
|
|
|
// 新增用户留存率
|
|
|
newUserRate = parseFloat((newUsers * 100 / thisDayNewUids.length).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 数据更新
|
|
|
const retentionData = resultLog.retention
|
|
|
const dataKey = type.substr(0, 1) + '_' + day
|
|
|
if (!retentionData.active_user) {
|
|
|
retentionData.active_user = {}
|
|
|
}
|
|
|
retentionData.active_user[dataKey] = {
|
|
|
user_count: activeUsers,
|
|
|
user_rate: activeUserRate
|
|
|
}
|
|
|
if (!retentionData.new_user) {
|
|
|
retentionData.new_user = {}
|
|
|
}
|
|
|
retentionData.new_user[dataKey] = {
|
|
|
user_count: newUsers,
|
|
|
user_rate: newUserRate
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionData', JSON.stringify(retentionData))
|
|
|
}
|
|
|
|
|
|
res = await this.update(this.tableName, {
|
|
|
retention: retentionData
|
|
|
}, {
|
|
|
_id: resultLog._id
|
|
|
})
|
|
|
}
|
|
|
|
|
|
if (res && res.updated) {
|
|
|
return {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
} else {
|
|
|
return {
|
|
|
code: 500,
|
|
|
msg: 'retention data update failed'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 用户周/月留存数据填充
|
|
|
* @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计
|
|
|
* @param {Date|Time} date 指定日期或时间戳
|
|
|
* @param {Boolean} reset 是否重置,为ture时会重置该批次数据
|
|
|
*/
|
|
|
async userRetentionFillWeekOrMonth(type, day, date) {
|
|
|
if (!['week', 'month'].includes(type)) {
|
|
|
return {
|
|
|
code: 301,
|
|
|
msg: 'Type error:' + type
|
|
|
}
|
|
|
}
|
|
|
const dateTime = new DateTime()
|
|
|
const {
|
|
|
startTime,
|
|
|
endTime
|
|
|
} = dateTime.getTimeDimensionByType(type, 0 - day, date)
|
|
|
|
|
|
if (!startTime || !endTime) {
|
|
|
return {
|
|
|
code: 1001,
|
|
|
msg: 'The statistic time get failed'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 截止时间范围
|
|
|
const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)
|
|
|
|
|
|
// 获取当时批次的统计日志
|
|
|
const resultLogRes = await this.selectAll(this.tableName, {
|
|
|
dimension: type,
|
|
|
start_time: startTime,
|
|
|
end_time: endTime
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('resultLogRes', JSON.stringify(resultLogRes))
|
|
|
}
|
|
|
|
|
|
if (!resultLogRes || resultLogRes.data.length === 0) {
|
|
|
if (this.debug) {
|
|
|
console.log('Not found this session log --' + type + ':' + day + ', start:' + startTime +
|
|
|
',endTime:' + endTime)
|
|
|
}
|
|
|
return {
|
|
|
code: 1000,
|
|
|
msg: 'Not found this session log'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const activeUserObj = new ActiveUsers()
|
|
|
const uniIDUsers = new UniIDUsers()
|
|
|
const platform = new Platform()
|
|
|
const channel = new Channel()
|
|
|
const version = new Version()
|
|
|
let res = null
|
|
|
//活跃用户留存率
|
|
|
let activeUserRate
|
|
|
//活跃用户留存数
|
|
|
let activeUsers
|
|
|
//新增用户留存率
|
|
|
let newUserRate
|
|
|
//新增用户留存数
|
|
|
let newUsers
|
|
|
for (const resultIndex in resultLogRes.data) {
|
|
|
const resultLog = resultLogRes.data[resultIndex]
|
|
|
|
|
|
// 平台信息
|
|
|
let platformInfo = null
|
|
|
if (this.platforms && this.platforms[resultLog.platform_id]) {
|
|
|
platformInfo = this.platforms[resultLog.platform_id]
|
|
|
} else {
|
|
|
platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
|
|
|
if (!platformInfo || platformInfo.length === 0) {
|
|
|
platformInfo.code = ''
|
|
|
}
|
|
|
this.platforms[resultLog.platform_id] = platformInfo
|
|
|
if (this.debug) {
|
|
|
console.log('platformInfo', JSON.stringify(platformInfo))
|
|
|
}
|
|
|
}
|
|
|
// 渠道信息
|
|
|
let channelInfo = null
|
|
|
if (this.channels && this.channels[resultLog.channel_id]) {
|
|
|
channelInfo = this.channels[resultLog.channel_id]
|
|
|
} else {
|
|
|
channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
|
|
|
if (!channelInfo || channelInfo.length === 0) {
|
|
|
channelInfo.channel_code = ''
|
|
|
}
|
|
|
this.channels[resultLog.channel_id] = channelInfo
|
|
|
if (this.debug) {
|
|
|
console.log('channelInfo', JSON.stringify(channelInfo))
|
|
|
}
|
|
|
}
|
|
|
// 版本信息
|
|
|
let versionInfo = null
|
|
|
if (this.versions && this.versions[resultLog.version_id]) {
|
|
|
versionInfo = this.versions[resultLog.version_id]
|
|
|
} else {
|
|
|
versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
|
|
|
if (!versionInfo || versionInfo.length === 0) {
|
|
|
versionInfo.version = ''
|
|
|
}
|
|
|
this.versions[resultLog.version_id] = versionInfo
|
|
|
if (this.debug) {
|
|
|
console.log('versionInfo', JSON.stringify(versionInfo))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取该批次的活跃用户数
|
|
|
const activeUserRes = await this.selectAll(activeUserObj.tableName, {
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
}
|
|
|
}, {
|
|
|
uid: 1
|
|
|
})
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('activeUserRes', JSON.stringify(activeUserRes))
|
|
|
}
|
|
|
|
|
|
activeUserRate = 0
|
|
|
activeUsers = 0
|
|
|
if (activeUserRes && activeUserRes.data.length > 0) {
|
|
|
const thisDayactiveUsers = activeUserRes.data.length
|
|
|
const thisDayActiveDeviceIds = []
|
|
|
for (const tau in activeUserRes.data) {
|
|
|
thisDayActiveDeviceIds.push(activeUserRes.data[tau].uid)
|
|
|
}
|
|
|
if (this.debug) {
|
|
|
console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
|
|
|
}
|
|
|
// 留存活跃用户数
|
|
|
const retentionactiveUserRes = await this.getCollection(activeUserObj.tableName).where({
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
uid: {
|
|
|
$in: thisDayActiveDeviceIds
|
|
|
},
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
}).count()
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionactiveUserRes', JSON.stringify(retentionactiveUserRes))
|
|
|
}
|
|
|
if (retentionactiveUserRes && retentionactiveUserRes.total > 0) {
|
|
|
// 活跃用户留存数
|
|
|
activeUsers = retentionactiveUserRes.total
|
|
|
// 活跃用户留存率
|
|
|
activeUserRate = parseFloat((activeUsers * 100 / thisDayactiveUsers).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
//新增用户编号
|
|
|
const thisDayNewUids = await uniIDUsers.getUserIds(resultLog.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
|
|
|
$gte: startTime,
|
|
|
$lte: endTime
|
|
|
})
|
|
|
|
|
|
// 新增用户留存率
|
|
|
newUserRate = 0
|
|
|
// 新增用户留存数
|
|
|
newUsers = 0
|
|
|
if(thisDayNewUids && thisDayNewUids.length > 0) {
|
|
|
// 新增用户留存数
|
|
|
const retentionnewUserRes = await this.getCollection(activeUserObj.tableName).where({
|
|
|
appid: resultLog.appid,
|
|
|
platform_id: resultLog.platform_id,
|
|
|
channel_id: resultLog.channel_id,
|
|
|
version_id: resultLog.version_id,
|
|
|
uid: {
|
|
|
$in: thisDayNewUids
|
|
|
},
|
|
|
dimension: type,
|
|
|
create_time: {
|
|
|
$gte: lastTimeInfo.startTime,
|
|
|
$lte: lastTimeInfo.endTime
|
|
|
}
|
|
|
}).count()
|
|
|
|
|
|
if (retentionnewUserRes && retentionnewUserRes.total > 0) {
|
|
|
// 新增用户留存数
|
|
|
newUsers = retentionnewUserRes.total
|
|
|
// 新增用户留存率
|
|
|
newUserRate = parseFloat((newUsers * 100 / thisDayNewUids.length).toFixed(2))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 数据更新
|
|
|
const retentionData = resultLog.retention
|
|
|
const dataKey = type.substr(0, 1) + '_' + day
|
|
|
if (!retentionData.active_user) {
|
|
|
retentionData.active_user = {}
|
|
|
}
|
|
|
retentionData.active_user[dataKey] = {
|
|
|
user_count: activeUsers,
|
|
|
user_rate: activeUserRate
|
|
|
}
|
|
|
if (!retentionData.new_user) {
|
|
|
retentionData.new_user = {}
|
|
|
}
|
|
|
retentionData.new_user[dataKey] = {
|
|
|
user_count: newUsers,
|
|
|
user_rate: newUserRate
|
|
|
}
|
|
|
|
|
|
if (this.debug) {
|
|
|
console.log('retentionData', JSON.stringify(retentionData))
|
|
|
}
|
|
|
|
|
|
res = await this.update(this.tableName, {
|
|
|
retention: retentionData
|
|
|
}, {
|
|
|
_id: resultLog._id
|
|
|
})
|
|
|
}
|
|
|
|
|
|
if (res && res.updated) {
|
|
|
return {
|
|
|
code: 0,
|
|
|
msg: 'success'
|
|
|
}
|
|
|
} else {
|
|
|
return {
|
|
|
code: 500,
|
|
|
msg: 'retention data update failed'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 清理实时统计的日志
|
|
|
* @param {Number} days 实时统计日志保留天数
|
|
|
*/
|
|
|
async cleanHourLog(days = 7) {
|
|
|
|
|
|
console.log('clean hour logs - day:', days)
|
|
|
|
|
|
const dateTime = new DateTime()
|
|
|
|
|
|
const res = await this.delete(this.tableName, {
|
|
|
dimension: 'hour',
|
|
|
start_time: {
|
|
|
$lt: dateTime.getTimeBySetDays(0 - days)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
if (!res.code) {
|
|
|
console.log('clean hour logs - res:', res)
|
|
|
}
|
|
|
}
|
|
|
}
|