diff --git a/electron/main/database/core.ts b/electron/main/database/core.ts index f6a216e..4dbd198 100644 --- a/electron/main/database/core.ts +++ b/electron/main/database/core.ts @@ -121,8 +121,10 @@ export function openDatabase(sessionId: string, readonly = true): Database.Datab /** * 打开数据库并执行迁移(如果需要) * 用于需要写入的场景 + * @param sessionId 会话ID + * @param forceRepair 是否强制修复(即使版本号已是最新也重新执行迁移脚本) */ -export function openDatabaseWithMigration(sessionId: string): Database.Database | null { +export function openDatabaseWithMigration(sessionId: string, forceRepair = false): Database.Database | null { const dbPath = getDbPath(sessionId) if (!fs.existsSync(dbPath)) { return null @@ -132,7 +134,7 @@ export function openDatabaseWithMigration(sessionId: string): Database.Database db.pragma('journal_mode = WAL') // 执行迁移 - migrateDatabase(db) + migrateDatabase(db, forceRepair) return db } @@ -368,13 +370,19 @@ export function getDbDirectory(): string { /** * 检查是否有数据库需要迁移 - * @returns 需要迁移的数据库数量和最低版本 + * @returns 需要迁移的数据库数量、列表、最低版本和需要强制修复的列表 */ -export function checkMigrationNeeded(): { count: number; sessionIds: string[]; lowestVersion: number } { +export function checkMigrationNeeded(): { + count: number + sessionIds: string[] + lowestVersion: number + forceRepairIds: string[] +} { ensureDbDir() const dbDir = getDbDir() const files = fs.readdirSync(dbDir).filter((f) => f.endsWith('.db')) const needsMigrationList: string[] = [] + const forceRepairList: string[] = [] let lowestVersion = CURRENT_SCHEMA_VERSION for (const file of files) { @@ -385,18 +393,28 @@ export function checkMigrationNeeded(): { count: number; sessionIds: string[]; l const db = new Database(dbPath, { readonly: true }) db.pragma('journal_mode = WAL') + // 获取当前 schema_version + const metaTableInfo = db.prepare('PRAGMA table_info(meta)').all() as Array<{ name: string }> + const hasVersionColumn = metaTableInfo.some((col) => col.name === 'schema_version') + let dbVersion = 0 + if (hasVersionColumn) { + const result = db.prepare('SELECT schema_version FROM meta LIMIT 1').get() as + | { schema_version: number | null } + | undefined + dbVersion = result?.schema_version ?? 0 + } + + // 检查 message 表是否有 reply_to_message_id 列 + const messageTableInfo = db.prepare('PRAGMA table_info(message)').all() as Array<{ name: string }> + const hasReplyColumn = messageTableInfo.some((col) => col.name === 'reply_to_message_id') + if (needsMigration(db)) { needsMigrationList.push(sessionId) - // 获取这个数据库的版本 - const tableInfo = db.prepare('PRAGMA table_info(meta)').all() as Array<{ name: string }> - const hasVersionColumn = tableInfo.some((col) => col.name === 'schema_version') - let dbVersion = 0 - if (hasVersionColumn) { - const result = db.prepare('SELECT schema_version FROM meta LIMIT 1').get() as - | { schema_version: number | null } - | undefined - dbVersion = result?.schema_version ?? 0 - } + lowestVersion = Math.min(lowestVersion, dbVersion) + } else if (!hasReplyColumn) { + // 特殊情况:版本号已更新但列不存在,需要强制修复 + needsMigrationList.push(sessionId) + forceRepairList.push(sessionId) lowestVersion = Math.min(lowestVersion, dbVersion) } @@ -406,7 +424,7 @@ export function checkMigrationNeeded(): { count: number; sessionIds: string[]; l } } - return { count: needsMigrationList.length, sessionIds: needsMigrationList, lowestVersion } + return { count: needsMigrationList.length, sessionIds: needsMigrationList, lowestVersion, forceRepairIds: forceRepairList } } /** @@ -414,7 +432,8 @@ export function checkMigrationNeeded(): { count: number; sessionIds: string[]; l * @returns 迁移结果 */ export function migrateAllDatabases(): { success: boolean; migratedCount: number; error?: string } { - const { sessionIds } = checkMigrationNeeded() + const { sessionIds, forceRepairIds } = checkMigrationNeeded() + const forceRepairSet = new Set(forceRepairIds) if (sessionIds.length === 0) { return { success: true, migratedCount: 0 } @@ -424,7 +443,8 @@ export function migrateAllDatabases(): { success: boolean; migratedCount: number for (const sessionId of sessionIds) { try { - const db = openDatabaseWithMigration(sessionId) + const needsForceRepair = forceRepairSet.has(sessionId) + const db = openDatabaseWithMigration(sessionId, needsForceRepair) if (db) { db.close() migratedCount++ diff --git a/electron/main/database/migrations.ts b/electron/main/database/migrations.ts index 29c0c5b..1cba38a 100644 --- a/electron/main/database/migrations.ts +++ b/electron/main/database/migrations.ts @@ -136,39 +136,36 @@ function setSchemaVersion(db: Database.Database, version: number): void { * 自动检测当前版本并执行所有需要的迁移 * * @param db 数据库连接 + * @param forceRepair 是否强制修复(即使版本号已是最新也重新执行迁移脚本) * @returns 是否执行了迁移 */ -export function migrateDatabase(db: Database.Database): boolean { +export function migrateDatabase(db: Database.Database, forceRepair = false): boolean { const currentVersion = getSchemaVersion(db) - // 已是最新版本,无需迁移 - if (currentVersion >= CURRENT_SCHEMA_VERSION) { + // 如果不是强制修复模式,检查版本号 + if (!forceRepair && currentVersion >= CURRENT_SCHEMA_VERSION) { return false } // 获取需要执行的迁移 - const pendingMigrations = migrations.filter((m) => m.version > currentVersion) + // 如果是强制修复,从 version 0 开始执行所有迁移 + const pendingMigrations = forceRepair + ? migrations + : migrations.filter((m) => m.version > currentVersion) if (pendingMigrations.length === 0) { return false } - console.log( - `[Migration] 数据库版本 ${currentVersion} -> ${CURRENT_SCHEMA_VERSION},执行 ${pendingMigrations.length} 个迁移` - ) - // 在事务中执行所有迁移 const migrate = db.transaction(() => { for (const migration of pendingMigrations) { - console.log(`[Migration] 执行迁移 v${migration.version}: ${migration.description}`) migration.up(db) setSchemaVersion(db, migration.version) } }) migrate() - - console.log(`[Migration] 迁移完成,当前版本: ${CURRENT_SCHEMA_VERSION}`) return true } diff --git a/electron/main/index.ts b/electron/main/index.ts index 2d7c97c..8046325 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -6,6 +6,7 @@ import mainIpcMain from './ipcMain' import { initAnalytics, trackDailyActive } from './analytics' import { initProxy } from './network/proxy' import { needsLegacyMigration, migrateFromLegacyDir, ensureAppDirs } from './paths' +import { migrateAllDatabases, checkMigrationNeeded } from './database/core' class MainProcess { mainWindow: BrowserWindow | null @@ -54,6 +55,9 @@ class MainProcess { // 确保应用目录存在 ensureAppDirs() + // 执行数据库 schema 迁移(确保所有数据库在 Worker 查询前已是最新 schema) + this.migrateDatabasesIfNeeded() + initProxy() // 初始化代理配置 // 注册应用协议 @@ -81,6 +85,21 @@ class MainProcess { } } + // 执行数据库 schema 迁移(静默迁移) + migrateDatabasesIfNeeded() { + try { + const { count } = checkMigrationNeeded() + if (count > 0) { + const result = migrateAllDatabases() + if (!result.success) { + console.error('[Main] Database schema migration failed:', result.error) + } + } + } catch (error) { + console.error('[Main] Error in migrateDatabasesIfNeeded:', error) + } + } + // 创建主窗口 async createWindow() { this.mainWindow = new BrowserWindow({