Files
kitchen/docs/audit/crash_risk_analysis.md
2026-04-11 22:55:27 +08:00

9.8 KiB
Raw Blame History

代码闪退风险分析报告

分析日期: 2026-04-11 修复日期: 2026-04-11 分析范围: 全项目代码审查 严重程度: 🔴 高危 🟡 中危 🟢 低危


修复进度

已修复 (P0)

  • WeeklyMenuController 全局注册
  • BedtimeReminderController 全局注册
  • 移除页面中的重复 Get.put 调用
  • HiveService 添加 box 缓存机制
  • BedtimeReminderController 添加时间有效性检查

⏸️ 待修复 (P2)

  • 本地通知功能实现(需添加 flutter_local_notifications 依赖)

一、Controller 重复注册风险 🔴 高危

问题描述

多个页面使用 Get.put() 直接注册控制器,可能导致重复注册或生命周期混乱。

风险代码位置

1. WeeklyMenuController 重复注册

文件: lib/src/pages/tools/weekly_menu_planner_page.dart:26

final WeeklyMenuController _controller = Get.put(WeeklyMenuController());

风险:

  • 该控制器未在 AppBinding 中全局注册
  • 每次进入页面都会重新创建实例
  • 如果页面被多次推入,可能导致多个实例共存
  • Hive 数据可能被不同实例覆盖

建议修复:

// 方案1: 在 AppBinding 中全局注册
// lib/src/app_binding.dart
Get.put(WeeklyMenuController(), permanent: true);

// 方案2: 使用 Get.lazyPut + fenix
class WeeklyMenuBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => WeeklyMenuController(), fenix: true);
  }
}

2. BedtimeReminderController 重复注册

文件: lib/src/pages/profile/bedtime_reminder_page.dart:19

final controller = Get.put(BedtimeReminderController());

风险: 同上

建议修复:

// 在 AppBinding 中全局注册
// lib/src/app_binding.dart
Get.put(BedtimeReminderController(), permanent: true);

3. MealRecordController 重复注册

文件: lib/src/pages/home/home_page.dart:53

Get.put(MealRecordController(), permanent: true);

风险:

  • 该控制器已在 NutritionBinding 中注册(带检查)
  • 这里再次注册可能导致重复实例

建议修复: 移除 home_page.dart 中的注册,依赖 NutritionBinding 的注册逻辑


二、HiveService 通用方法风险 🔴 高危

问题描述

新添加的通用 get()put() 方法直接使用 Hive.box<dynamic>(boxName) 打开 box缺少安全检查。

风险代码位置

文件: lib/src/services/data/hive_service.dart:449-473

dynamic get(String boxName, String key) {
  if (!_initialized) return null;
  try {
    final box = Hive.box<dynamic>(boxName);  // ⚠️ box 可能不存在
    return box.get(key);
  } catch (e) {
    LoggerService().error('HiveService get error ($boxName/$key): $e');
    return null;
  }
}

风险:

  • 如果 box 不存在,Hive.box<dynamic>(boxName) 会抛出异常
  • 虽然有 try-catch但每次访问都会尝试打开 box性能差
  • 多个并发访问可能导致 box 重复打开

建议修复:

// 添加 box 缓存机制
final Map<String, Box<dynamic>> _dynamicBoxCache = {};

Box<dynamic> _getOrOpenBox(String boxName) {
  if (_dynamicBoxCache.containsKey(boxName)) {
    return _dynamicBoxCache[boxName]!;
  }
  
  try {
    final box = Hive.box<dynamic>(boxName);
    _dynamicBoxCache[boxName] = box;
    return box;
  } catch (e) {
    LoggerService().error('Failed to open box $boxName: $e');
    rethrow;
  }
}

dynamic get(String boxName, String key) {
  if (!_initialized) return null;
  try {
    final box = _getOrOpenBox(boxName);
    return box.get(key);
  } catch (e) {
    LoggerService().error('HiveService get error ($boxName/$key): $e');
    return null;
  }
}

// 在 close() 方法中清理缓存
Future<void> close() async {
  // ... 现有代码 ...
  for (final box in _dynamicBoxCache.values) {
    await box.close();
  }
  _dynamicBoxCache.clear();
  _initialized = false;
}

三、空指针风险 🟡 中危

问题描述

多处代码缺少空值检查,可能导致空指针异常。

风险代码位置

1. WeeklyMenuController 数据访问

文件: lib/src/controllers/weekly_menu_controller.dart:111-114

void addMealToDay(String dateKey, String mealType, RecipeModel recipe) {
  if (currentMenu.value == null) return;

  final dayMenu = currentMenu.value!.dailyMenus[dateKey];
  if (dayMenu == null) return;  // ✅ 有检查
  // ...
}

评价: 此处有检查,但其他类似方法可能遗漏

2. BedtimeReminderController 时间计算

文件: lib/src/controllers/bedtime_reminder_controller.dart:169-187

