feat(background-task): 完善iOS后台任务系统并优化断食通知和UI体验
- 修复iOS后台任务注册时机问题,确保任务能正常触发 - 添加后台任务调试辅助工具和完整测试指南 - 优化断食通知系统,增加防抖机制避免频繁重调度 - 改进断食自动续订逻辑,使用固定时间而非相对时间计算 - 优化统计页面布局,添加身体指标section标题 - 增强饮水详情页面视觉效果,改进卡片样式和配色 - 添加用户反馈入口到个人设置页面 - 完善锻炼摘要卡片条件渲染逻辑 - 增强日志记录和错误处理机制 这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
This commit is contained in:
308
services/backgroundTaskDebugHelper.ts
Normal file
308
services/backgroundTaskDebugHelper.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* 后台任务调试辅助工具
|
||||
*
|
||||
* 用于在开发和测试阶段验证后台任务配置和执行情况
|
||||
*/
|
||||
|
||||
import { logger } from '@/utils/logger';
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
import { BackgroundTaskManager } from './backgroundTaskManagerV2';
|
||||
|
||||
const NativeBackgroundModule = NativeModules.BackgroundTaskBridge;
|
||||
|
||||
export class BackgroundTaskDebugHelper {
|
||||
private static instance: BackgroundTaskDebugHelper;
|
||||
|
||||
static getInstance(): BackgroundTaskDebugHelper {
|
||||
if (!BackgroundTaskDebugHelper.instance) {
|
||||
BackgroundTaskDebugHelper.instance = new BackgroundTaskDebugHelper();
|
||||
}
|
||||
return BackgroundTaskDebugHelper.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行完整的后台任务诊断
|
||||
*/
|
||||
async runFullDiagnostics(): Promise<DiagnosticsReport> {
|
||||
logger.info('[BackgroundTaskDebug] ====== 开始后台任务诊断 ======');
|
||||
|
||||
const report: DiagnosticsReport = {
|
||||
platform: Platform.OS,
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: {},
|
||||
};
|
||||
|
||||
// 1. 检查平台支持
|
||||
report.checks.platformSupport = this.checkPlatformSupport();
|
||||
|
||||
// 2. 检查原生模块
|
||||
report.checks.nativeModule = await this.checkNativeModule();
|
||||
|
||||
// 3. 检查后台刷新权限
|
||||
report.checks.backgroundRefresh = await this.checkBackgroundRefreshStatus();
|
||||
|
||||
// 4. 检查待处理任务
|
||||
report.checks.pendingTasks = await this.checkPendingTasks();
|
||||
|
||||
// 5. 检查配置
|
||||
report.checks.configuration = this.checkConfiguration();
|
||||
|
||||
// 6. 检查最后执行时间
|
||||
report.checks.lastExecution = await this.checkLastExecution();
|
||||
|
||||
logger.info('[BackgroundTaskDebug] ====== 诊断完成 ======');
|
||||
logger.info('[BackgroundTaskDebug] 报告:', JSON.stringify(report, null, 2));
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查平台支持
|
||||
*/
|
||||
private checkPlatformSupport(): CheckResult {
|
||||
const isIOS = Platform.OS === 'ios';
|
||||
return {
|
||||
status: isIOS ? 'success' : 'error',
|
||||
message: isIOS
|
||||
? 'iOS 平台支持后台任务'
|
||||
: `当前平台 (${Platform.OS}) 不支持后台任务`,
|
||||
details: {
|
||||
platform: Platform.OS,
|
||||
version: Platform.Version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查原生模块
|
||||
*/
|
||||
private async checkNativeModule(): Promise<CheckResult> {
|
||||
if (!NativeBackgroundModule) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: '原生模块 BackgroundTaskBridge 不可用',
|
||||
details: {
|
||||
available: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试调用一个简单的方法来验证模块是否正常工作
|
||||
const status = await NativeBackgroundModule.backgroundRefreshStatus();
|
||||
return {
|
||||
status: 'success',
|
||||
message: '原生模块可用且正常工作',
|
||||
details: {
|
||||
available: true,
|
||||
backgroundRefreshStatus: status,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: '原生模块存在但调用失败',
|
||||
details: {
|
||||
available: true,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查后台刷新权限状态
|
||||
*/
|
||||
private async checkBackgroundRefreshStatus(): Promise<CheckResult> {
|
||||
try {
|
||||
const manager = BackgroundTaskManager.getInstance();
|
||||
const status = await manager.getStatus();
|
||||
const statusText = await manager.checkStatus();
|
||||
|
||||
let resultStatus: 'success' | 'warning' | 'error';
|
||||
let message: string;
|
||||
|
||||
switch (status) {
|
||||
case 'available':
|
||||
resultStatus = 'success';
|
||||
message = '后台刷新权限已启用';
|
||||
break;
|
||||
case 'denied':
|
||||
resultStatus = 'error';
|
||||
message = '后台刷新被拒绝,请在设置中启用';
|
||||
break;
|
||||
case 'restricted':
|
||||
resultStatus = 'error';
|
||||
message = '后台刷新被限制(可能是家长控制)';
|
||||
break;
|
||||
default:
|
||||
resultStatus = 'warning';
|
||||
message = `后台刷新状态未知: ${status}`;
|
||||
}
|
||||
|
||||
return {
|
||||
status: resultStatus,
|
||||
message,
|
||||
details: {
|
||||
status,
|
||||
statusText,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: '检查后台刷新状态失败',
|
||||
details: {
|
||||
error: (error as Error).message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查待处理的任务
|
||||
*/
|
||||
private async checkPendingTasks(): Promise<CheckResult> {
|
||||
try {
|
||||
const manager = BackgroundTaskManager.getInstance();
|
||||
const pendingRequests = await manager.getPendingRequests();
|
||||
|
||||
return {
|
||||
status: pendingRequests.length > 0 ? 'success' : 'warning',
|
||||
message: pendingRequests.length > 0
|
||||
? `有 ${pendingRequests.length} 个待处理任务`
|
||||
: '没有待处理的任务(可能需要调度)',
|
||||
details: {
|
||||
count: pendingRequests.length,
|
||||
tasks: pendingRequests,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: '检查待处理任务失败',
|
||||
details: {
|
||||
error: (error as Error).message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置
|
||||
*/
|
||||
private checkConfiguration(): CheckResult {
|
||||
const config = {
|
||||
identifier: 'com.anonymous.digitalpilates.task',
|
||||
defaultDelay: 15 * 60, // 15分钟
|
||||
};
|
||||
|
||||
return {
|
||||
status: 'info',
|
||||
message: '后台任务配置',
|
||||
details: config,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查最后执行时间
|
||||
*/
|
||||
private async checkLastExecution(): Promise<CheckResult> {
|
||||
try {
|
||||
const manager = BackgroundTaskManager.getInstance();
|
||||
const lastCheckTime = await manager.getLastBackgroundCheckTime();
|
||||
|
||||
if (!lastCheckTime) {
|
||||
return {
|
||||
status: 'warning',
|
||||
message: '后台任务从未执行过',
|
||||
details: {
|
||||
lastExecution: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const timeSinceLastCheck = Date.now() - lastCheckTime;
|
||||
const hoursSinceLastCheck = timeSinceLastCheck / (1000 * 60 * 60);
|
||||
|
||||
return {
|
||||
status: hoursSinceLastCheck > 24 ? 'warning' : 'success',
|
||||
message: hoursSinceLastCheck > 24
|
||||
? `距离上次执行已超过24小时 (${hoursSinceLastCheck.toFixed(1)}小时)`
|
||||
: `上次执行时间: ${new Date(lastCheckTime).toLocaleString()}`,
|
||||
details: {
|
||||
lastExecution: lastCheckTime,
|
||||
timeSinceLastCheck: `${hoursSinceLastCheck.toFixed(1)} 小时`,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: '检查最后执行时间失败',
|
||||
details: {
|
||||
error: (error as Error).message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成可读的诊断报告
|
||||
*/
|
||||
generateReadableReport(report: DiagnosticsReport): string {
|
||||
let output = '\n========== 后台任务诊断报告 ==========\n';
|
||||
output += `时间: ${new Date(report.timestamp).toLocaleString()}\n`;
|
||||
output += `平台: ${report.platform}\n`;
|
||||
output += '\n';
|
||||
|
||||
Object.entries(report.checks).forEach(([key, check]) => {
|
||||
const icon = check.status === 'success' ? '✅' : check.status === 'error' ? '❌' : '⚠️';
|
||||
output += `${icon} ${key}: ${check.message}\n`;
|
||||
if (check.details && Object.keys(check.details).length > 0) {
|
||||
output += ` 详情: ${JSON.stringify(check.details, null, 2)}\n`;
|
||||
}
|
||||
output += '\n';
|
||||
});
|
||||
|
||||
output += '=====================================\n';
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟触发后台任务(仅用于测试)
|
||||
*/
|
||||
async triggerTestTask(): Promise<void> {
|
||||
logger.info('[BackgroundTaskDebug] 触发测试任务...');
|
||||
|
||||
try {
|
||||
const manager = BackgroundTaskManager.getInstance();
|
||||
await manager.triggerTaskForTesting();
|
||||
logger.info('[BackgroundTaskDebug] ✅ 测试任务执行完成');
|
||||
} catch (error: any) {
|
||||
const errorCode = error?.code || '';
|
||||
|
||||
if (errorCode === 'SIMULATOR_NOT_SUPPORTED') {
|
||||
logger.info('[BackgroundTaskDebug] ℹ️ 在模拟器上执行了后台任务逻辑');
|
||||
logger.info('[BackgroundTaskDebug] 模拟器不支持完整的后台任务调度');
|
||||
logger.info('[BackgroundTaskDebug] 这是正常的,请在真机上测试完整功能');
|
||||
} else {
|
||||
logger.error('[BackgroundTaskDebug] ❌ 测试任务执行失败', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface DiagnosticsReport {
|
||||
platform: string;
|
||||
timestamp: string;
|
||||
checks: {
|
||||
[key: string]: CheckResult;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CheckResult {
|
||||
status: 'success' | 'warning' | 'error' | 'info';
|
||||
message: string;
|
||||
details?: Record<string, any>;
|
||||
}
|
||||
@@ -339,37 +339,52 @@ export class BackgroundTaskManagerV2 {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('[BackgroundTaskManagerV2] ====== 开始初始化后台任务管理器 ======');
|
||||
|
||||
if (!isIosBackgroundModuleAvailable) {
|
||||
logger.warn('[BackgroundTaskManagerV2] iOS 原生后台模块不可用,跳过初始化');
|
||||
logger.warn('[BackgroundTaskManagerV2] Platform:', Platform.OS);
|
||||
this.isInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('[BackgroundTaskManagerV2] 原生模块可用,开始注册事件监听器');
|
||||
|
||||
const emitter = new NativeEventEmitter(NativeBackgroundModule);
|
||||
|
||||
this.eventSubscription = emitter.addListener(BACKGROUND_EVENT, (payload) => {
|
||||
logger.info('[BackgroundTaskManagerV2] 收到后台任务事件', payload);
|
||||
logger.info('[BackgroundTaskManagerV2] ✅ 收到后台任务执行事件', payload);
|
||||
this.handleBackgroundExecution();
|
||||
});
|
||||
|
||||
this.expirationSubscription = emitter.addListener(EXPIRATION_EVENT, (payload) => {
|
||||
logger.warn('[BackgroundTaskManagerV2] 后台任务在完成前即将过期', payload);
|
||||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台任务即将过期', payload);
|
||||
// 处理任务过期情况,确保重新调度
|
||||
this.handleTaskExpiration();
|
||||
});
|
||||
|
||||
logger.info('[BackgroundTaskManagerV2] 事件监听器注册完成');
|
||||
|
||||
try {
|
||||
// 检查后台刷新状态
|
||||
logger.info('[BackgroundTaskManagerV2] 检查后台刷新权限状态...');
|
||||
const status = await this.getStatus();
|
||||
logger.info('[BackgroundTaskManagerV2] 后台刷新状态:', status);
|
||||
|
||||
if (status === 'denied' || status === 'restricted') {
|
||||
logger.warn('[BackgroundTaskManagerV2] 后台刷新被限制或拒绝,后台任务可能无法正常工作');
|
||||
// 不抛出错误,但标记为未完全初始化
|
||||
if (status === 'denied') {
|
||||
logger.error('[BackgroundTaskManagerV2] ❌ 后台刷新被拒绝!');
|
||||
logger.error('[BackgroundTaskManagerV2] 请在 设置 > Out Live > 后台App刷新 中启用');
|
||||
this.isInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'restricted') {
|
||||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台刷新被限制(可能是家长控制)');
|
||||
this.isInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('[BackgroundTaskManagerV2] 配置后台任务...');
|
||||
await NativeBackgroundModule.configure({
|
||||
identifier: BACKGROUND_TASK_IDENTIFIER,
|
||||
taskType: 'refresh',
|
||||
@@ -377,16 +392,24 @@ export class BackgroundTaskManagerV2 {
|
||||
requiresExternalPower: false,
|
||||
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
|
||||
});
|
||||
|
||||
this.isInitialized = true;
|
||||
logger.info('[BackgroundTaskManagerV2] 已初始化并注册 iOS 后台任务');
|
||||
logger.info('[BackgroundTaskManagerV2] ✅ 后台任务配置成功');
|
||||
|
||||
// 立即调度一次后台任务
|
||||
logger.info('[BackgroundTaskManagerV2] 调度首次后台任务...');
|
||||
await this.scheduleNextTask();
|
||||
|
||||
// 检查待处理的任务请求
|
||||
const pendingRequests = await this.getPendingRequests();
|
||||
logger.info('[BackgroundTaskManagerV2] 当前待处理的任务请求数量:', pendingRequests.length);
|
||||
|
||||
if (pendingRequests.length > 0) {
|
||||
logger.info('[BackgroundTaskManagerV2] 待处理任务详情:', JSON.stringify(pendingRequests, null, 2));
|
||||
}
|
||||
|
||||
logger.info('[BackgroundTaskManagerV2] ====== 初始化完成 ======');
|
||||
|
||||
} catch (error: any) {
|
||||
// BGTaskSchedulerErrorDomain 错误码 1 表示后台任务功能不可用
|
||||
// 这在模拟器上是正常的,因为模拟器不完全支持后台任务
|
||||
@@ -395,7 +418,9 @@ export class BackgroundTaskManagerV2 {
|
||||
(errorMessage.includes('错误1') || errorMessage.includes('code 1'));
|
||||
|
||||
if (isBGTaskUnavailable) {
|
||||
logger.warn('[BackgroundTaskManagerV2] 后台任务功能在当前环境不可用(模拟器限制),将在真机上正常工作');
|
||||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台任务功能在当前环境不可用');
|
||||
logger.warn('[BackgroundTaskManagerV2] 这是模拟器的正常限制,在真机上会正常工作');
|
||||
logger.warn('[BackgroundTaskManagerV2] 建议:在真机上测试后台任务功能');
|
||||
this.removeListeners();
|
||||
this.isInitialized = false;
|
||||
// 不抛出错误,因为这是预期行为
|
||||
@@ -403,12 +428,15 @@ export class BackgroundTaskManagerV2 {
|
||||
}
|
||||
|
||||
// 其他错误情况,尝试恢复
|
||||
logger.error('[BackgroundTaskManagerV2] 初始化失败,尝试恢复', error);
|
||||
logger.error('[BackgroundTaskManagerV2] ❌ 初始化失败', error);
|
||||
logger.error('[BackgroundTaskManagerV2] 错误详情:', errorMessage);
|
||||
|
||||
try {
|
||||
// 尝试重新初始化一次
|
||||
logger.info('[BackgroundTaskManagerV2] 尝试恢复...');
|
||||
await this.attemptRecovery();
|
||||
logger.info('[BackgroundTaskManagerV2] ✅ 恢复成功');
|
||||
} catch (recoveryError) {
|
||||
logger.error('[BackgroundTaskManagerV2] 恢复失败,放弃初始化', recoveryError);
|
||||
logger.error('[BackgroundTaskManagerV2] ❌ 恢复失败,放弃初始化', recoveryError);
|
||||
this.removeListeners();
|
||||
throw error;
|
||||
}
|
||||
@@ -569,14 +597,41 @@ export class BackgroundTaskManagerV2 {
|
||||
|
||||
async triggerTaskForTesting(): Promise<void> {
|
||||
if (!isIosBackgroundModuleAvailable) {
|
||||
logger.info('[BackgroundTaskManagerV2] 原生模块不可用,直接执行后台任务逻辑');
|
||||
await executeBackgroundTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info('[BackgroundTaskManagerV2] 尝试模拟触发后台任务...');
|
||||
await NativeBackgroundModule.simulateLaunch();
|
||||
} catch (error) {
|
||||
logger.error('[BackgroundTaskManagerV2] 模拟后台任务触发失败', error);
|
||||
logger.info('[BackgroundTaskManagerV2] ✅ 模拟触发成功');
|
||||
} catch (error: any) {
|
||||
const errorMessage = error?.message || String(error);
|
||||
const errorCode = error?.code || '';
|
||||
|
||||
// 检查是否是模拟器不支持的错误
|
||||
if (errorCode === 'SIMULATOR_NOT_SUPPORTED') {
|
||||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 模拟器不支持后台任务');
|
||||
logger.warn('[BackgroundTaskManagerV2] 这是正常的限制,请在真机上测试');
|
||||
logger.info('[BackgroundTaskManagerV2] 作为替代,直接执行后台任务逻辑...');
|
||||
// 在模拟器上直接执行后台任务逻辑作为测试
|
||||
await executeBackgroundTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是监听器未注册的错误
|
||||
if (errorCode === 'NO_LISTENERS') {
|
||||
logger.warn('[BackgroundTaskManagerV2] ⚠️ JS 监听器未注册');
|
||||
logger.warn('[BackgroundTaskManagerV2] 可能是应用还未完全初始化');
|
||||
logger.info('[BackgroundTaskManagerV2] 尝试直接执行后台任务逻辑...');
|
||||
await executeBackgroundTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error('[BackgroundTaskManagerV2] ❌ 模拟后台任务触发失败', error);
|
||||
logger.error('[BackgroundTaskManagerV2] 错误代码:', errorCode);
|
||||
logger.error('[BackgroundTaskManagerV2] 错误信息:', errorMessage);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,16 +263,31 @@ const selectiveSyncNotifications = async ({
|
||||
const start = dayjs(schedule.startISO);
|
||||
const end = dayjs(schedule.endISO);
|
||||
|
||||
if (end.isBefore(now)) {
|
||||
if (end.isBefore(now.subtract(1, 'hour'))) {
|
||||
await clearFastingNotificationIds();
|
||||
return {};
|
||||
}
|
||||
|
||||
const updatedIds: FastingNotificationIds = { ...validIds };
|
||||
|
||||
// 先取消所有无效的旧通知,避免重复
|
||||
const invalidIds = Object.entries(storedIds).filter(
|
||||
([key, id]) => id && !Object.values(validIds).includes(id)
|
||||
);
|
||||
|
||||
for (const [_, id] of invalidIds) {
|
||||
if (id) {
|
||||
try {
|
||||
await notificationService.cancelNotification(id);
|
||||
} catch (error) {
|
||||
console.warn('取消无效通知失败', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 检查开始前30分钟通知
|
||||
const preStart = start.subtract(REMINDER_OFFSET_MINUTES, 'minute');
|
||||
if (preStart.isAfter(now) && !validIds.preStartId) {
|
||||
if (preStart.isAfter(now.add(1, 'minute')) && !validIds.preStartId) {
|
||||
try {
|
||||
const preStartId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -289,13 +304,14 @@ const selectiveSyncNotifications = async ({
|
||||
preStart.toDate()
|
||||
);
|
||||
updatedIds.preStartId = preStartId;
|
||||
console.log(`已安排断食开始前30分钟通知: ${preStart.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食开始前30分钟通知失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查开始时通知
|
||||
if (start.isAfter(now) && !validIds.startId) {
|
||||
if (start.isAfter(now.add(1, 'minute')) && !validIds.startId) {
|
||||
try {
|
||||
const startId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -312,6 +328,7 @@ const selectiveSyncNotifications = async ({
|
||||
start.toDate()
|
||||
);
|
||||
updatedIds.startId = startId;
|
||||
console.log(`已安排断食开始时通知: ${start.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食开始时通知失败', error);
|
||||
}
|
||||
@@ -319,7 +336,7 @@ const selectiveSyncNotifications = async ({
|
||||
|
||||
// 3. 检查结束前30分钟通知
|
||||
const preEnd = end.subtract(REMINDER_OFFSET_MINUTES, 'minute');
|
||||
if (preEnd.isAfter(now) && !validIds.preEndId) {
|
||||
if (preEnd.isAfter(now.add(1, 'minute')) && !validIds.preEndId) {
|
||||
try {
|
||||
const preEndId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -336,13 +353,14 @@ const selectiveSyncNotifications = async ({
|
||||
preEnd.toDate()
|
||||
);
|
||||
updatedIds.preEndId = preEndId;
|
||||
console.log(`已安排断食结束前30分钟通知: ${preEnd.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食结束前30分钟通知失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查结束时通知
|
||||
if (end.isAfter(now) && !validIds.endId) {
|
||||
if (end.isAfter(now.add(1, 'minute')) && !validIds.endId) {
|
||||
try {
|
||||
const endId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -359,6 +377,7 @@ const selectiveSyncNotifications = async ({
|
||||
end.toDate()
|
||||
);
|
||||
updatedIds.endId = endId;
|
||||
console.log(`已安排断食结束时通知: ${end.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食结束时通知失败', error);
|
||||
}
|
||||
@@ -398,8 +417,9 @@ export const verifyFastingNotifications = async ({
|
||||
const start = dayjs(schedule.startISO);
|
||||
const end = dayjs(schedule.endISO);
|
||||
|
||||
// 如果断食期已结束,应该清空所有通知
|
||||
if (end.isBefore(now)) {
|
||||
// 如果断食期已结束超过1小时,应该清空所有通知
|
||||
// 这样可以避免在自动续订过程中过早清空通知
|
||||
if (end.isBefore(now.subtract(1, 'hour'))) {
|
||||
if (Object.values(storedIds).some(id => id)) {
|
||||
await cancelNotificationIds(storedIds);
|
||||
await clearFastingNotificationIds();
|
||||
@@ -431,9 +451,15 @@ export const verifyFastingNotifications = async ({
|
||||
const validIds: FastingNotificationIds = {};
|
||||
|
||||
for (const expected of expectedNotifications) {
|
||||
// 跳过已过期的通知
|
||||
if (expected.time.isBefore(now)) {
|
||||
// 跳过已过期的通知(过期超过5分钟)
|
||||
if (expected.time.isBefore(now.subtract(5, 'minute'))) {
|
||||
if (expected.id) {
|
||||
// 取消已过期的通知
|
||||
try {
|
||||
await notificationService.cancelNotification(expected.id);
|
||||
} catch (error) {
|
||||
console.warn('取消过期通知失败', error);
|
||||
}
|
||||
needsResync = true;
|
||||
}
|
||||
continue;
|
||||
@@ -451,6 +477,20 @@ export const verifyFastingNotifications = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查通知时间是否匹配(容差1分钟)
|
||||
const notification = scheduledNotifications.find(n => n.identifier === expected.id);
|
||||
if (notification?.trigger && 'date' in notification.trigger) {
|
||||
const scheduledTime = dayjs(notification.trigger.date);
|
||||
const timeDiff = Math.abs(scheduledTime.diff(expected.time, 'minute'));
|
||||
|
||||
// 如果时间差异超过1分钟,说明需要重新安排
|
||||
if (timeDiff > 1) {
|
||||
console.log(`通知时间不匹配,需要重新安排: ${expected.type}, 期望: ${expected.time.format('HH:mm')}, 实际: ${scheduledTime.format('HH:mm')}`);
|
||||
needsResync = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 通知存在且有效
|
||||
switch (expected.type) {
|
||||
case 'pre_start':
|
||||
|
||||
Reference in New Issue
Block a user