Files
digital-pilates/services/sleepMonitor.ts
richarjiang 7bd0b5fc52 # 方案总结
基于提供的 Git diff,我将生成以下 conventional commit message:

## 变更分析:

1. **核心功能**:
   - 新增睡眠监控服务(`services/sleepMonitor.ts`)
   - 新增睡眠通知服务(`services/sleepNotificationService.ts`)
   - iOS 原生端增加睡眠观察者方法

2. **应用启动优化**:
   - 重构 `app/_layout.tsx` 中的初始化流程,按优先级分阶段加载服务

3. **药品功能改进**:
   - 优化语音识别交互(实时预览、可取消)
   - Widget 增加 URL scheme 支持

4. **路由配置**:
   - 新增药品管理路由常量

## 提交信息类型:
- **主类型**:`feat` (新增睡眠监控功能)
- **作用域**:`health` (健康相关功能)

---

请确认方案后,我将生成最终的 commit message。

---

**最终 Commit Message:**

feat(health): 添加睡眠监控和通知服务,优化应用启动流程

- 新增睡眠监控服务,支持实时监听 HealthKit 睡眠数据更新
- 实现睡眠质量分析算法,计算睡眠评分和各阶段占比
- 新增睡眠通知服务,分析完成后自动推送质量评估和建议
- iOS 原生端实现睡眠数据观察者,支持后台数据传递
- 重构应用启动初始化流程,按优先级分阶段加载服务(关键/次要/后台/空闲)
- 优化药品录入页面语音识别交互,支持实时预览和取消操作
- 药品 Widget 增加 deeplink 支持,点击跳转到应用
- 新增药品管理路由常量配置
2025-11-14 10:52:26 +08:00

513 lines
15 KiB
TypeScript
Raw 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 { NativeEventEmitter, NativeModules } from 'react-native';
import { analyzeSleepAndSendNotification } from './sleepNotificationService';
const { HealthKitManager } = NativeModules;
// 睡眠阶段类型
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 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);
}
} 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;
}
}
// 导出单例
export const sleepMonitorService = new SleepMonitorService();