## 🎯 里程碑完成:数据层架构建设 ### ✅ 数据持久化实现 - Hive数据库完整集成 - 依赖配置、初始化、TypeAdapter注册 - BookRepository数据访问层 - 完整CRUD操作实现 - 自动代码生成 - build_runner + hive_generator集成 - 数据持久化验证 - 应用启动时自动测试所有功能 ### 🏗️ 架构组件 - DatabaseService - 单例模式数据库管理服务 - BookRepository - Repository模式数据访问抽象层 - TypeAdapter - 自动生成对象序列化适配器 - 错误处理机制 - 完善的异常捕获和日志记录 ### 📊 代码成果 - 新增2个服务类文件 (database_service.dart, book_repository.dart) - 自动生成1个TypeAdapter文件 (book.g.dart) - 更新4个数据模型文件 (添加Hive注解) - 完善main.dart集成测试验证 - 新增1篇Hive详细教程文档 (06_Hive数据库数据持久化详解.md) ### 🧪 测试验证 - 数据库初始化测试 ✅ - CRUD操作完整测试 ✅ - 数据持久化验证 ✅ - TypeAdapter序列化测试 ✅ - 错误处理机制测试 ✅ ### 📚 文档完善 - 更新项目主文档 (CLAUDE.md) - 完整进度和成果统计 - 更新学习阶段总结 (05_数据模型设计阶段总结.md) - 新增Hive使用详解 (06_Hive数据库数据持久化详解.md) - 详细的代码示例和最佳实践指南 🚀 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
264 lines
6.9 KiB
Dart
264 lines
6.9 KiB
Dart
import 'package:hive/hive.dart';
|
||
part 'book.g.dart';
|
||
|
||
// 电子书数据模型
|
||
// 这是Flutter中数据模型的标准写法,后续其他模型都可以参考这个结构
|
||
|
||
/// 书籍文件格式枚举
|
||
/// 使用enum提供类型安全的枚举值
|
||
@HiveType(typeId: 0)
|
||
enum BookFormat {
|
||
@HiveField(0)
|
||
epub, // EPUB格式电子书
|
||
@HiveField(1)
|
||
mobi, // Kindle MOBI格式
|
||
@HiveField(2)
|
||
txt, // 纯文本格式
|
||
@HiveField(3)
|
||
pdf // PDF格式
|
||
}
|
||
|
||
/// 阅读状态枚举
|
||
@HiveType(typeId: 1)
|
||
enum ReadingStatus {
|
||
@HiveField(0)
|
||
reading, // 阅读中
|
||
@HiveField(1)
|
||
completed, // 已完成
|
||
@HiveField(2)
|
||
pending // 待阅读
|
||
}
|
||
|
||
/// 书籍数据模型
|
||
///
|
||
/// 设计原则:
|
||
/// 1. 所有字段都是final(不可变性)
|
||
/// 2. 提供copyWith方法创建新对象
|
||
/// 3. 提供序列化和反序列化方法
|
||
/// 4. 使用空值安全语法
|
||
@HiveType(typeId: 2)
|
||
class Book {
|
||
/// 唯一标识符 - 使用UUID确保全局唯一
|
||
@HiveField(0)
|
||
final String id;
|
||
|
||
/// 书名 - 必需字段
|
||
@HiveField(1)
|
||
final String title;
|
||
|
||
/// 作者 - 可为null(因为有些书籍可能没有作者信息)
|
||
@HiveField(2)
|
||
final String? author;
|
||
|
||
/// 出版社 - 可选字段
|
||
@HiveField(3)
|
||
final String? publisher;
|
||
|
||
/// 书籍简介 - 可选字段
|
||
@HiveField(4)
|
||
final String? description;
|
||
|
||
/// 封面图片路径 - 可选字段
|
||
@HiveField(5)
|
||
final String? coverImagePath;
|
||
|
||
/// 文件路径 - 必需字段,指向实际的电子书文件
|
||
@HiveField(6)
|
||
final String filePath;
|
||
|
||
/// 文件格式 - 必需字段
|
||
@HiveField(7)
|
||
final BookFormat format;
|
||
|
||
/// 文件大小(字节)- 必需字段
|
||
@HiveField(8)
|
||
final int fileSize;
|
||
|
||
/// 添加到书架的时间 - 必需字段
|
||
@HiveField(9)
|
||
final DateTime addedDate;
|
||
|
||
/// 阅读状态 - 必需字段
|
||
@HiveField(10)
|
||
final ReadingStatus status;
|
||
|
||
/// 评分(1-5星)- 可选字段
|
||
@HiveField(11)
|
||
final double? rating;
|
||
|
||
/// 标签列表 - 使用空列表作为默认值,避免null指针
|
||
@HiveField(12)
|
||
final List<String> tags;
|
||
|
||
/// 总页数 - 必需字段
|
||
@HiveField(13)
|
||
final int totalPages;
|
||
|
||
/// 构造函数
|
||
///
|
||
/// 使用required关键字标记必需字段,这是Flutter的空值安全特性
|
||
const Book({
|
||
required this.id, // 必需
|
||
required this.title, // 必需
|
||
this.author, // 可选
|
||
this.publisher, // 可选
|
||
this.description, // 可选
|
||
this.coverImagePath, // 可选
|
||
required this.filePath, // 必需
|
||
required this.format, // 必需
|
||
required this.fileSize, // 必需
|
||
required this.addedDate, // 必需
|
||
required this.status, // 必需
|
||
this.rating, // 可选
|
||
this.tags = const [], // 默认空列表,避免null
|
||
required this.totalPages, // 必需
|
||
});
|
||
|
||
/// copyWith方法 - 创建对象的副本
|
||
///
|
||
/// 这是Flutter中不可变对象的标准模式
|
||
/// 当需要修改对象时,不是直接修改原对象,而是创建一个新对象
|
||
///
|
||
/// 使用示例:
|
||
/// ```dart
|
||
/// final newBook = book.copyWith(
|
||
/// title: "新书名",
|
||
/// status: ReadingStatus.completed,
|
||
/// );
|
||
/// ```
|
||
Book copyWith({
|
||
String? id,
|
||
String? title,
|
||
String? author,
|
||
String? publisher,
|
||
String? description,
|
||
String? coverImagePath,
|
||
String? filePath,
|
||
BookFormat? format,
|
||
int? fileSize,
|
||
DateTime? addedDate,
|
||
ReadingStatus? status,
|
||
double? rating,
|
||
List<String>? tags,
|
||
int? totalPages,
|
||
}) {
|
||
return Book(
|
||
id: id ?? this.id, // 如果id不为null就使用新值,否则保持原值
|
||
title: title ?? this.title,
|
||
author: author ?? this.author,
|
||
publisher: publisher ?? this.publisher,
|
||
description: description ?? this.description,
|
||
coverImagePath: coverImagePath ?? this.coverImagePath,
|
||
filePath: filePath ?? this.filePath,
|
||
format: format ?? this.format,
|
||
fileSize: fileSize ?? this.fileSize,
|
||
addedDate: addedDate ?? this.addedDate,
|
||
status: status ?? this.status,
|
||
rating: rating ?? this.rating,
|
||
tags: tags ?? this.tags,
|
||
totalPages: totalPages ?? this.totalPages,
|
||
);
|
||
}
|
||
|
||
/// toMap方法 - 将对象转换为Map
|
||
///
|
||
/// 这个方法用于:
|
||
/// 1. 本地存储(如Hive数据库)
|
||
/// 2. 网络传输(转换为JSON)
|
||
/// 3. 状态保存
|
||
Map<String, dynamic> toMap() {
|
||
return {
|
||
'id': id,
|
||
'title': title,
|
||
'author': author,
|
||
'publisher': publisher,
|
||
'description': description,
|
||
'coverImagePath': coverImagePath,
|
||
'filePath': filePath,
|
||
'format': format.name, // 枚举值转换为字符串
|
||
'fileSize': fileSize,
|
||
'addedDate': addedDate.toIso8601String(), // DateTime转换为字符串
|
||
'status': status.name,
|
||
'rating': rating,
|
||
'tags': tags, // List直接存储
|
||
'totalPages': totalPages,
|
||
};
|
||
}
|
||
|
||
/// fromMap构造函数 - 从Map创建Book对象
|
||
///
|
||
/// 这是toMap的逆操作,用于:
|
||
/// 1. 从本地存储中读取数据
|
||
/// 2. 从网络请求中解析数据
|
||
/// 3. 从保存的状态中恢复对象
|
||
factory Book.fromMap(Map<String, dynamic> map) {
|
||
return Book(
|
||
id: map['id'],
|
||
title: map['title'],
|
||
author: map['author'],
|
||
publisher: map['publisher'],
|
||
description: map['description'],
|
||
coverImagePath: map['coverImagePath'],
|
||
filePath: map['filePath'],
|
||
format: BookFormat.values.firstWhere(
|
||
// 字符串转换回枚举
|
||
(e) => e.name == map['format'],
|
||
),
|
||
fileSize: map['fileSize'],
|
||
addedDate: DateTime.parse(map['addedDate']),
|
||
status: ReadingStatus.values.firstWhere(
|
||
(e) => e.name == map['status'],
|
||
),
|
||
rating: map['rating'],
|
||
tags: List<String>.from(map['tags'] ?? []), // 确保返回List<String>
|
||
totalPages: map['totalPages'],
|
||
);
|
||
}
|
||
|
||
/// 创建一个新Book实例的工厂方法
|
||
///
|
||
/// 这是一个便利方法,用于创建基本的Book对象
|
||
/// 通常在用户导入新书籍时使用
|
||
factory Book.create({
|
||
required String title,
|
||
required String filePath,
|
||
required BookFormat format,
|
||
required int fileSize,
|
||
String? author,
|
||
int totalPages = 0,
|
||
}) {
|
||
return Book(
|
||
id: _generateId(), // 生成唯一ID
|
||
title: title,
|
||
author: author,
|
||
filePath: filePath,
|
||
format: format,
|
||
fileSize: fileSize,
|
||
totalPages: totalPages,
|
||
addedDate: DateTime.now(), // 当前时间作为添加时间
|
||
status: ReadingStatus.pending, // 默认状态为待阅读
|
||
);
|
||
}
|
||
|
||
/// 生成唯一ID的私有方法
|
||
static String _generateId() {
|
||
return '${DateTime.now().millisecondsSinceEpoch}_${(DateTime.now().microsecond).toString()}';
|
||
}
|
||
|
||
/// 重写toString方法,便于调试
|
||
@override
|
||
String toString() {
|
||
return 'Book(id: $id, title: $title, author: $author, format: $format)';
|
||
}
|
||
|
||
/// 重写equals和hashCode,用于对象比较
|
||
@override
|
||
bool operator ==(Object other) {
|
||
if (identical(this, other)) return true;
|
||
return other is Book && other.id == id;
|
||
}
|
||
|
||
@override
|
||
int get hashCode => id.hashCode;
|
||
}
|