详细页优化
This commit is contained in:
381
docs/audit/crash_risk_analysis.md
Normal file
381
docs/audit/crash_risk_analysis.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 代码闪退风险分析报告
|
||||
|
||||
> 分析日期: 2026-04-11
|
||||
> 修复日期: 2026-04-11
|
||||
> 分析范围: 全项目代码审查
|
||||
> 严重程度: 🔴 高危 🟡 中危 🟢 低危
|
||||
|
||||
---
|
||||
|
||||
## 修复进度
|
||||
|
||||
### ✅ 已修复 (P0)
|
||||
- [x] WeeklyMenuController 全局注册
|
||||
- [x] BedtimeReminderController 全局注册
|
||||
- [x] 移除页面中的重复 Get.put 调用
|
||||
- [x] HiveService 添加 box 缓存机制
|
||||
- [x] BedtimeReminderController 添加时间有效性检查
|
||||
|
||||
### ⏸️ 待修复 (P2)
|
||||
- [ ] 本地通知功能实现(需添加 flutter_local_notifications 依赖)
|
||||
|
||||
---
|
||||
|
||||
## 一、Controller 重复注册风险 🔴 高危
|
||||
|
||||
### 问题描述
|
||||
多个页面使用 `Get.put()` 直接注册控制器,可能导致重复注册或生命周期混乱。
|
||||
|
||||
### 风险代码位置
|
||||
|
||||
#### 1. WeeklyMenuController 重复注册
|
||||
**文件**: `lib/src/pages/tools/weekly_menu_planner_page.dart:26`
|
||||
```dart
|
||||
final WeeklyMenuController _controller = Get.put(WeeklyMenuController());
|
||||
```
|
||||
**风险**:
|
||||
- 该控制器未在 `AppBinding` 中全局注册
|
||||
- 每次进入页面都会重新创建实例
|
||||
- 如果页面被多次推入,可能导致多个实例共存
|
||||
- Hive 数据可能被不同实例覆盖
|
||||
|
||||
**建议修复**:
|
||||
```dart
|
||||
// 方案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`
|
||||
```dart
|
||||
final controller = Get.put(BedtimeReminderController());
|
||||
```
|
||||
**风险**: 同上
|
||||
|
||||
**建议修复**:
|
||||
```dart
|
||||
// 在 AppBinding 中全局注册
|
||||
// lib/src/app_binding.dart
|
||||
Get.put(BedtimeReminderController(), permanent: true);
|
||||
```
|
||||
|
||||
#### 3. MealRecordController 重复注册
|
||||
**文件**: `lib/src/pages/home/home_page.dart:53`
|
||||
```dart
|
||||
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`
|
||||
|
||||
```dart
|
||||
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 重复打开
|
||||
|
||||
**建议修复**:
|
||||
```dart
|
||||
// 添加 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`
|
||||
```dart
|
||||
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`
|
||||
```dart
|
||||
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:70),DateTime 构造会抛异常
|
||||
// ...
|
||||
}
|
||||
```
|
||||
**建议修复**:
|
||||
```dart
|
||||
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`
|
||||
```dart
|
||||
if (enabled) {
|
||||
ToastService.show(message: '已启用就寝提醒 🔔');
|
||||
// TODO: 实际设置本地通知
|
||||
} else {
|
||||
ToastService.show(message: '已关闭就寝提醒 🔕');
|
||||
// TODO: 取消本地通知
|
||||
}
|
||||
```
|
||||
**影响**:
|
||||
- 用户启用提醒后不会收到实际通知
|
||||
- 用户体验不完整
|
||||
|
||||
**建议实现**:
|
||||
```dart
|
||||
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)
|
||||
3. **空指针风险** - 添加时间有效性检查
|
||||
4. **本地通知功能实现** - 完善用户体验
|
||||
|
||||
### 🟢 计划修复(P2)
|
||||
5. **输入验证** - 提升代码健壮性
|
||||
6. **日志记录** - 便于问题排查
|
||||
7. **单元测试** - 保证代码质量
|
||||
|
||||
---
|
||||
|
||||
## 七、总结
|
||||
|
||||
本次代码审查发现的主要问题:
|
||||
|
||||
1. **Controller 管理混乱** - 多处重复注册,需要统一管理
|
||||
2. **HiveService 安全性不足** - 通用方法缺少 box 缓存和错误处理
|
||||
3. **功能不完整** - 本地通知等 TODO 未实现
|
||||
4. **缺少防御性编程** - 输入验证、空值检查不足
|
||||
|
||||
建议优先修复 P0 和 P1 级别问题,然后逐步完善 P2 级别改进。
|
||||
Reference in New Issue
Block a user