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