Files
digital-pilates/services/sleepMonitor.ts
richarjiang 21e57634e0 feat(hrv): 添加心率变异性监控和压力评估功能
- 新增 HRV 监听服务,实时监控心率变异性数据
- 实现 HRV 到压力指数的转换算法和压力等级评估
- 添加智能通知服务,在压力偏高时推送健康建议
- 优化日志系统,修复日志丢失问题并增强刷新机制
- 改进个人页面下拉刷新,支持并行数据加载
- 优化勋章数据缓存策略,减少不必要的网络请求
- 重构应用初始化流程,优化权限服务和健康监听服务的启动顺序
- 移除冗余日志输出,提升应用性能
2025-11-18 14:08:20 +08:00

547 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 睡眠监控服务
*
* 负责监听睡眠数据更新事件,分析睡眠质量并提供反馈
*/
import { logger } from '@/utils/logger';
import AsyncStorage from '@/utils/kvStore';
import dayjs from 'dayjs';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { analyzeSleepAndSendNotification } from './sleepNotificationService';
const { HealthKitManager } = NativeModules;
const SLEEP_ANALYSIS_SENT_KEY = '@sleep_analysis_sent';
// 睡眠阶段类型
export type SleepStage = 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown';
// 睡眠样本数据
export interface SleepSample {
id: string;
startDate: string;
endDate: string;
value: number;
categoryType: SleepStage;
duration: number;
source: {
name: string;
bundleIdentifier: string;
};
metadata: Record<string, any>;
}
// 睡眠分析结果
export interface SleepAnalysisData {
sessionStart: string;
sessionEnd: string;
totalSleepDuration: number; // 秒
totalSleepHours: number; // 小时
deepSleepDuration: number; // 秒
deepSleepHours: number; // 小时
deepSleepPercentage: number; // 百分比
remSleepDuration: number; // 秒
remSleepHours: number; // 小时
remSleepPercentage: number; // 百分比
coreSleepDuration: number; // 秒
coreSleepHours: number; // 小时
coreSleepPercentage: number; // 百分比
awakeDuration: number; // 秒
awakeMinutes: number; // 分钟
sleepEfficiency: number; // 百分比
sleepScore: number; // 0-100分
quality: 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor';
}
// 睡眠事件数据
export interface SleepEventData {
timestamp: number;
type: 'sleep_data_updated';
}
/**
* 睡眠监控服务类
*/
class SleepMonitorService {
private eventEmitter: NativeEventEmitter | null = null;
private eventSubscription: any = null;
private isInitialized = false;
private lastProcessedTime = 0;
private debounceDelay = 3000; // 3秒防抖延迟
/**
* 初始化睡眠监控服务
*/
async initialize(): Promise<boolean> {
if (this.isInitialized) {
console.log('[SleepMonitor] Already initialized');
return true;
}
try {
// 创建事件发射器
this.eventEmitter = new NativeEventEmitter(HealthKitManager);
// 订阅睡眠更新事件
this.eventSubscription = this.eventEmitter.addListener(
'sleepUpdate',
this.handleSleepUpdate.bind(this)
);
// 启动睡眠观察者
await HealthKitManager.startSleepObserver();
this.isInitialized = true;
console.log('[SleepMonitor] Initialized successfully');
return true;
} catch (error) {
console.error('[SleepMonitor] Initialization failed:', error);
return false;
}
}
/**
* 停止睡眠监控服务
*/
async stop(): Promise<void> {
if (!this.isInitialized) {
return;
}
try {
// 取消事件订阅
if (this.eventSubscription) {
this.eventSubscription.remove();
this.eventSubscription = null;
}
// 停止睡眠观察者
await HealthKitManager.stopSleepObserver();
this.isInitialized = false;
console.log('[SleepMonitor] Stopped successfully');
} catch (error) {
console.error('[SleepMonitor] Stop failed:', error);
}
}
/**
* 处理睡眠更新事件
*/
private async handleSleepUpdate(event: SleepEventData): Promise<void> {
console.log('[SleepMonitor] Sleep data updated:', event);
// 防抖处理:避免短时间内重复处理
const now = Date.now();
if (now - this.lastProcessedTime < this.debounceDelay) {
console.log('[SleepMonitor] Debouncing, skipping this update');
return;
}
this.lastProcessedTime = now;
try {
const alreadySentToday = await this.hasSentSleepAnalysisToday();
if (alreadySentToday) {
console.log('[SleepMonitor] Sleep analysis already sent today, skipping notification');
return;
}
// 分析最近的睡眠数据
const analysis = await this.analyzeSleepData();
if (analysis) {
logger.info('[SleepMonitor] Sleep analysis completed:', {
score: analysis.sleepScore,
quality: analysis.quality,
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
});
// 发送睡眠分析通知
await this.notifySleepAnalysis(analysis);
await this.markSleepAnalysisSent();
}
} catch (error) {
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
}
}
/**
* 分析睡眠数据
*/
async analyzeSleepData(): Promise<SleepAnalysisData | null> {
try {
// 获取最近24小时的睡眠数据
const endDate = new Date();
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
const result = await HealthKitManager.getSleepData({
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
});
if (!result.data || result.data.length === 0) {
console.log('[SleepMonitor] No sleep data found');
return null;
}
const sleepSamples: SleepSample[] = result.data;
// 找到最近一次完整的睡眠会话
const session = this.findLatestCompleteSleepSession(sleepSamples);
if (!session) {
console.log('[SleepMonitor] No complete sleep session found');
return null;
}
// 分析睡眠样本
const analysis = this.analyzeSleepSamples(session.samples, session.startDate, session.endDate);
return analysis;
} catch (error) {
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
return null;
}
}
/**
* 找到最近一次完整的睡眠会话
*/
private findLatestCompleteSleepSession(samples: SleepSample[]): {
samples: SleepSample[];
startDate: Date;
endDate: Date;
} | null {
if (samples.length === 0) return null;
// 按开始时间排序
const sortedSamples = [...samples].sort((a, b) =>
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
);
// 从最新的样本开始往前查找
const reversedSamples = [...sortedSamples].reverse();
const sessionSamples: SleepSample[] = [];
let sessionEnd: Date | null = null;
for (const sample of reversedSamples) {
// 如果是第一个样本,记录结束时间
if (!sessionEnd) {
sessionEnd = new Date(sample.endDate);
}
// 收集连续的睡眠样本时间间隔不超过30分钟
if (sessionSamples.length > 0) {
const lastSample = sessionSamples[sessionSamples.length - 1];
const gap = new Date(lastSample.startDate).getTime() - new Date(sample.endDate).getTime();
if (gap > 30 * 60 * 1000) { // 间隔超过30分钟
break;
}
}
sessionSamples.push(sample);
}
// 反转回正常顺序
sessionSamples.reverse();
if (sessionSamples.length === 0) return null;
const sessionStart = new Date(sessionSamples[0].startDate);
const sessionEndDate = sessionEnd || new Date(sessionSamples[sessionSamples.length - 1].endDate);
// 确保会话至少有2小时的数据
const duration = sessionEndDate.getTime() - sessionStart.getTime();
if (duration < 2 * 60 * 60 * 1000) { // 至少2小时
return null;
}
return {
samples: sessionSamples,
startDate: sessionStart,
endDate: sessionEndDate,
};
}
/**
* 分析睡眠样本数据
*/
private analyzeSleepSamples(
samples: SleepSample[],
sessionStart: Date,
sessionEnd: Date
): SleepAnalysisData {
// 计算各阶段睡眠时长
let totalSleepDuration = 0;
let deepSleepDuration = 0;
let remSleepDuration = 0;
let coreSleepDuration = 0;
let awakeDuration = 0;
for (const sample of samples) {
const duration = sample.duration;
switch (sample.categoryType) {
case 'deep':
deepSleepDuration += duration;
totalSleepDuration += duration;
break;
case 'rem':
remSleepDuration += duration;
totalSleepDuration += duration;
break;
case 'core':
coreSleepDuration += duration;
totalSleepDuration += duration;
break;
case 'asleep':
totalSleepDuration += duration;
break;
case 'awake':
awakeDuration += duration;
break;
}
}
// 计算睡眠效率
const totalInBedDuration = (sessionEnd.getTime() - sessionStart.getTime()) / 1000;
const sleepEfficiency = totalInBedDuration > 0
? (totalSleepDuration / totalInBedDuration) * 100
: 0;
// 计算各阶段睡眠占比
const deepSleepPercentage = totalSleepDuration > 0
? (deepSleepDuration / totalSleepDuration) * 100
: 0;
const remSleepPercentage = totalSleepDuration > 0
? (remSleepDuration / totalSleepDuration) * 100
: 0;
const coreSleepPercentage = totalSleepDuration > 0
? (coreSleepDuration / totalSleepDuration) * 100
: 0;
// 计算睡眠评分
const sleepScore = this.calculateSleepScore(
totalSleepDuration,
deepSleepDuration,
remSleepDuration,
sleepEfficiency,
awakeDuration
);
return {
sessionStart: sessionStart.toISOString(),
sessionEnd: sessionEnd.toISOString(),
totalSleepDuration,
totalSleepHours: totalSleepDuration / 3600,
deepSleepDuration,
deepSleepHours: deepSleepDuration / 3600,
deepSleepPercentage,
remSleepDuration,
remSleepHours: remSleepDuration / 3600,
remSleepPercentage,
coreSleepDuration,
coreSleepHours: coreSleepDuration / 3600,
coreSleepPercentage,
awakeDuration,
awakeMinutes: awakeDuration / 60,
sleepEfficiency,
sleepScore,
quality: this.getSleepQualityLabel(sleepScore),
};
}
/**
* 计算睡眠评分0-100分
*/
private calculateSleepScore(
totalSleepDuration: number,
deepSleepDuration: number,
remSleepDuration: number,
sleepEfficiency: number,
awakeDuration: number
): number {
let score = 0;
// 1. 总睡眠时长评分40分
const sleepHours = totalSleepDuration / 3600;
if (sleepHours >= 7 && sleepHours <= 9) {
score += 40; // 理想睡眠时长
} else if (sleepHours >= 6 && sleepHours < 7) {
score += 35; // 稍短
} else if (sleepHours >= 9 && sleepHours < 10) {
score += 35; // 稍长
} else if (sleepHours >= 5 && sleepHours < 6) {
score += 25; // 较短
} else if (sleepHours >= 10 && sleepHours < 11) {
score += 30; // 较长
} else if (sleepHours >= 4 && sleepHours < 5) {
score += 15; // 很短
} else if (sleepHours >= 11) {
score += 20; // 过长
} else {
score += 10; // 极短
}
// 2. 深度睡眠评分25分
const deepSleepPercentage = totalSleepDuration > 0
? (deepSleepDuration / totalSleepDuration) * 100
: 0;
if (deepSleepPercentage >= 13 && deepSleepPercentage <= 23) {
score += 25; // 理想的深度睡眠占比13-23%
} else if (deepSleepPercentage >= 10 && deepSleepPercentage < 13) {
score += 20;
} else if (deepSleepPercentage >= 23 && deepSleepPercentage < 30) {
score += 20;
} else if (deepSleepPercentage >= 7 && deepSleepPercentage < 10) {
score += 15;
} else if (deepSleepPercentage >= 30) {
score += 15;
} else {
score += 10;
}
// 3. REM睡眠评分20分
const remSleepPercentage = totalSleepDuration > 0
? (remSleepDuration / totalSleepDuration) * 100
: 0;
if (remSleepPercentage >= 20 && remSleepPercentage <= 25) {
score += 20; // 理想的REM睡眠占比20-25%
} else if (remSleepPercentage >= 15 && remSleepPercentage < 20) {
score += 18;
} else if (remSleepPercentage >= 25 && remSleepPercentage < 30) {
score += 18;
} else if (remSleepPercentage >= 10 && remSleepPercentage < 15) {
score += 12;
} else if (remSleepPercentage >= 30) {
score += 15;
} else {
score += 8;
}
// 4. 睡眠效率评分15分
if (sleepEfficiency >= 85) {
score += 15; // 优秀的睡眠效率
} else if (sleepEfficiency >= 75) {
score += 12;
} else if (sleepEfficiency >= 65) {
score += 8;
} else {
score += 5;
}
return Math.min(Math.round(score), 100);
}
/**
* 根据评分获取睡眠质量标签
*/
private getSleepQualityLabel(score: number): 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor' {
if (score >= 90) return 'excellent';
if (score >= 75) return 'good';
if (score >= 60) return 'fair';
if (score >= 40) return 'poor';
return 'very_poor';
}
/**
* 通知睡眠分析结果
*/
private async notifySleepAnalysis(analysis: SleepAnalysisData): Promise<void> {
try {
// 发送睡眠分析通知
await analyzeSleepAndSendNotification(analysis);
logger.info('[SleepMonitor] Sleep analysis notification sent:', {
score: analysis.sleepScore,
quality: analysis.quality,
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
efficiency: `${analysis.sleepEfficiency.toFixed(0)}%`,
});
// 可以在这里更新 Redux store
// store.dispatch(updateSleepAnalysis(analysis));
} catch (error) {
logger.error('[SleepMonitor] Failed to send sleep notification:', error);
}
}
/**
* 获取睡眠质量描述
*/
getQualityDescription(quality: string): string {
const descriptions = {
excellent: '优秀 - 您的睡眠质量非常好!',
good: '良好 - 您的睡眠质量不错',
fair: '一般 - 您的睡眠质量还可以改善',
poor: '较差 - 建议改善睡眠习惯',
very_poor: '很差 - 强烈建议关注睡眠健康',
};
return descriptions[quality as keyof typeof descriptions] || '未知';
}
/**
* 获取睡眠建议
*/
getSleepRecommendations(analysis: SleepAnalysisData): string[] {
const recommendations: string[] = [];
// 基于总睡眠时长的建议
if (analysis.totalSleepHours < 7) {
recommendations.push('建议增加睡眠时间成年人每晚需要7-9小时睡眠');
} else if (analysis.totalSleepHours > 9) {
recommendations.push('睡眠时间偏长,可能影响睡眠质量');
}
// 基于深度睡眠的建议
if (analysis.deepSleepPercentage < 13) {
recommendations.push('深度睡眠不足,建议睡前避免使用电子设备');
}
// 基于REM睡眠的建议
if (analysis.remSleepPercentage < 20) {
recommendations.push('REM睡眠不足建议保持规律的作息时间');
}
// 基于睡眠效率的建议
if (analysis.sleepEfficiency < 85) {
recommendations.push('睡眠效率较低,建议改善睡眠环境');
}
// 基于清醒时间的建议
if (analysis.awakeMinutes > 30) {
recommendations.push('夜间醒来时间较长,建议睡前放松身心');
}
return recommendations;
}
private getTodayKey(): string {
return dayjs().format('YYYY-MM-DD');
}
private async hasSentSleepAnalysisToday(): Promise<boolean> {
try {
const todayKey = this.getTodayKey();
const value = await AsyncStorage.getItem(SLEEP_ANALYSIS_SENT_KEY);
return value === todayKey;
} catch (error) {
logger.warn('[SleepMonitor] Failed to check sleep analysis sent flag:', error);
return false;
}
}
private async markSleepAnalysisSent(): Promise<void> {
try {
await AsyncStorage.setItem(SLEEP_ANALYSIS_SENT_KEY, this.getTodayKey());
} catch (error) {
logger.warn('[SleepMonitor] Failed to mark sleep analysis as sent:', error);
}
}
}
// 导出单例
export const sleepMonitorService = new SleepMonitorService();