runtime_tracker / services /EyeTimeRecorder.js
Murasame52's picture
Upload 5 files
00b9b23 verified
Raw
History Blame Contribute Delete
5.86 kB
// 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;