bool shouldShowBeforeSleepEatingWarning() {
  if (!beforeSleepEatingReminder.value) return false;

  final now = DateTime.now();
  final dinnerTime = DateTime(
    now.year,
    now.month,
    now.day,
    dinnerHour.value,
    dinnerMinute.value,
  );
  // ⚠️ 如果 dinnerHour/dinnerMinute 超出有效范围(如 25:70DateTime 构造会抛异常
  // ...
}

建议修复:

bool shouldShowBeforeSleepEatingWarning() {
  if (!beforeSleepEatingReminder.value) return false;

  // 添加时间有效性检查
  if (dinnerHour.value < 0 || dinnerHour.value > 23 ||
      dinnerMinute.value < 0 || dinnerMinute.value > 59) {
    debugPrint('Invalid dinner time: ${dinnerHour.value}:${dinnerMinute.value}');
    return false;
  }

  final now = DateTime.now();
  final dinnerTime = DateTime(
    now.year,
    now.month,
    now.day,
    dinnerHour.value,
    dinnerMinute.value,
  );
  // ...
}

四、TODO 未完成功能 🟡 中危

问题描述

代码中存在 TODO 标记的功能未实现,可能导致功能不完整。

未完成功能

1. 本地通知功能

文件: lib/src/controllers/bedtime_reminder_controller.dart:126-129

if (enabled) {
  ToastService.show(message: '已启用就寝提醒 🔔');
  // TODO: 实际设置本地通知
} else {
  ToastService.show(message: '已关闭就寝提醒 🔕');
  // TODO: 取消本地通知
}

影响:

  • 用户启用提醒后不会收到实际通知
  • 用户体验不完整

建议实现:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class BedtimeReminderController extends BaseController {
  final FlutterLocalNotificationsPlugin _notifications = 
      FlutterLocalNotificationsPlugin();

  @override
  void onInit() {
    super.onInit();
    _initNotifications();
    _loadSettings();
    _calculateRecommendedBedtime();
  }

  Future<void> _initNotifications() async {
    const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings();
    const settings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );
    await _notifications.initialize(settings);
  }

  Future<void> _scheduleNotification() async {
    final now = DateTime.now();
    final bedtime = DateTime(
      now.year,
      now.month,
      now.day,
      recommendedBedtimeHour.value,
      recommendedBedtimeMinute.value,
    );
    
    if (bedtime.isBefore(now)) {
      bedtime = bedtime.add(Duration(days: 1));
    }

    await _notifications.zonedSchedule(
      'bedtime_reminder',
      '就寝提醒',
      controller.bedtimeRecommendation,
      tz.TZDateTime.from(bedtime, tz.local),
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'bedtime_reminder_channel',
          '就寝提醒',
          importance: Importance.high,
        ),
        iOS: DarwinNotificationDetails(),
      ),
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
    );
  }

  void toggleReminder(bool enabled) {
    reminderEnabled.value = enabled;
    _saveSettings();

    if (enabled) {
      ToastService.show(message: '已启用就寝提醒 🔔');
      _scheduleNotification();
    } else {
      ToastService.show(message: '已关闭就寝提醒 🔕');
      _notifications.cancel('bedtime_reminder');
    }
  }
}

五、代码不足与改进建议 🟢 低危

1. 缺少输入验证

问题: 用户输入的时间、数值等缺少范围验证

建议:

  • 在 Controller 中添加数据验证方法
  • 在 UI 层添加输入限制(如 CupertinoPicker 的范围)
  • 添加错误提示

2. 缺少日志记录

问题: 关键操作缺少日志记录,不利于问题排查

建议:

  • 在数据保存/加载时添加日志
  • 在异常处理时记录详细错误信息
  • 使用 LoggerService 替代 debugPrint

3. 缺少数据迁移机制

问题: Hive 数据结构变更时缺少迁移机制

建议:

  • 为每个 box 添加 schema 版本号
  • 实现数据迁移逻辑
  • 参考现有的 _runMigrations() 方法扩展

4. 缺少单元测试

问题: 关键业务逻辑缺少单元测试

建议:

  • 为 Controller 添加单元测试
  • 为 Service 添加单元测试
  • 为工具类添加单元测试

六、优先级修复建议

🔴 立即修复P0

  1. Controller 重复注册问题 - 可能导致数据丢失和内存泄漏
  2. HiveService 通用方法风险 - 可能导致数据读写失败

🟡 尽快修复P1

  1. 空指针风险 - 添加时间有效性检查
  2. 本地通知功能实现 - 完善用户体验

🟢 计划修复P2

  1. 输入验证 - 提升代码健壮性
  2. 日志记录 - 便于问题排查
  3. 单元测试 - 保证代码质量

七、总结

本次代码审查发现的主要问题:

  1. Controller 管理混乱 - 多处重复注册,需要统一管理
  2. HiveService 安全性不足 - 通用方法缺少 box 缓存和错误处理
  3. 功能不完整 - 本地通知等 TODO 未实现
  4. 缺少防御性编程 - 输入验证、空值检查不足

建议优先修复 P0 和 P1 级别问题,然后逐步完善 P2 级别改进。