Files
frontend/src/database/core/DatabaseManager.ts

196 lines
6.4 KiB
TypeScript
Raw Normal View History

import * as SQLite from 'expo-sqlite';
import { LockManager } from './LockManager';
import { DB_CONFIG, getDbName } from './DatabaseConfig';
import { runMigrations } from '../schema/MigrationManager';
type SQLiteDatabase = SQLite.SQLiteDatabase;
class DatabaseManager {
private static instance: DatabaseManager;
private db: SQLiteDatabase | null = null;
private currentUserId: string | null = null;
private initPromise: Promise<void> | null = null;
private initTargetUserId: string | null = null;
private isInitialized = false;
static getInstance(): DatabaseManager {
if (!DatabaseManager.instance) {
DatabaseManager.instance = new DatabaseManager();
}
return DatabaseManager.instance;
}
async initialize(userId?: string | null): Promise<void> {
const targetUserId = userId || null;
const callId = Date.now();
console.log(`[DatabaseManager ${callId}] initialize 开始, userId=${targetUserId}, 当前db=${!!this.db}, 当前userId=${this.currentUserId}`);
if (this.db && this.currentUserId === targetUserId) {
console.log(`[DatabaseManager ${callId}] 已是目标数据库,直接返回`);
this.isInitialized = true;
return;
}
if (this.initPromise && this.initTargetUserId === targetUserId) {
console.log(`[DatabaseManager ${callId}] 等待进行中的初始化`);
await this.initPromise;
console.log(`[DatabaseManager ${callId}] 初始化完成`);
return;
}
if (this.initPromise) {
console.log(`[DatabaseManager ${callId}] 等待其他初始化完成`);
await this.initPromise.catch(() => {});
}
if (this.db && this.currentUserId === targetUserId) {
console.log(`[DatabaseManager ${callId}] 其他初始化已完成目标,直接返回`);
this.isInitialized = true;
return;
}
this.initTargetUserId = targetUserId;
console.log(`[DatabaseManager ${callId}] 开始实际初始化`);
this.initPromise = LockManager.withMutex(async () => {
const mutexId = callId;
console.log(`[DatabaseManager ${mutexId}] 获得互斥锁`);
if (this.db && this.currentUserId === targetUserId) {
console.log(`[DatabaseManager ${mutexId}] 互斥锁内检查:已是目标数据库`);
this.isInitialized = true;
return;
}
const isWeb = typeof window !== 'undefined';
if (this.db) {
console.log(`[DatabaseManager ${mutexId}] 关闭旧数据库连接...`);
try {
await this.db.closeAsync();
console.log(`[DatabaseManager ${mutexId}] 旧连接已关闭`);
} catch (e) {
console.warn(`[DatabaseManager ${mutexId}] 关闭旧连接失败:`, e);
}
this.db = null;
this.currentUserId = null;
this.isInitialized = false;
if (isWeb) {
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_CLOSE_DELAY));
}
}
const dbName = getDbName(targetUserId);
console.log(`[DatabaseManager ${mutexId}] 打开数据库: ${dbName}`);
const database = await this.openDatabaseWithRetry(dbName);
console.log(`[DatabaseManager ${mutexId}] 数据库已打开,执行迁移`);
await runMigrations(database);
console.log(`[DatabaseManager ${mutexId}] 迁移完成`);
this.db = database;
this.currentUserId = targetUserId;
this.isInitialized = true;
console.log(`[DatabaseManager ${mutexId}] 初始化完成`);
}).finally(() => {
console.log(`[DatabaseManager ${callId}] 清理初始化状态`);
this.initPromise = null;
this.initTargetUserId = null;
});
await this.initPromise;
console.log(`[DatabaseManager ${callId}] initialize 返回`);
}
private async openDatabaseWithRetry(dbName: string): Promise<SQLiteDatabase> {
const isWeb = typeof window !== 'undefined';
return LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
for (let attempt = 0; attempt <= DB_CONFIG.MAX_RETRIES; attempt++) {
try {
const database = await SQLite.openDatabaseAsync(dbName);
console.log(`[DatabaseManager] 成功打开数据库 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1})`);
return database;
} catch (error) {
const msg = String(error);
console.error(`[DatabaseManager] 打开失败 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1}):`, msg);
if (attempt < DB_CONFIG.MAX_RETRIES) {
const delay = isWeb ? DB_CONFIG.WEB_RETRY_DELAY_BASE * (attempt + 1) : DB_CONFIG.NATIVE_RETRY_DELAY;
console.warn(`[DatabaseManager] ${delay}ms 后重试...`);
await new Promise(r => setTimeout(r, delay));
} else {
throw error;
}
}
}
throw new Error('打开数据库失败');
});
}
async close(): Promise<void> {
if (this.initPromise) {
await this.initPromise.catch(() => {});
}
const dbName = getDbName(this.currentUserId);
await LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
await LockManager.withMutex(async () => {
if (!this.db) return;
try {
await this.db.closeAsync();
console.log('[DatabaseManager] 数据库已关闭');
if (typeof window !== 'undefined') {
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_FINAL_CLOSE_DELAY));
}
} catch (e) {
console.warn('[DatabaseManager] 关闭失败:', e);
}
this.db = null;
this.currentUserId = null;
this.isInitialized = false;
});
});
}
async getDb(timeout: number = 10000): Promise<SQLiteDatabase> {
if (this.isInitialized && this.db) return this.db;
if (this.initPromise) {
const timeoutPromise = new Promise<null>((_, reject) =>
setTimeout(() => reject(new Error('等待数据库初始化超时')), timeout)
);
await Promise.race([this.initPromise, timeoutPromise]);
if (this.db) return this.db;
}
throw new Error('数据库未初始化,请先调用 initialize(userId)');
}
getDbSync(): SQLiteDatabase | null {
return this.db;
}
getCurrentUserId(): string | null {
return this.currentUserId;
}
isReady(): boolean {
return this.isInitialized && this.db !== null;
}
async withMutex<T>(fn: () => Promise<T>): Promise<T> {
return LockManager.withMutex(fn);
}
}
export const databaseManager = DatabaseManager.getInstance();
export { DatabaseManager };