import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import '../models/data_models.dart'; /// 数据库帮助类 - 管理数据库的创建和版本控制 class DatabaseHelper { static final DatabaseHelper instance = DatabaseHelper._init(); static Database? _database; DatabaseHelper._init(); /// 数据库文件路径 Future get databasePath async { final path = await getDatabasesPath(); return join(path, 'mooknote.db'); } /// 重新打开数据库(用于 WebDAV 同步后) Future reopenDatabase() async { // 关闭现有连接 if (_database != null) { await _database!.close(); _database = null; } // 重新初始化 _database = await _initDB('mooknote.db'); } Future get database async { if (_database != null) return _database!; _database = await _initDB('mooknote.db'); return _database!; } Future _initDB(String filePath) async { final dbPath = await getDatabasesPath(); final path = join(dbPath, filePath); return await openDatabase( path, version: 13, onCreate: _createDB, onUpgrade: _onUpgrade, ); } /// 数据库升级 Future _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { // 升级movies表结构 await _upgradeMoviesTableV2(db); } if (oldVersion < 3) { // 升级books表结构 await _upgradeBooksTableV3(db); } if (oldVersion < 4) { // 升级notes表结构 await _upgradeNotesTableV4(db); } if (oldVersion < 5) { // 创建影评表和海报墙表 await _createMovieReviewsTable(db); await _createMoviePostersTable(db); } if (oldVersion < 6) { // 为笔记表添加软删除字段 await _upgradeNotesTableV6(db); } if (oldVersion < 7) { // 创建书评表和摘抄表 await _createBookReviewsTable(db); await _createBookExcerptsTable(db); } if (oldVersion < 8) { // 确保书评表和摘抄表存在(兼容之前版本未成功创建的情况) await _createBookReviewsTable(db); await _createBookExcerptsTable(db); } if (oldVersion < 9) { // 为笔记表添加图片字段 await _upgradeNotesTableV9(db); } if (oldVersion < 10) { // 为影视表添加观看日期字段 await _upgradeMoviesTableV10(db); } if (oldVersion < 11) { // 为书籍表添加ISBN和出版时间字段 await _upgradeBooksTableV11(db); } if (oldVersion < 12) { // 确保notes表有title列 await _upgradeNotesTableV12(db); } if (oldVersion < 13) { await _upgradeToV13(db); } } /// 升级books表到V11(添加ISBN和出版时间字段) Future _upgradeBooksTableV11(Database db) async { // 检查是否存在 isbn 列 final columns = await db.rawQuery('PRAGMA table_info(books)'); final hasIsbn = columns.any((col) => col['name'] == 'isbn'); final hasPublishDate = columns.any((col) => col['name'] == 'publish_date'); if (!hasIsbn) { await db.execute('ALTER TABLE books ADD COLUMN isbn TEXT'); } if (!hasPublishDate) { await db.execute('ALTER TABLE books ADD COLUMN publish_date TEXT'); } } /// 升级movies表到V10(添加观看日期字段) Future _upgradeMoviesTableV10(Database db) async { // 检查是否存在 watch_date 列 final columns = await db.rawQuery('PRAGMA table_info(movies)'); final hasWatchDate = columns.any((col) => col['name'] == 'watch_date'); if (!hasWatchDate) { await db.execute('ALTER TABLE movies ADD COLUMN watch_date TEXT'); } } /// 升级notes表到V12(确保title列存在) Future _upgradeNotesTableV12(Database db) async { // 检查是否存在 title 列 final columns = await db.rawQuery('PRAGMA table_info(notes)'); final hasTitle = columns.any((col) => col['name'] == 'title'); if (!hasTitle) { await db.execute('ALTER TABLE notes ADD COLUMN title TEXT DEFAULT \'\''); } } /// 升级到V13:创建标签表并回填已有数据 Future _upgradeToV13(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS tags ( id TEXT PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(name, type) ) '''); await _backfillTags(db); } Future _backfillTags(Database db) async { final now = DateTime.now().toIso8601String(); int counter = 0; Future insertTag(String name, String type) async { try { await db.insert('tags', { 'id': 'tag_${DateTime.now().millisecondsSinceEpoch}_${counter++}', 'name': name, 'type': type, 'created_at': now, }); } catch (_) { // 忽略 UNIQUE 约束冲突 } } // 回填影视类型 final movies = await db.query('movies', where: 'genres IS NOT NULL AND genres != ?', whereArgs: ['[]']); for (final row in movies) { for (final genre in Movie.parseStringList(row['genres'])) { await insertTag(genre, 'movie_genre'); } } // 回填书籍类型 final books = await db.query('books', where: 'genres IS NOT NULL AND genres != ?', whereArgs: ['[]']); for (final row in books) { for (final genre in Movie.parseStringList(row['genres'])) { await insertTag(genre, 'book_genre'); } } // 回填笔记标签 final notes = await db.query('notes', where: 'tags IS NOT NULL AND tags != ? AND tags != ?', whereArgs: ['[]', '']); for (final row in notes) { for (final tag in Movie.parseStringList(row['tags'])) { await insertTag(tag, 'note_tag'); } } } /// 升级notes表到V9(添加图片字段) Future _upgradeNotesTableV9(Database db) async { // 检查是否存在 images 列 final columns = await db.rawQuery('PRAGMA table_info(notes)'); final hasImages = columns.any((col) => col['name'] == 'images'); if (!hasImages) { await db.execute('ALTER TABLE notes ADD COLUMN images TEXT'); } } /// 升级notes表到V6(添加软删除字段) Future _upgradeNotesTableV6(Database db) async { // 检查是否存在 is_deleted 列 final columns = await db.rawQuery('PRAGMA table_info(notes)'); final hasIsDeleted = columns.any((col) => col['name'] == 'is_deleted'); if (!hasIsDeleted) { await db.execute('ALTER TABLE notes ADD COLUMN is_deleted INTEGER DEFAULT 0'); } } /// 创建影评表 Future _createMovieReviewsTable(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS movie_reviews ( id TEXT PRIMARY KEY, movie_id TEXT NOT NULL, content TEXT NOT NULL, reviewer TEXT, source TEXT, review_type INTEGER DEFAULT 1, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (movie_id) REFERENCES movies (id) ) '''); } /// 创建影视海报墙表 Future _createMoviePostersTable(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS movie_posters ( id TEXT PRIMARY KEY, movie_id TEXT NOT NULL, poster_path TEXT NOT NULL, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (movie_id) REFERENCES movies (id) ) '''); } /// 创建书评表 Future _createBookReviewsTable(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS book_reviews ( id TEXT PRIMARY KEY, book_id TEXT NOT NULL, content TEXT NOT NULL, reviewer TEXT, source TEXT, review_type INTEGER DEFAULT 1, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (book_id) REFERENCES books (id) ) '''); } /// 创建书籍摘抄表 Future _createBookExcerptsTable(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS book_excerpts ( id TEXT PRIMARY KEY, book_id TEXT NOT NULL, chapter TEXT, content TEXT NOT NULL, comment TEXT, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (book_id) REFERENCES books (id) ) '''); } /// 升级notes表到V4 Future _upgradeNotesTableV4(Database db) async { // 备份旧数据 final oldData = await db.query('notes'); // 删除旧表 await db.execute('DROP TABLE IF EXISTS notes'); // 创建新表 await db.execute(''' CREATE TABLE notes ( id TEXT PRIMARY KEY, title TEXT DEFAULT '', content TEXT NOT NULL, content_type TEXT DEFAULT 'markdown', tags TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) '''); // 迁移旧数据(将title字段恢复) for (final row in oldData) { try { final now = DateTime.now().toIso8601String(); final title = row['title']?.toString() ?? ''; final content = row['content']?.toString() ?? ''; await db.insert('notes', { 'id': row['id']?.toString() ?? DateTime.now().millisecondsSinceEpoch.toString(), 'title': title, 'content': content, 'content_type': 'markdown', 'tags': row['tags'] ?? '', 'created_at': row['created_at']?.toString() ?? now, 'updated_at': row['updated_at']?.toString() ?? now, }); } catch (e) { // 忽略迁移失败的记录 } } } /// 升级books表到V3 Future _upgradeBooksTableV3(Database db) async { // 备份旧数据 final oldData = await db.query('books'); // 删除旧表 await db.execute('DROP TABLE IF EXISTS books'); // 创建新表 await db.execute(''' CREATE TABLE books ( id TEXT PRIMARY KEY, title TEXT NOT NULL, cover_path TEXT, authors TEXT, alternate_titles TEXT, publisher TEXT, genres TEXT, summary TEXT, rating REAL, status TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, is_deleted INTEGER DEFAULT 0 ) '''); // 迁移旧数据 for (final row in oldData) { try { final now = DateTime.now().toIso8601String(); await db.insert('books', { 'id': row['id']?.toString() ?? DateTime.now().millisecondsSinceEpoch.toString(), 'title': row['title']?.toString() ?? '', 'cover_path': row['cover'], 'authors': row['author'] != null ? '["${row['author']}"]' : '[]', 'alternate_titles': '[]', 'publisher': null, 'genres': '[]', 'summary': row['note'], 'rating': row['rating'], 'status': row['status'] ?? 'want_to_read', 'created_at': now, 'updated_at': now, 'is_deleted': 0, }); } catch (e) { // 忽略迁移失败的记录 } } } /// 升级movies表到V2 Future _upgradeMoviesTableV2(Database db) async { // 备份旧数据 final oldData = await db.query('movies'); // 删除旧表 await db.execute('DROP TABLE IF EXISTS movies'); // 创建新表 await db.execute(''' CREATE TABLE movies ( id TEXT PRIMARY KEY, title TEXT NOT NULL, poster_path TEXT, release_date TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, alternate_titles TEXT, summary TEXT, rating REAL, status TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, is_deleted INTEGER DEFAULT 0 ) '''); // 迁移旧数据(尽可能保留) for (final row in oldData) { try { await db.insert('movies', { 'id': row['id']?.toString() ?? DateTime.now().millisecondsSinceEpoch.toString(), 'title': row['title']?.toString() ?? '', 'poster_path': row['poster_path'], 'release_date': null, 'directors': '[]', 'writers': '[]', 'actors': '[]', 'genres': '[]', 'alternate_titles': '[]', 'summary': row['note'], 'rating': row['rating'], 'status': row['status'] ?? 'want_to_watch', 'created_at': row['created_at']?.toString() ?? DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), 'is_deleted': row['is_deleted'] ?? 0, }); } catch (e) { // 忽略迁移失败的记录 } } } // 创建数据库表 Future _createDB(Database db, int version) async { const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT'; const textType = 'TEXT NOT NULL'; const integerType = 'INTEGER NOT NULL'; const booleanType = 'INTEGER NOT NULL'; // 影视表 await db.execute(''' CREATE TABLE movies ( id TEXT PRIMARY KEY, title TEXT NOT NULL, poster_path TEXT, release_date TEXT, directors TEXT, writers TEXT, actors TEXT, genres TEXT, alternate_titles TEXT, summary TEXT, rating REAL, status TEXT NOT NULL, watch_date TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, is_deleted INTEGER DEFAULT 0 ) '''); // 书籍表 await db.execute(''' CREATE TABLE books ( id TEXT PRIMARY KEY, title TEXT NOT NULL, cover_path TEXT, authors TEXT, alternate_titles TEXT, publisher TEXT, genres TEXT, summary TEXT, rating REAL, status TEXT NOT NULL, isbn TEXT, publish_date TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, is_deleted INTEGER DEFAULT 0 ) '''); // 笔记表 await db.execute(''' CREATE TABLE notes ( id TEXT PRIMARY KEY, title TEXT DEFAULT '', content TEXT NOT NULL, content_type TEXT DEFAULT 'markdown', tags TEXT, images TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, is_deleted INTEGER DEFAULT 0 ) '''); // 影评表 await db.execute(''' CREATE TABLE movie_reviews ( id TEXT PRIMARY KEY, movie_id TEXT NOT NULL, content TEXT NOT NULL, reviewer TEXT, source TEXT, review_type INTEGER DEFAULT 1, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (movie_id) REFERENCES movies (id) ) '''); // 影视海报墙表 await db.execute(''' CREATE TABLE movie_posters ( id TEXT PRIMARY KEY, movie_id TEXT NOT NULL, poster_path TEXT NOT NULL, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (movie_id) REFERENCES movies (id) ) '''); // 书评表 await db.execute(''' CREATE TABLE book_reviews ( id TEXT PRIMARY KEY, book_id TEXT NOT NULL, content TEXT NOT NULL, reviewer TEXT, source TEXT, review_type INTEGER DEFAULT 1, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (book_id) REFERENCES books (id) ) '''); // 书籍摘抄表 await db.execute(''' CREATE TABLE book_excerpts ( id TEXT PRIMARY KEY, book_id TEXT NOT NULL, chapter TEXT, content TEXT NOT NULL, comment TEXT, is_deleted INTEGER DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (book_id) REFERENCES books (id) ) '''); // 标签表 await db.execute(''' CREATE TABLE IF NOT EXISTS tags ( id TEXT PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(name, type) ) '''); } // 关闭数据库 Future close() async { if (_database != null) { await _database!.close(); _database = null; } } // 重新打开(关闭后重新初始化) Future reopen() async { await close(); await database; } }