Spaces:
Running
Running
| // EyeTimeRecorder.js | |
| const { mongoose } = require('../index'); | |
| const TimezoneUtils = require('../utils/timezone'); | |
| const DailyEyeTime = mongoose.model('DailyEyeTime', { | |
| date: Date, // 本地时区日期零点对应的 UTC 时间戳 | |
| hourlyUsage: [Number] // 24小时数组,每项代表分钟数 | |
| }); | |
| class EyeTimeRecorder { | |
| constructor(timezoneConfig) { | |
| // 初始化时区工具 | |
| this.timezoneUtils = new TimezoneUtils(timezoneConfig.timezoneOffset || 'Asia/Shanghai'); | |
| // 存储所有设备的状态 | |
| this.deviceStates = new Map(); // { deviceId: { isActive: boolean, lastUpdateTime: timestamp } } | |
| // 全局状态追踪 | |
| this.globalActive = false; | |
| this.lastRecordTime = null; | |
| } | |
| /** | |
| * 记录设备活动状态 | |
| * @param {string} deviceId - 设备ID | |
| * @param {boolean} isActive - 是否活跃(用眼中) | |
| */ | |
| async recordActivity(deviceId, isActive = true) { | |
| const now = Date.now(); | |
| const wasGlobalActive = this.globalActive; | |
| // 更新设备状态 | |
| this.deviceStates.set(deviceId, { | |
| isActive: isActive, | |
| lastUpdateTime: now | |
| }); | |
| // 重新计算全局状态(只要有一个设备活跃,全局就活跃) | |
| this.globalActive = false; | |
| for (const [, state] of this.deviceStates) { | |
| if (state.isActive) { | |
| this.globalActive = true; | |
| break; | |
| } | |
| } | |
| // 如果全局状态从活跃变为非活跃,需要记录最后一段时间 | |
| if (wasGlobalActive && !this.globalActive && this.lastRecordTime) { | |
| await this._saveUsageTime(this.lastRecordTime, now); | |
| this.lastRecordTime = null; | |
| } | |
| // 如果全局状态从非活跃变为活跃,开始新的记录 | |
| if (!wasGlobalActive && this.globalActive) { | |
| this.lastRecordTime = now; | |
| } | |
| // 如果全局状态持续活跃,记录这段时间并更新起始时间 | |
| if (wasGlobalActive && this.globalActive && this.lastRecordTime) { | |
| await this._saveUsageTime(this.lastRecordTime, now); | |
| this.lastRecordTime = now; | |
| } | |
| } | |
| /** | |
| * 保存使用时间到数据库 | |
| * @private | |
| */ | |
| async _saveUsageTime(startTime, endTime) { | |
| const durationMs = endTime - startTime; | |
| if (durationMs <= 0) return; | |
| // 将时间段按本地时区的小时和日期拆分 | |
| const segments = this._splitTimeSegments(startTime, endTime); | |
| // 为每个时间段更新数据库 | |
| for (const segment of segments) { | |
| await this._updateDailyRecord(segment.date, segment.hour, segment.minutes); | |
| } | |
| } | |
| /** | |
| * 将时间段按本地时区的日期和小时拆分 | |
| * @private | |
| */ | |
| _splitTimeSegments(startTime, endTime) { | |
| const segments = []; | |
| let currentTime = startTime; | |
| while (currentTime < endTime) { | |
| // 转换为本地时间 | |
| const localDate = this.timezoneUtils.utcToLocal(new Date(currentTime)); | |
| const currentHour = localDate.getHours(); | |
| // 计算当前小时结束时间(本地时间) | |
| const hourEndLocal = new Date(localDate); | |
| hourEndLocal.setHours(currentHour, 59, 59, 999); | |
| // 转换回UTC | |
| const hourEndUtc = this.timezoneUtils.localToUtc(hourEndLocal).getTime(); | |
| // 确定这个时间段的结束时间 | |
| const segmentEnd = Math.min(endTime, hourEndUtc + 1); // +1ms 进入下一小时 | |
| // 计算分钟数 | |
| const segmentDurationMs = segmentEnd - currentTime; | |
| const minutes = segmentDurationMs / (60 * 1000); | |
| // 获取本地时区零点的正确 UTC 时间 | |
| // 例如:北京时间 2025-11-24 -> UTC 2025-11-23T16:00Z | |
| const localDayStartUtc = this.timezoneUtils.getLocalDayStart(new Date(currentTime)); | |
| segments.push({ | |
| date: localDayStartUtc, // 存储为本地时区零点对应的 UTC 时间 | |
| hour: currentHour, | |
| minutes: minutes | |
| }); | |
| currentTime = segmentEnd; | |
| } | |
| return segments; | |
| } | |
| /** | |
| * 更新数据库中的每日记录 | |
| * @private | |
| */ | |
| async _updateDailyRecord(date, hour, minutes) { | |
| try { | |
| // 查找或创建当天的记录 | |
| let record = await DailyEyeTime.findOne({ date: date }); | |
| if (!record) { | |
| // 创建新记录,初始化24小时数组为0 | |
| record = new DailyEyeTime({ | |
| date: date, // 存储为本地时区零点的 UTC 时间 | |
| hourlyUsage: Array(24).fill(0) | |
| }); | |
| } | |
| // 累加该小时的使用时间 | |
| record.hourlyUsage[hour] += minutes; | |
| // 保存到数据库 | |
| await record.save(); | |
| } catch (error) { | |
| console.error('保存用眼时间记录失败:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * 获取设备当前状态(可选方法,用于调试) | |
| */ | |
| getDeviceStatus(deviceId) { | |
| return this.deviceStates.get(deviceId) || null; | |
| } | |
| /** | |
| * 获取全局活跃状态(可选方法,用于调试) | |
| */ | |
| isGlobalActive() { | |
| return this.globalActive; | |
| } | |
| /** | |
| * 获取所有活跃设备列表(可选方法,用于调试) | |
| */ | |
| getActiveDevices() { | |
| const activeDevices = []; | |
| for (const [deviceId, state] of this.deviceStates) { | |
| if (state.isActive) { | |
| activeDevices.push(deviceId); | |
| } | |
| } | |
| return activeDevices; | |
| } | |
| } | |
| module.exports = EyeTimeRecorder; |