// StatsQuery.js - 简化版(适配新的存储逻辑) const { mongoose } = require('../index'); const DateRangeHelper = require('../utils/DateRange'); const TimezoneUtils = require('../utils/timezone'); const DailyStat = mongoose.model('DailyStat'); class StatsQuery { constructor(recorder, config = {}) { this.recorder = recorder; this.timezoneOffset = config.timezoneOffset || 8; this.dateRangeHelper = new DateRangeHelper(this.timezoneOffset); this.tzUtils = new TimezoneUtils(this.timezoneOffset); } // 查询统计数据(直接查询本地日期) async _queryStats(query, startDate, endDate) { // 构建查询日期数组(本地时区日期) const queryDates = []; const current = new Date(startDate); while (current <= endDate) { // 转换为数据库存储格式(本地零点对应的 UTC 时间) const localDayStart = new Date(Date.UTC( current.getUTCFullYear(), current.getUTCMonth(), current.getUTCDate(), 0, 0, 0, 0 )); const dbDate = this.tzUtils.localToUtc(localDayStart); queryDates.push(dbDate); // 移动到下一天 current.setUTCDate(current.getUTCDate() + 1); } query.date = { $in: queryDates }; return DailyStat.find(query); } // 处理统计数据 _processStats(allStats, startDate, endDate, isSingleDay = false) { const dailyStats = {}; const appDailyStats = {}; const hourlyStats = Array(24).fill(0); const appHourlyStats = {}; let totalUsage = 0; allStats.forEach(stat => { const appName = stat.appName; // 将数据库的 date 转换回本地日期 const dbDate = new Date(stat.date); const localDayStart = this.tzUtils.utcToLocal(dbDate); const localDateOnly = new Date(Date.UTC( localDayStart.getUTCFullYear(), localDayStart.getUTCMonth(), localDayStart.getUTCDate() )); const dateKey = localDateOnly.toISOString().split('T')[0]; // 初始化 if (!dailyStats[dateKey]) dailyStats[dateKey] = 0; if (!appDailyStats[appName]) appDailyStats[appName] = {}; if (!appDailyStats[appName][dateKey]) appDailyStats[appName][dateKey] = 0; if (!appHourlyStats[appName]) { appHourlyStats[appName] = Array(24).fill(0); } // 累加每小时数据 stat.hourlyUsage.forEach((minutes, hour) => { if (minutes > 0) { dailyStats[dateKey] += minutes; appDailyStats[appName][dateKey] += minutes; hourlyStats[hour] += minutes; appHourlyStats[appName][hour] += minutes; totalUsage += minutes; } }); }); return { dailyStats, appDailyStats, hourlyStats, appHourlyStats, totalUsage }; } // ============ 对外接口 ============ // 获取某天的统计数据 async getDailyStats(deviceId, date) { const localDate = this.tzUtils.parseDate(date); const allStats = await this._queryStats( { deviceId }, localDate, localDate ); const { hourlyStats, appHourlyStats, totalUsage } = this._processStats( allStats, localDate, localDate, true ); // 只保留有数据的应用 const appStats = {}; Object.keys(appHourlyStats).forEach(appName => { const total = appHourlyStats[appName].reduce((sum, val) => sum + val, 0); if (total > 0) appStats[appName] = total; }); return { totalUsage, appStats, hourlyStats, appHourlyStats: Object.keys(appStats).length > 0 ? appHourlyStats : {} }; } // 获取周统计 async getWeeklyAppStats(deviceId, appName = null, weekOffset = 0) { const { startDate, endDate } = this.dateRangeHelper.getWeekRange(weekOffset); const query = { deviceId }; if (appName) query.appName = appName; const allStats = await this._queryStats(query, startDate, endDate); const { dailyStats, appDailyStats } = this._processStats(allStats, startDate, endDate); return { weekOffset, weekRange: { start: startDate.toISOString().split('T')[0], end: endDate.toISOString().split('T')[0] }, dailyTotals: dailyStats, appDailyStats: appName ? { [appName]: appDailyStats[appName] || {} } : appDailyStats }; } // 获取月统计 async getMonthlyAppStats(deviceId, appName = null, monthOffset = 0) { const { startDate, endDate } = this.dateRangeHelper.getMonthRange(monthOffset); const query = { deviceId }; if (appName) query.appName = appName; const allStats = await this._queryStats(query, startDate, endDate); const { dailyStats, appDailyStats } = this._processStats(allStats, startDate, endDate); return { monthOffset, monthRange: { start: startDate.toISOString().split('T')[0], end: endDate.toISOString().split('T')[0] }, dailyTotals: dailyStats, appDailyStats: appName ? { [appName]: appDailyStats[appName] || {} } : appDailyStats }; } // 获取某天所有设备统计 async getDailyStatsForAllDevices(date) { const localDate = this.tzUtils.parseDate(date); const allStats = await this._queryStats({}, localDate, localDate); const { hourlyStats, appHourlyStats, totalUsage } = this._processStats( allStats, localDate, localDate, true ); const appStats = {}; Object.keys(appHourlyStats).forEach(appName => { const total = appHourlyStats[appName].reduce((sum, val) => sum + val, 0); if (total > 0) appStats[appName] = total; }); return { totalUsage, appStats, hourlyStats, appHourlyStats }; } // 获取周统计所有设备 async getWeeklyAppStatsForAllDevices(appName = null, weekOffset = 0) { const { startDate, endDate } = this.dateRangeHelper.getWeekRange(weekOffset); const query = {}; if (appName) query.appName = appName; const allStats = await this._queryStats(query, startDate, endDate); const { dailyStats, appDailyStats } = this._processStats(allStats, startDate, endDate); return { weekOffset, weekRange: { start: startDate.toISOString().split('T')[0], end: endDate.toISOString().split('T')[0] }, dailyTotals: dailyStats, appDailyStats: appName ? { [appName]: appDailyStats[appName] || {} } : appDailyStats }; } // 获取月统计所有设备 async getMonthlyAppStatsForAllDevices(appName = null, monthOffset = 0) { const { startDate, endDate } = this.dateRangeHelper.getMonthRange(monthOffset); const query = {}; if (appName) query.appName = appName; const allStats = await this._queryStats(query, startDate, endDate); const { dailyStats, appDailyStats } = this._processStats(allStats, startDate, endDate); return { monthOffset, monthRange: { start: startDate.toISOString().split('T')[0], end: endDate.toISOString().split('T')[0] }, dailyTotals: dailyStats, appDailyStats: appName ? { [appName]: appDailyStats[appName] || {} } : appDailyStats }; } // 获取设备列表 async getDevices() { return Array.from(this.recorder.recentAppSwitches.keys()).map(deviceId => { let currentApp = "Unknown"; let runningSince = new Date(); let isRunning = true; const batteryInfo = this.recorder.getLatestBatteryInfo(deviceId); if (this.recorder.recentAppSwitches.has(deviceId) && this.recorder.recentAppSwitches.get(deviceId).length > 0) { const lastSwitch = this.recorder.recentAppSwitches.get(deviceId)[0]; currentApp = lastSwitch.appName; runningSince = lastSwitch.timestamp; isRunning = lastSwitch.running !== false; } return { device: deviceId, currentApp, running: isRunning, runningSince, batteryLevel: batteryInfo.level, isCharging: batteryInfo.isCharging, batteryTimestamp: batteryInfo.timestamp }; }); } } module.exports = StatsQuery;