import path from 'path'; import fs from 'fs'; import { v4 as uuidv4 } from 'uuid'; import { createRequire } from 'module'; import { fileURLToPath } from 'url'; // 在ES模块中创建require函数,用于兼容CommonJS模块 const require = createRequire(import.meta.url); const DEFAULT_DB_DIR = path.join(process.cwd(), 'data'); const DEFAULT_DB_PATH = path.join(DEFAULT_DB_DIR, 'survey.db'); const DB_PATH = process.env.DB_PATH || DEFAULT_DB_PATH; const DB_DIR = path.dirname(DB_PATH); // 确保数据目录存在 if (!fs.existsSync(DB_DIR)) { fs.mkdirSync(DB_DIR, { recursive: true }); } // 延迟初始化数据库连接 let db: any = null; let isInitialized = false; // 初始化数据库连接的函数 export const initDbConnection = async () => { if (!isInitialized) { try { // 使用require加载sqlite3,因为它是CommonJS模块 const sqlite3 = require('sqlite3'); db = new sqlite3.Database(DB_PATH, (err: Error) => { if (err) { console.error('数据库连接失败:', err); } else { console.log('数据库连接成功'); // 启用外键约束 db.run('PRAGMA foreign_keys = ON'); } }); isInitialized = true; } catch (error) { console.error('初始化数据库失败:', error); // 初始化失败,不设置isInitialized为true,允许后续调用再次尝试 return null; } } return db; }; // 导出一个函数,用于获取数据库连接 export const getDb = async () => { if (!db) { return await initDbConnection(); } return db; }; const exec = async (sql: string): Promise => { return new Promise(async (resolve, reject) => { const db = await getDb(); if (!db) { reject(new Error('数据库连接未初始化')); return; } db.exec(sql, (err: Error) => { if (err) reject(err); else resolve(); }); }); }; const tableExists = async (tableName: string): Promise => { const row = await get( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", [tableName] ); return Boolean(row); }; const columnExists = async (tableName: string, columnName: string): Promise => { const columns = await query(`PRAGMA table_info(${tableName})`); return columns.some((col: any) => col.name === columnName); }; const ensureColumn = async (tableName: string, columnDefSql: string, columnName: string) => { if (!(await columnExists(tableName, columnName))) { console.log(`添加列 ${tableName}.${columnName}`); await exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnDefSql}`); } else { console.log(`列 ${tableName}.${columnName} 已存在`); } }; const ensureTable = async (createTableSql: string) => { await exec(createTableSql); }; const ensureIndex = async (createIndexSql: string) => { await exec(createIndexSql); }; const ensureUserGroupSchemaAndAllUsersMembership = async () => { // 1) Ensure tables await ensureTable(` CREATE TABLE IF NOT EXISTS user_groups ( id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, description TEXT NOT NULL DEFAULT '', is_system BOOLEAN NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); await ensureTable(` CREATE TABLE IF NOT EXISTS user_group_members ( group_id TEXT NOT NULL, user_id TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (group_id, user_id), FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); `); // 2) Ensure indexes await ensureIndex( `CREATE INDEX IF NOT EXISTS idx_user_group_members_group_id ON user_group_members(group_id);`, ); await ensureIndex( `CREATE INDEX IF NOT EXISTS idx_user_group_members_user_id ON user_group_members(user_id);`, ); // 3) Ensure system group exists const existingSystemGroup = await get( `SELECT id FROM user_groups WHERE is_system = 1 ORDER BY created_at ASC LIMIT 1`, ); let systemGroupId = existingSystemGroup?.id as string | undefined; if (!systemGroupId) { const preferredId = 'all-users'; try { await run( `INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, 1)`, [preferredId, '全体用户', '系统内置:新用户自动加入'], ); systemGroupId = preferredId; } catch { const fallbackId = uuidv4(); await run( `INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, 1)`, [fallbackId, '全体用户', '系统内置:新用户自动加入'], ); systemGroupId = fallbackId; } } // 4) Backfill membership: ensure all existing users are in the system group if (systemGroupId) { await run( `INSERT OR IGNORE INTO user_group_members (group_id, user_id) SELECT ?, u.id FROM users u`, [systemGroupId], ); } }; const migrateDatabase = async () => { // 跳过迁移,因为数据库连接可能未初始化 console.log('跳过数据库迁移'); }; // 数据库初始化函数 export const initDatabase = async () => { try { // 确保数据库连接已初始化 await initDbConnection(); // 检查是否需要初始化:如果users表不存在,则执行初始化 const usersTableExists = await tableExists('users'); if (!usersTableExists) { // 读取并执行初始化SQL文件 const initSqlPath = fileURLToPath(new URL('./init.sql', import.meta.url)); const initSql = fs.readFileSync(initSqlPath, 'utf8'); await exec(initSql); console.log('数据库初始化成功'); // 用户组(含“全体用户”系统组) await ensureUserGroupSchemaAndAllUsersMembership(); } else { console.log('数据库表已存在,跳过初始化'); await ensureColumn('questions', "analysis TEXT NOT NULL DEFAULT ''", 'analysis'); await ensureColumn('quiz_records', "score_percentage REAL", 'score_percentage'); await ensureColumn('quiz_records', "status TEXT", 'status'); // 用户组(含“全体用户”系统组) await ensureUserGroupSchemaAndAllUsersMembership(); } } catch (error) { console.error('数据库初始化失败:', error); // 即使初始化失败,服务器也应该继续运行 } }; // 数据库查询工具函数 export const query = async (sql: string, params: any[] = []): Promise => { return new Promise(async (resolve, reject) => { const db = await getDb(); if (!db) { reject(new Error('数据库连接未初始化')); return; } db.all(sql, params, (err: Error, rows: any[]) => { if (err) { reject(err); } else { resolve(rows); } }); }); }; // all函数是query函数的别名,用于向后兼容 export const all = query; export const run = async (sql: string, params: any[] = []): Promise<{ id: string }> => { return new Promise(async (resolve, reject) => { const db = await getDb(); if (!db) { reject(new Error('数据库连接未初始化')); return; } db.run(sql, params, function(this: any, err: Error | null) { if (err) { reject(err); } else { resolve({ id: this.lastID ? this.lastID.toString() : '' }); } }); }); }; export const get = async (sql: string, params: any[] = []): Promise => { return new Promise(async (resolve, reject) => { const db = await getDb(); if (!db) { reject(new Error('数据库连接未初始化')); return; } db.get(sql, params, (err: Error, row: any) => { if (err) { reject(err); } else { resolve(row); } }); }); };