You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
canteen/uniCloud-aliyun/cloudfunctions/common/uni-stat/stat/mod/statResult.js

2152 lines
55 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* @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)
}
}
}