import { listChallenges } from '@/services/challengesApi'; import { store } from '@/store'; import { getWaterIntakeFromHealthKit } from '@/utils/health'; import AsyncStorage from '@/utils/kvStore'; import { log } from '@/utils/logger'; import { ChallengeNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers'; import { getWaterGoalFromStorage } from '@/utils/userPreferences'; import { resyncFastingNotifications } from '@/services/fastingNotifications'; import { selectActiveFastingSchedule, selectActiveFastingPlan } from '@/store/fastingSlice'; import dayjs from 'dayjs'; import * as BackgroundTask from 'expo-background-task'; import * as TaskManager from 'expo-task-manager'; import { TaskManagerTaskBody } from 'expo-task-manager'; export const BACKGROUND_TASK_IDENTIFIER = 'com.anonymous.digitalpilates.task'; // 检查通知权限 async function checkNotificationPermissions(): Promise { try { const Notifications = await import('expo-notifications'); const { status } = await Notifications.getPermissionsAsync(); return status === 'granted'; } catch (error) { console.error('检查通知权限失败:', error); return false; } } // 执行喝水提醒后台任务 async function executeWaterReminderTask(): Promise { try { console.log('执行喝水提醒后台任务...'); // 获取当前状态,添加错误处理 let state; try { state = store.getState(); } catch (error) { console.log('无法获取 Redux state,使用本地存储:', error); // 使用本地存储作为后备方案 const dailyGoal = await getWaterGoalFromStorage(); if (!dailyGoal || dailyGoal <= 0) { console.log('没有设置喝水目标,跳过喝水提醒'); return; } // 简化的提醒逻辑 await sendSimpleWaterReminder(); return; } const waterStats = state.water?.todayStats; const userProfile = state.user?.profile; // 优先使用 Redux 中的目标,若无则读取本地存储 let dailyGoal = waterStats?.dailyGoal ?? 0; if (!dailyGoal || dailyGoal <= 0) { dailyGoal = await getWaterGoalFromStorage(); } if (!dailyGoal || dailyGoal <= 0) { console.log('没有设置喝水目标,跳过喝水提醒'); return; } // 检查时间限制(避免深夜打扰) const currentHour = new Date().getHours(); // 获取用户名 const userName = userProfile?.name || '朋友'; const todayRange = { startDate: dayjs().startOf('day').toDate().toISOString(), endDate: dayjs().endOf('day').toDate().toISOString() }; let totalAmount = waterStats?.totalAmount ?? 0; let completionRate = waterStats?.completionRate ?? (dailyGoal > 0 ? (totalAmount / dailyGoal) * 100 : 0); try { const healthKitRecords = await getWaterIntakeFromHealthKit(todayRange); if (Array.isArray(healthKitRecords) && healthKitRecords.length > 0) { totalAmount = healthKitRecords.reduce((sum: number, record: unknown) => { if (record && typeof record === 'object' && 'value' in record) { const { value } = record as { value?: number | string }; const numericValue = Number(value ?? 0); return Number.isFinite(numericValue) ? sum + numericValue : sum; } return sum; }, 0); completionRate = Math.min((totalAmount / dailyGoal) * 100, 100); } else { console.log('HealthKit 未返回今日饮水记录,使用应用内缓存数据'); } } catch (healthKitError) { console.error('从HealthKit获取饮水记录失败,使用应用内缓存数据:', healthKitError); } // 构造今日统计数据 const todayWaterStats = { totalAmount, dailyGoal, completionRate: Number.isFinite(completionRate) ? completionRate : 0 }; // 调用喝水通知检查函数 const notificationSent = await WaterNotificationHelpers.checkWaterGoalAndNotify( userName, todayWaterStats, currentHour ); if (notificationSent) { console.log('后台喝水提醒通知已发送'); // 记录后台任务执行时间 await AsyncStorage.setItem('@last_background_water_check', Date.now().toString()); } else { console.log('无需发送后台喝水提醒通知'); } } catch (error) { console.error('执行喝水提醒后台任务失败:', error); } } async function executeChallengeReminderTask(): Promise { try { console.log('执行挑战鼓励提醒后台任务...'); let userName = '朋友'; try { const state = store.getState(); const normalizedUserName = state.user?.profile?.name?.trim(); userName = normalizedUserName && normalizedUserName.length > 0 ? normalizedUserName : '朋友'; } catch (error) { console.log('无法获取用户名,使用默认值:', error); } const challenges = await listChallenges(); const joinedChallenges = challenges.filter((challenge) => challenge.isJoined && challenge.progress); if (!joinedChallenges.length) { console.log('没有加入的挑战或挑战没有进度,跳过挑战提醒'); return; } const todayKey = new Date().toISOString().slice(0, 10); // 筛选出需要发送通知的挑战(未签到且今天未发送过通知) const eligibleChallenges = []; for (const challenge of joinedChallenges) { const progress = challenge.progress; if (!progress || progress.checkedInToday) { continue; } const storageKey = `@challenge_encouragement_sent:${challenge.id}`; const lastSent = await AsyncStorage.getItem(storageKey); if (lastSent === todayKey) { continue; } eligibleChallenges.push(challenge); } // 如果有符合条件的挑战,随机选择一个发送通知 if (eligibleChallenges.length > 0) { const randomIndex = Math.floor(Math.random() * eligibleChallenges.length); const selectedChallenge = eligibleChallenges[randomIndex]; try { await ChallengeNotificationHelpers.sendEncouragementNotification({ userName, challengeTitle: selectedChallenge.title, challengeId: selectedChallenge.id, }); const storageKey = `@challenge_encouragement_sent:${selectedChallenge.id}`; await AsyncStorage.setItem(storageKey, todayKey); console.log(`已随机选择并发送挑战鼓励通知: ${selectedChallenge.title}`); } catch (notificationError) { console.error('发送挑战鼓励通知失败:', notificationError); } } else { console.log('没有符合条件的挑战需要发送鼓励通知'); } console.log('挑战鼓励提醒后台任务完成'); } catch (error) { console.error('执行挑战鼓励提醒后台任务失败:', error); } } // 执行断食通知后台任务 async function executeFastingNotificationTask(): Promise { try { console.log('执行断食通知后台任务...'); let state; try { state = store.getState(); } catch (error) { console.log('无法获取 Redux state,跳过断食通知任务:', error); return; } const activeSchedule = selectActiveFastingSchedule(state); const activePlan = selectActiveFastingPlan(state); if (!activeSchedule || !activePlan) { console.log('没有激活的断食计划,跳过断食通知任务'); return; } // 检查断食计划是否已结束 const end = dayjs(activeSchedule.endISO); if (end.isBefore(dayjs())) { console.log('断食计划已结束,跳过断食通知任务'); return; } console.log('正在同步断食通知...', { planId: activePlan.id, start: activeSchedule.startISO, end: activeSchedule.endISO, }); // 重新同步断食通知 await resyncFastingNotifications({ schedule: activeSchedule, plan: activePlan, enabled: true, }); console.log('断食通知后台同步完成'); } catch (error) { console.error('执行断食通知后台任务失败:', error); } } // 发送测试通知以验证后台任务执行 async function sendTestNotification(): Promise { try { console.log('发送后台任务测试通知...'); const Notifications = await import('expo-notifications'); // 发送简单的测试通知 await Notifications.scheduleNotificationAsync({ content: { title: '后台任务测试', body: `后台任务正在执行中... 时间: ${new Date().toLocaleTimeString()}`, data: { type: 'background_task_test', timestamp: Date.now() } }, trigger: null, // 立即发送 }); console.log('后台任务测试通知发送成功'); // 记录测试通知发送时间 await AsyncStorage.setItem('@last_background_test_notification', Date.now().toString()); } catch (error) { console.error('发送测试通知失败:', error); } } /** * 发送简单的喝水提醒(后备方案) */ async function sendSimpleWaterReminder(): Promise { try { const userName = '朋友'; // 默认用户名 const Notifications = await import('expo-notifications'); const notificationId = await Notifications.scheduleNotificationAsync({ content: { title: '💧 该喝水啦!', body: `${userName},记得补充水分,保持身体健康~`, data: { type: 'water_reminder', url: '/statistics' }, sound: 'default', }, trigger: null, // 立即发送 }); console.log('简单喝水提醒已发送,ID:', notificationId); } catch (error) { console.error('发送简单喝水提醒失败:', error); } } // 后台任务执行函数 async function executeBackgroundTasks(): Promise { console.log('开始执行后台任务...'); try { // 检查应用权限和用户设置 const hasPermission = await checkNotificationPermissions(); if (!hasPermission) { console.log('没有通知权限,跳过后台任务'); return; } // 确保 Redux store 已初始化 try { const state = store.getState(); if (!state) { console.log('Redux store 未初始化,跳过后台任务'); return; } } catch (error) { console.log('无法访问 Redux store,跳过后台任务:', error); return; } // await sendTestNotification() // 可选:启用测试通知 // 检查是否启用测试通知 const testNotificationsEnabled = await AsyncStorage.getItem('@background_test_notifications_enabled') === 'true'; if (testNotificationsEnabled) { await sendTestNotification(); } // 执行喝水提醒检查任务 await executeWaterReminderTask(); // 执行挑战鼓励提醒任务 await executeChallengeReminderTask(); // 执行断食通知任务 await executeFastingNotificationTask(); console.log('后台任务执行完成'); } catch (error) { console.error('执行后台任务失败:', error); } } /** * 后台任务管理器 * 负责配置和管理应用的后台任务执行 */ export class BackgroundTaskManager { private static instance: BackgroundTaskManager; private isInitialized = false; static getInstance(): BackgroundTaskManager { if (!BackgroundTaskManager.instance) { BackgroundTaskManager.instance = new BackgroundTaskManager(); } return BackgroundTaskManager.instance; } /** * 初始化后台任务管理器 */ async initialize(): Promise { if (this.isInitialized) { console.log('后台任务管理器已初始化'); return; } try { // 定义后台任务 TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, async (_body: TaskManagerTaskBody) => { try { log.info(`[BackgroundTask] 后台任务执行, 任务 ID: ${BACKGROUND_TASK_IDENTIFIER}`); await executeBackgroundTasks(); } catch (error) { console.error('[BackgroundTask] 任务执行失败:', error); return BackgroundTask.BackgroundTaskResult.Failed; } return BackgroundTask.BackgroundTaskResult.Success; }); if (await TaskManager.isTaskRegisteredAsync(BACKGROUND_TASK_IDENTIFIER)) { log.info('[BackgroundTask] 任务已注册'); } else { log.info('[BackgroundTask] 任务未注册,开始注册...'); await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER); log.info('[BackgroundTask] 任务注册完成'); } // 验证任务状态 const status = await BackgroundTask.getStatusAsync(); log.info(`[BackgroundTask] 任务状态: ${status}`); this.isInitialized = true; log.info('后台任务管理器初始化完成'); } catch (error) { console.error('初始化后台任务管理器失败:', error); throw error; } } /** * 停止后台任务 */ async stop(): Promise { try { await BackgroundTask.unregisterTaskAsync(BACKGROUND_TASK_IDENTIFIER); console.log('后台任务已停止'); } catch (error) { console.error('停止后台任务失败:', error); } } /** * 获取后台任务状态 */ async getStatus(): Promise { try { const status = await BackgroundTask.getStatusAsync(); return status || BackgroundTask.BackgroundTaskStatus.Restricted; } catch (error) { console.error('获取后台任务状态失败:', error); return BackgroundTask.BackgroundTaskStatus.Restricted; } } /** * 检查后台任务状态 */ async checkStatus(): Promise { const status = await this.getStatus(); switch (status) { case BackgroundTask.BackgroundTaskStatus.Available: return '可用'; case BackgroundTask.BackgroundTaskStatus.Restricted: return '受限制'; default: return '未知'; } } async triggerTaskForTesting(): Promise { await BackgroundTask.triggerTaskWorkerForTestingAsync(); } /** * 测试后台任务 */ async testBackgroundTask(): Promise { console.log('开始测试后台任务...'); try { // 手动触发后台任务执行 await executeBackgroundTasks(); console.log('后台任务测试完成'); } catch (error) { console.error('后台任务测试失败:', error); } } /** * 获取最后一次后台检查时间 */ async getLastBackgroundCheckTime(): Promise { try { const lastCheck = await AsyncStorage.getItem('@last_background_water_check'); return lastCheck ? parseInt(lastCheck) : null; } catch (error) { console.error('获取最后后台检查时间失败:', error); return null; } } } /** * 后台任务事件类型 */ export interface BackgroundTaskEvent { taskId: string; timestamp: number; success: boolean; error?: string; }