Files
frontend/src/database/core/DatabaseManager.ts
lafay 82c2970a85
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 12m51s
Frontend CI / build-android-apk (push) Successful in 1h1m26s
refactor(database): migrate to new modular database layer and unify data access
- Remove legacy database.ts, LocalDataSource.ts, and MessageRepository.ts
- Create new src/database/ module with messageRepository, userCacheRepository, conversationRepository, and groupCacheRepository
- Update all consumers to import from @/database instead of services/database
- Add web platform blur handling for modal components to fix focus issues
- Flatten SystemMessageItem and NotificationsScreen styles for consistent design
- Add draggable slider in ChatSettingsScreen and dynamic font size support
- Introduce 9 new chat color themes
- Add profile screens for about, terms, and privacy policy with navigation routes
- Add policy links to login and registration screens
- Fix post share URL format from /posts/ to /post/
2026-04-04 08:01:45 +08:00

196 lines
6.4 KiB
TypeScript

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 };