feat(resultStore): 支持数据库文件迁移和兼容性处理

- 添加对旧版 results.sqlite 到新版 results.db 的自动迁移支持
- 实现数据库连接时的错误处理和日志记录
- 修改数据库事务模式为 DELETE 模式以提高稳定性
- 增加对临时 WAL 和 SHM 文件的清理处理
- 提供环境变量配置的数据库路径优先级支持
```
This commit is contained in:
2026-03-19 15:12:55 +08:00
parent 2a5fd99319
commit f8dfad26a4

View File

@@ -1,15 +1,23 @@
import Database from 'better-sqlite3'; import Database from 'better-sqlite3';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import {
copyFileSync,
existsSync,
mkdirSync,
readFileSync,
renameSync,
unlinkSync,
writeFileSync,
} from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const DB_PATH = const DEFAULT_DB_DIR = join(__dirname, '..', 'data');
process.env.APP_DB_PATH || const DEFAULT_DB_PATH = join(DEFAULT_DB_DIR, 'results.db');
process.env.RESULTS_DB_PATH || const LEGACY_DB_PATH = join(DEFAULT_DB_DIR, 'results.sqlite');
join(__dirname, '..', 'data', 'results.sqlite'); const DB_PATH = resolveDbPath();
const CONFIG_PATH = join(__dirname, '..', 'config.json'); const CONFIG_PATH = join(__dirname, '..', 'config.json');
const MAX_RESULT_RECORDS = 500; const MAX_RESULT_RECORDS = 500;
const DEFAULT_TASK_MODE = 'qwen3.5-plus'; const DEFAULT_TASK_MODE = 'qwen3.5-plus';
@@ -22,6 +30,55 @@ function clone(value) {
return JSON.parse(JSON.stringify(value)); return JSON.parse(JSON.stringify(value));
} }
function removeFileIfExists(filePath) {
if (!existsSync(filePath)) return;
unlinkSync(filePath);
}
function migrateLegacyDbIfNeeded(nextPath, legacyPath) {
if (existsSync(nextPath) || !existsSync(legacyPath)) return nextPath;
const legacyDb = new Database(legacyPath);
try {
legacyDb.pragma('wal_checkpoint(TRUNCATE)');
} catch (_error) {
// Ignore checkpoint failures and still attempt to switch to single-file mode.
}
legacyDb.pragma('journal_mode = DELETE');
legacyDb.close();
try {
renameSync(legacyPath, nextPath);
} catch (_error) {
copyFileSync(legacyPath, nextPath);
}
removeFileIfExists(`${legacyPath}-shm`);
removeFileIfExists(`${legacyPath}-wal`);
return nextPath;
}
function resolveDbPath() {
const explicitPath = process.env.APP_DB_PATH || process.env.RESULTS_DB_PATH;
if (explicitPath) return explicitPath;
mkdirSync(DEFAULT_DB_DIR, { recursive: true });
try {
return migrateLegacyDbIfNeeded(DEFAULT_DB_PATH, LEGACY_DB_PATH);
} catch (error) {
if (existsSync(LEGACY_DB_PATH)) {
console.warn(`[resultStore] Legacy database migration skipped: ${error.message}`);
return LEGACY_DB_PATH;
}
throw error;
}
}
function generateResultId() { function generateResultId() {
return `result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; return `result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
} }
@@ -141,7 +198,13 @@ function getDb() {
mkdirSync(dirname(DB_PATH), { recursive: true }); mkdirSync(dirname(DB_PATH), { recursive: true });
db = new Database(DB_PATH); db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
try {
db.pragma('journal_mode = DELETE');
} catch (error) {
console.warn(`[resultStore] Database journal mode unchanged: ${error.message}`);
}
return db; return db;
} }