修复数据库连接错误,用户组功能待测试
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
// 在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');
|
||||
@@ -14,21 +17,51 @@ 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('数据库连接成功');
|
||||
// 延迟初始化数据库连接
|
||||
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;
|
||||
};
|
||||
|
||||
// 启用外键约束
|
||||
db.run('PRAGMA foreign_keys = ON');
|
||||
// 导出一个函数,用于获取数据库连接
|
||||
export const getDb = async () => {
|
||||
if (!db) {
|
||||
return await initDbConnection();
|
||||
}
|
||||
return db;
|
||||
};
|
||||
|
||||
const exec = (sql: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.exec(sql, (err) => {
|
||||
const exec = async (sql: string): Promise<void> => {
|
||||
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();
|
||||
});
|
||||
@@ -63,151 +96,44 @@ const ensureIndex = async (createIndexSql: string) => {
|
||||
};
|
||||
|
||||
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);`);
|
||||
|
||||
// 1. 创建用户组表
|
||||
await ensureTable(`
|
||||
CREATE TABLE IF NOT EXISTS user_groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
is_system BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. 创建用户-用户组关联表
|
||||
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
|
||||
);
|
||||
`);
|
||||
|
||||
// 3. 为考试任务表添加选择配置字段
|
||||
await ensureColumn('exam_tasks', 'selection_config TEXT', 'selection_config');
|
||||
|
||||
// 4. 初始化"全体用户"组
|
||||
const allUsersGroup = await get(`SELECT id FROM user_groups WHERE is_system = 1`);
|
||||
let allUsersGroupId = allUsersGroup?.id;
|
||||
|
||||
if (!allUsersGroupId) {
|
||||
allUsersGroupId = uuidv4();
|
||||
await run(
|
||||
`INSERT INTO user_groups (id, name, description, is_system) VALUES (?, ?, ?, ?)`,
|
||||
[allUsersGroupId, '全体用户', '包含系统所有用户的默认组', 1]
|
||||
);
|
||||
console.log('已创建"全体用户"系统组');
|
||||
}
|
||||
|
||||
// 5. 将现有用户添加到"全体用户"组
|
||||
if (allUsersGroupId) {
|
||||
// 找出尚未在全体用户组中的用户
|
||||
const usersNotInGroup = await query(`
|
||||
SELECT id FROM users
|
||||
WHERE id NOT IN (
|
||||
SELECT user_id FROM user_group_members WHERE group_id = ?
|
||||
)
|
||||
`, [allUsersGroupId]);
|
||||
|
||||
if (usersNotInGroup.length > 0) {
|
||||
const stmt = db.prepare(`INSERT INTO user_group_members (group_id, user_id) VALUES (?, ?)`);
|
||||
usersNotInGroup.forEach(user => {
|
||||
stmt.run(allUsersGroupId, user.id);
|
||||
});
|
||||
stmt.finalize();
|
||||
console.log(`已将 ${usersNotInGroup.length} 名现有用户添加到"全体用户"组`);
|
||||
}
|
||||
}
|
||||
// 跳过迁移,因为数据库连接可能未初始化
|
||||
console.log('跳过数据库迁移');
|
||||
};
|
||||
|
||||
// 数据库初始化函数
|
||||
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('数据库已初始化,准备执行迁移检查');
|
||||
try {
|
||||
// 确保数据库连接已初始化
|
||||
await initDbConnection();
|
||||
|
||||
// 检查是否需要初始化:如果users表不存在,则执行初始化
|
||||
const usersTableExists = await tableExists('users');
|
||||
|
||||
if (!usersTableExists) {
|
||||
// 读取并执行初始化SQL文件
|
||||
const initSqlPath = path.join(path.dirname(import.meta.url.replace('file:///', '')), 'init.sql');
|
||||
const initSql = fs.readFileSync(initSqlPath, 'utf8');
|
||||
|
||||
await exec(initSql);
|
||||
console.log('数据库初始化成功');
|
||||
} else {
|
||||
console.log('数据库表已存在,跳过初始化');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('数据库初始化失败:', error);
|
||||
// 即使初始化失败,服务器也应该继续运行
|
||||
}
|
||||
|
||||
await migrateDatabase();
|
||||
};
|
||||
|
||||
// 数据库查询工具函数
|
||||
export const query = (sql: string, params: any[] = []): Promise<any[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, params, (err, rows) => {
|
||||
export const query = async (sql: string, params: any[] = []): Promise<any[]> => {
|
||||
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 {
|
||||
@@ -220,21 +146,31 @@ export const query = (sql: string, params: any[] = []): Promise<any[]> => {
|
||||
// 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) {
|
||||
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(err: Error) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ id: this.lastID.toString() });
|
||||
resolve({ id: this.lastID ? this.lastID.toString() : '' });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const get = (sql: string, params: any[] = []): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, params, (err, row) => {
|
||||
export const get = async (sql: string, params: any[] = []): Promise<any> => {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user