Files
Web_BLV_OA_Exam_Prod/api/database/index.ts

186 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sqlite3 from 'sqlite3';
import path from 'path';
import fs from 'fs';
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 });
}
// 创建数据库连接
export const db = new sqlite3.Database(DB_PATH, (err) => {
if (err) {
console.error('数据库连接失败:', err);
} else {
console.log('数据库连接成功');
}
});
// 启用外键约束
db.run('PRAGMA foreign_keys = ON');
const exec = (sql: string): Promise<void> => {
return new Promise((resolve, reject) => {
db.exec(sql, (err) => {
if (err) reject(err);
else resolve();
});
});
};
const tableExists = async (tableName: string): Promise<boolean> => {
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<boolean> => {
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))) {
await exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnDefSql}`);
}
};
const ensureTable = async (createTableSql: string) => {
await exec(createTableSql);
};
const ensureIndex = async (createIndexSql: string) => {
await exec(createIndexSql);
};
const migrateDatabase = async () => {
await ensureColumn('users', `password TEXT NOT NULL DEFAULT ''`, 'password');
await ensureColumn('questions', `category TEXT NOT NULL DEFAULT '通用'`, 'category');
await run(`UPDATE questions SET category = '通用' WHERE category IS NULL OR category = ''`);
await ensureTable(`
CREATE TABLE IF NOT EXISTS question_categories (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
await run(
`INSERT OR IGNORE INTO question_categories (id, name) VALUES ('default', '通用')`
);
await ensureTable(`
CREATE TABLE IF NOT EXISTS exam_subjects (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
type_ratios TEXT NOT NULL,
category_ratios TEXT NOT NULL,
total_score INTEGER NOT NULL,
duration_minutes INTEGER NOT NULL DEFAULT 60,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
await ensureTable(`
CREATE TABLE IF NOT EXISTS exam_tasks (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
subject_id TEXT NOT NULL,
start_at DATETIME NOT NULL,
end_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (subject_id) REFERENCES exam_subjects(id)
);
`);
await ensureTable(`
CREATE TABLE IF NOT EXISTS exam_task_users (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(task_id, user_id),
FOREIGN KEY (task_id) REFERENCES exam_tasks(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`);
if (await tableExists('quiz_records')) {
await ensureColumn('quiz_records', `subject_id TEXT`, 'subject_id');
await ensureColumn('quiz_records', `task_id TEXT`, 'task_id');
}
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_questions_category ON questions(category);`);
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_exam_tasks_subject_id ON exam_tasks(subject_id);`);
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_exam_task_users_task_id ON exam_task_users(task_id);`);
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_exam_task_users_user_id ON exam_task_users(user_id);`);
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_quiz_records_subject_id ON quiz_records(subject_id);`);
await ensureIndex(`CREATE INDEX IF NOT EXISTS idx_quiz_records_task_id ON quiz_records(task_id);`);
};
// 数据库初始化函数
export const initDatabase = async () => {
const initSQL = fs.readFileSync(path.join(process.cwd(), 'api', 'database', 'init.sql'), 'utf8');
const hasUsersTable = await tableExists('users');
if (!hasUsersTable) {
await exec(initSQL);
console.log('数据库初始化成功');
} else {
console.log('数据库已初始化,准备执行迁移检查');
}
await migrateDatabase();
};
// 数据库查询工具函数
export const query = (sql: string, params: any[] = []): Promise<any[]> => {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
};
// all函数是query函数的别名用于向后兼容
export const all = query;
export const run = (sql: string, params: any[] = []): Promise<{ id: string }> => {
return new Promise((resolve, reject) => {
db.run(sql, params, function(err) {
if (err) {
reject(err);
} else {
resolve({ id: this.lastID.toString() });
}
});
});
};
export const get = (sql: string, params: any[] = []): Promise<any> => {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) {
reject(err);
} else {
resolve(row);
}
});
});
};