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

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