/** * @class UniStatDataStat uni统计-数据统计调度处理模块 * @function cron 数据统计定时任务处理函数 * @function stat 数据统计调度处理函数 * @function cleanLog 日志清理调度处理函数 */ const { DateTime } = require('./lib') const { sleep } = require('../shared') const { BaseMod, SessionLog, PageLog, EventLog, ShareLog, ErrorLog, StatResult, ActiveDevices, ActiveUsers, PageResult, EventResult, ErrorResult, Loyalty, RunErrors, UserSessionLog, uniPay, Setting } = require('./mod') class UniStatDataStat { /** * 数据统计定时任务处理函数 * @param {Object} context 服务器请求上下文参数 */ async cron(context) { const baseMod = new BaseMod() const dateTime = new DateTime() console.log('Cron start time: ', dateTime.getDate('Y-m-d H:i:s')) // const setting = new Setting(); // let settingValue = await setting.getSetting() // if (settingValue.mode === "close") { // // 如果关闭了统计任务,则任务直接结束 // return { // code: 0, // msg: 'Task is close', // } // } else if (settingValue.mode === "auto") { // // 如果开启了节能模式,则判断N天内是否有设备访问记录 // let runKey = await setting.checkAutoRun(settingValue); // if (!runKey) { // return { // code: 0, // msg: 'Task is auto close', // } // } // } //获取运行参数 const timeInfo = dateTime.getTimeInfo(null, false) const cronConfig = baseMod.getConfig('cron') const cronMin = baseMod.getConfig('cronMin') const realtimeStat = baseMod.getConfig('realtimeStat') // 数据跑批 let res = null if (cronConfig && cronConfig.length > 0) { for (var mi in cronConfig) { const currCronConfig = cronConfig[mi] const cronType = currCronConfig.type const cronTime = currCronConfig.time.split(' ') const cronDimension = currCronConfig.dimension //未开启分钟级定时任务,则设置为小时级定时任务 if (cronTime.length === 4 && !cronMin) { cronTime.splice(3, 1) } if (baseMod.debug) { console.log('cronTime', cronTime) } //精度为分钟级的定时任务 if (cronTime.length === 4) { if (cronTime[0] !== '*') { //周统计任务 if (timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) { let dimension = cronDimension || 'week'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: cronDimension, config: currCronConfig }) } } else if (cronTime[1] !== '*') { //月统计任务(包含季度统计任务和年统计任务) if (timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) { let dimension = cronDimension || 'month'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } else if (cronTime[2] !== '*') { //日统计任务 if (timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) { let dimension = cronDimension || 'day'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } else if (cronTime[3] !== '*') { //实时统计任务 if (timeInfo.nMinutes == cronTime[3] && realtimeStat) { let dimension = cronDimension || 'hour'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } } //精度为小时级的定时任务 else if (cronTime.length === 3) { if (cronTime[0] !== '*') { //周统计任务 if (timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2]) { let dimension = cronDimension || 'week'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } else if (cronTime[1] !== '*') { //月统计任务(包含季度统计任务和年统计任务) if (timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2]) { let dimension = cronDimension || 'month'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } else if (cronTime[2] !== '*') { //日统计任务 if (timeInfo.nHour == cronTime[2]) { let dimension = cronDimension || 'day'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } else { //实时统计任务 if (realtimeStat) { let dimension = cronDimension || 'hour'; console.log(cronType + `--${dimension} run`) res = await this.stat({ type: cronType, dimension: dimension, config: currCronConfig }) } } } else { console.error('Cron configuration error') } } } console.log('Cron end time: ', dateTime.getDate('Y-m-d H:i:s')) return { code: 0, msg: 'Task have done', lastCronResult: res } } /** * 数据统计调度处理函数 * @param {Object} params 统计参数 */ async stat(params) { const { type, dimension, date, reset, config } = params let res = { code: 0, msg: 'success' } try { switch (type) { // 基础统计 case 'stat': { const resultStat = new StatResult() res = await resultStat.stat(dimension, date, reset) break } // 活跃设备统计归集 case 'active-device': { const activeDevices = new ActiveDevices() res = await activeDevices.stat(date, reset) break } // 活跃用户统计归集 case 'active-user': { const activeUsers = new ActiveUsers() res = await activeUsers.stat(date, reset) break } // 设备留存统计 case 'retention-device': { const retentionStat = new StatResult() res = await retentionStat.retentionStat(dimension, date) break } // 用户留存统计 case 'retention-user': { const retentionStat = new StatResult() res = await retentionStat.retentionStat(dimension, date, 'user') break } // 页面统计 case 'page': { const pageStat = new PageResult() res = await pageStat.stat(dimension, date, reset) break } // 事件统计 case 'event': { const eventStat = new EventResult() res = await eventStat.stat(dimension, date, reset) break } // 错误统计 case 'error': { const errorStat = new ErrorResult() res = await errorStat.stat(dimension, date, reset) break } // 设备忠诚度统计 case 'loyalty': { const loyaltyStat = new Loyalty() res = await loyaltyStat.stat(dimension, date, reset) break } // 日志清理 case 'clean': { res = await this.cleanLog() } // 支付统计 case 'pay-result': { const paymentResult = new uniPay.PayResult() res = await paymentResult.stat(dimension, date, reset, config) break } } } catch (e) { const maxTryTimes = 2 if (!this.tryTimes) { this.tryTimes = 1 } else { this.tryTimes++ } //报错则重新尝试2次, 解决部分云服务器偶现连接超时问题 if (this.tryTimes <= maxTryTimes) { //休眠1秒后重新调用 await sleep(1000) params.reset = true res = await this.stat(params) } else { // 2次尝试失败后记录错误 console.error('server error: ' + e) const runError = new RunErrors() runError.create({ mod: 'stat', params: params, error: e, create_time: new DateTime().getTime() }) res = { code: 500, msg: 'server error' + e } } } return res } /** * 日志清理调度处理函数 */ async cleanLog() { const baseMod = new BaseMod() const cleanLog = baseMod.getConfig('cleanLog') if (!cleanLog || !cleanLog.open) { return { code: 100, msg: 'The log cleanup service has not been turned on' } } const res = { code: 0, msg: 'success', data: {} } // 会话日志 if (cleanLog.reserveDays.sessionLog > 0) { const sessionLog = new SessionLog() res.data.sessionLog = await sessionLog.clean(cleanLog.reserveDays.sessionLog) } // 用户会话日志 if (cleanLog.reserveDays.userSessionLog > 0) { const userSessionLog = new UserSessionLog() res.data.userSessionLog = await userSessionLog.clean(cleanLog.reserveDays.userSessionLog) } // 页面日志 if (cleanLog.reserveDays.pageLog > 0) { const pageLog = new PageLog() res.data.pageLog = await pageLog.clean(cleanLog.reserveDays.pageLog) } // 事件日志 if (cleanLog.reserveDays.eventLog > 0) { const eventLog = new EventLog() res.data.eventLog = await eventLog.clean(cleanLog.reserveDays.eventLog) } // 分享日志 if (cleanLog.reserveDays.shareLog > 0) { const shareLog = new ShareLog() res.data.shareLog = await shareLog.clean(cleanLog.reserveDays.shareLog) } // 错误日志 if (cleanLog.reserveDays.errorLog > 0) { const errorLog = new ErrorLog() res.data.errorLog = await errorLog.clean(cleanLog.reserveDays.errorLog) } // 活跃设备日志 const activeDevicesLog = new ActiveDevices() res.data.activeDevicesLog = await activeDevicesLog.clean() // 活跃用户日志 const activeUsersLog = new ActiveUsers() res.data.activeUsersLog = await activeUsersLog.clean() // 实时统计日志 const resultHourLog = new StatResult() res.data.resultHourLog = await resultHourLog.cleanHourLog() //原生应用崩溃日志 const appCrashLogs = new AppCrashLogs() res.data.appCrashLogs = await appCrashLogs.clean() return res } } module.exports = UniStatDataStat