初始化数据库

This commit is contained in:
chenqiang 2025-08-27 21:29:16 +08:00
parent 9790a3bbd3
commit f3b783bf86
20 changed files with 1172 additions and 4 deletions

2
.gitignore vendored
View File

@ -24,3 +24,5 @@ pnpm-debug.log*
#Electron-builder output
/dist_electron
/high_version

2
data/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "vue-cli-service electron:serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"electron:build": "vue-cli-service electron:build",
@ -12,7 +13,7 @@
"rebuild-deps": "electron-builder install-app-deps",
"rebuild-sqlite3": "npm_config_runtime=electron npm_config_target=11.5.0 npm_config_disturl=https://electronjs.org/headers npm_config_build_from_source=true node-pre-gyp rebuild --target=11.5.0 --runtime=electron --fallback-to-build --directory=./node_modules/sqlite3/ --dist-url=https://electronjs.org/headers"
},
"main": "background.js",
"main": "background/main.js",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",

423
src/background/db/index.js Normal file
View File

@ -0,0 +1,423 @@
const { getSystemDbPath, getUserDbPath } = require('./path.js');
const { systemSchema, userSchema, defaultData } = require('./schema.js');
const { openDatabase, batchInsert } = require('./utils.js');
const bcrypt = require('bcryptjs');
// 数据库连接池
const dbConnections = new Map();
// 获取数据库连接
exports.getDbConnection = async function getDbConnection(dbPath) {
if (dbConnections.has(dbPath)) {
console.log(`使用现有数据库连接: ${dbPath}`);
return dbConnections.get(dbPath);
}
const db = await openDatabase(dbPath);
dbConnections.set(dbPath, db);
return db;
};
// 关闭所有数据库连接
exports.closeAllConnections = function closeAllConnections() {
dbConnections.forEach((db, path) => {
try {
db.close();
console.log(`关闭数据库连接: ${path}`);
} catch (error) {
console.error(`关闭数据库连接失败: ${path}`, error);
}
});
dbConnections.clear();
};
// 检查数据库是否已初始化
exports.checkDatabaseInitialized = async function checkDatabaseInitialized() {
try {
console.log('开始检查数据库初始化状态...');
const systemDb = await exports.getDbConnection(getSystemDbPath());
console.log('成功打开系统数据库');
const result = await systemDb.getAsync('SELECT value FROM config WHERE key = ?', ['initialized']);
console.log('查询初始化状态结果:', result);
const isInitialized = result && result.value === 'true';
console.log('数据库初始化状态:', isInitialized ? '已初始化' : '未初始化');
return isInitialized;
} catch (error) {
console.error('检查数据库初始化状态失败:', error);
return false;
}
};
// 初始化系统数据库
async function initializeSystemDatabase() {
console.log('开始初始化系统数据库...');
const systemDbPath = getSystemDbPath();
const systemDb = await exports.getDbConnection(systemDbPath);
// 记录成功和失败的操作
const results = { success: [], failed: [] };
// 创建表结构
console.log('开始创建系统数据库表结构...');
// 创建config表
try {
await systemDb.execAsync(systemSchema.config.trim());
console.log('创建 config 表成功');
results.success.push('创建 config 表');
} catch (error) {
console.error('创建 config 表失败:', error);
results.failed.push({ operation: '创建 config 表', error: error.message });
}
// 创建dict_types表
try {
await systemDb.execAsync(systemSchema.dictTypes.trim());
console.log('创建 dict_types 表成功');
results.success.push('创建 dict_types 表');
} catch (error) {
console.error('创建 dict_types 表失败:', error);
results.failed.push({ operation: '创建 dict_types 表', error: error.message });
}
// 创建dict_items表
try {
await systemDb.execAsync(systemSchema.dictItems.trim());
console.log('创建 dict_items 表成功');
results.success.push('创建 dict_items 表');
} catch (error) {
console.error('创建 dict_items 表失败:', error);
results.failed.push({ operation: '创建 dict_items 表', error: error.message });
}
// 创建questions表
try {
await systemDb.execAsync(systemSchema.questions.trim());
console.log('创建 questions 表成功');
results.success.push('创建 questions 表');
} catch (error) {
console.error('创建 questions 表失败:', error);
results.failed.push({ operation: '创建 questions 表', error: error.message });
}
// 创建question_datasets表
try {
await systemDb.execAsync(systemSchema.questionDatasets.trim());
console.log('创建 question_datasets 表成功');
results.success.push('创建 question_datasets 表');
} catch (error) {
console.error('创建 question_datasets 表失败:', error);
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
}
// 创建question_images表
try {
await systemDb.execAsync(systemSchema.questionImages.trim());
console.log('创建 question_images 表成功');
results.success.push('创建 question_images 表');
} catch (error) {
console.error('创建 question_images 表失败:', error);
results.failed.push({ operation: '创建 question_images 表', error: error.message });
}
// 创建question_fill_table表
try {
await systemDb.execAsync(systemSchema.questionFillTable.trim());
console.log('创建 question_fill_table 表成功');
results.success.push('创建 question_fill_table 表');
} catch (error) {
console.error('创建 question_fill_table 表失败:', error);
results.failed.push({ operation: '创建 question_fill_table 表', error: error.message });
}
// 创建question_fill_table_blanks表
try {
await systemDb.execAsync(systemSchema.questionFillTableBlanks.trim());
console.log('创建 question_fill_table_blanks 表成功');
results.success.push('创建 question_fill_table_blanks 表');
} catch (error) {
console.error('创建 question_fill_table_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message });
}
// 创建question_choices表
try {
await systemDb.execAsync(systemSchema.questionChoices.trim());
console.log('创建 question_choices 表成功');
results.success.push('创建 question_choices 表');
} catch (error) {
console.error('创建 question_choices 表失败:', error);
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
}
// 创建question_fill_blanks表
try {
await systemDb.execAsync(systemSchema.questionFillBlanks.trim());
console.log('创建 question_fill_blanks 表成功');
results.success.push('创建 question_fill_blanks 表');
} catch (error) {
console.error('创建 question_fill_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
}
// 创建question_judge表
try {
await systemDb.execAsync(systemSchema.questionJudge.trim());
console.log('创建 question_judge 表成功');
results.success.push('创建 question_judge 表');
} catch (error) {
console.error('创建 question_judge 表失败:', error);
results.failed.push({ operation: '创建 question_judge 表', error: error.message });
}
// 创建question_short表
try {
await systemDb.execAsync(systemSchema.questionShort.trim());
console.log('创建 question_short 表成功');
results.success.push('创建 question_short 表');
} catch (error) {
console.error('创建 question_short 表失败:', error);
results.failed.push({ operation: '创建 question_short 表', error: error.message });
}
// 创建exam表
try {
await systemDb.execAsync(systemSchema.exam.trim());
console.log('创建 exam 表成功');
results.success.push('创建 exam 表');
} catch (error) {
console.error('创建 exam 表失败:', error);
results.failed.push({ operation: '创建 exam 表', error: error.message });
}
// 创建examinee表
try {
await systemDb.execAsync(systemSchema.examinee.trim());
console.log('创建 examinee 表成功');
results.success.push('创建 examinee 表');
} catch (error) {
console.error('创建 examinee 表失败:', error);
results.failed.push({ operation: '创建 examinee 表', error: error.message });
}
// 插入默认数据
console.log('开始插入默认数据...');
// 处理密码哈希
try {
const plainPassword = defaultData.config.find(item => item.key === 'admin_password').value;
const hashedPassword = await bcrypt.hash(plainPassword, 10);
// 更新密码为哈希值
const configData = defaultData.config.map(item => {
if (item.key === 'admin_password') {
return { ...item, value: hashedPassword };
}
return item;
});
// 插入config表数据
try {
await batchInsert(systemDb, 'config', configData);
console.log('插入 config 表数据成功');
results.success.push('插入 config 表数据');
} catch (error) {
console.error('插入 config 表数据失败:', error);
results.failed.push({ operation: '插入 config 表数据', error: error.message });
}
} catch (error) {
console.error('处理密码哈希失败:', error);
results.failed.push({ operation: '处理密码哈希', error: error.message });
}
// 插入dict_types表数据
try {
await batchInsert(systemDb, 'dict_types', defaultData.dictTypes);
console.log('插入 dict_types 表数据成功');
results.success.push('插入 dict_types 表数据');
} catch (error) {
console.error('插入 dict_types 表数据失败:', error);
results.failed.push({ operation: '插入 dict_types 表数据', error: error.message });
}
// 插入dict_items表数据
try {
await batchInsert(systemDb, 'dict_items', defaultData.dictItems);
console.log('插入 dict_items 表数据成功');
results.success.push('插入 dict_items 表数据');
} catch (error) {
console.error('插入 dict_items 表数据失败:', error);
results.failed.push({ operation: '插入 dict_items 表数据', error: error.message });
}
console.log('系统数据库初始化结果:');
console.log('成功操作:', results.success);
console.log('失败操作:', results.failed);
// 如果有失败操作,抛出错误
if (results.failed.length > 0) {
console.log(`系统数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`);
// 输出详细的失败信息
results.failed.forEach((item, index) => {
console.log(`${index + 1}. ${item.operation} 失败: ${item.error}`);
});
}
return true;
}
// 初始化用户数据库
async function initializeUserDatabase() {
console.log('开始初始化用户数据库...');
const userDbPath = getUserDbPath();
const userDb = await exports.getDbConnection(userDbPath);
// 记录成功和失败的操作
const results = { success: [], failed: [] };
// 创建表结构
console.log('开始创建用户数据库表结构...');
// 创建examinee表
try {
await userDb.execAsync(userSchema.examinee.trim());
console.log('创建 examinee 表成功');
results.success.push('创建 examinee 表');
} catch (error) {
console.error('创建 examinee 表失败:', error);
results.failed.push({ operation: '创建 examinee 表', error: error.message });
}
// 创建examinee_papers表
try {
await userDb.execAsync(userSchema.examinee_papers.trim());
console.log('创建 examinee_papers 表成功');
results.success.push('创建 examinee_papers 表');
} catch (error) {
console.error('创建 examinee_papers 表失败:', error);
results.failed.push({ operation: '创建 examinee_papers 表', error: error.message });
}
// 创建paper_questions表
try {
await userDb.execAsync(userSchema.paper_questions.trim());
console.log('创建 paper_questions 表成功');
results.success.push('创建 paper_questions 表');
} catch (error) {
console.error('创建 paper_questions 表失败:', error);
results.failed.push({ operation: '创建 paper_questions 表', error: error.message });
}
// 创建question_datasets表
try {
await userDb.execAsync(userSchema.question_datasets.trim());
console.log('创建 question_datasets 表成功');
results.success.push('创建 question_datasets 表');
} catch (error) {
console.error('创建 question_datasets 表失败:', error);
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
}
// 创建question_images表
try {
await userDb.execAsync(userSchema.question_images.trim());
console.log('创建 question_images 表成功');
results.success.push('创建 question_images 表');
} catch (error) {
console.error('创建 question_images 表失败:', error);
results.failed.push({ operation: '创建 question_images 表', error: error.message });
}
// 创建question_choices表
try {
await userDb.execAsync(userSchema.question_choices.trim());
console.log('创建 question_choices 表成功');
results.success.push('创建 question_choices 表');
} catch (error) {
console.error('创建 question_choices 表失败:', error);
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
}
// 创建question_fill_blanks表
try {
await userDb.execAsync(userSchema.question_fill_blanks.trim());
console.log('创建 question_fill_blanks 表成功');
results.success.push('创建 question_fill_blanks 表');
} catch (error) {
console.error('创建 question_fill_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
}
console.log('用户数据库初始化结果:');
console.log('成功操作:', results.success);
console.log('失败操作:', results.failed);
// 如果有失败操作,仅打印错误信息,不抛出异常
if (results.failed.length > 0) {
console.error(`用户数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`);
// 输出详细的失败信息
results.failed.forEach((item, index) => {
console.error(`${index + 1}. ${item.operation} 失败: ${item.error}`);
});
}
return true;
}
// 初始化数据库
exports.initializeDatabase = async function initializeDatabase() {
try {
console.log('开始初始化数据库...');
// 确保只有一个初始化请求在执行
if (global.isInitializing) {
console.log('数据库初始化已在进行中,等待完成...');
while (global.isInitializing) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return global.initResult;
}
global.isInitializing = true;
global.initResult = false;
// 先初始化系统数据库
console.log('开始初始化系统数据库...');
const systemResult = await initializeSystemDatabase();
console.log('系统数据库初始化结果:', systemResult ? '成功' : '失败');
if (!systemResult) {
throw new Error('系统数据库初始化失败');
}
// 再初始化用户数据库
console.log('开始初始化用户数据库...');
const userResult = await initializeUserDatabase();
console.log('用户数据库初始化结果:', userResult ? '成功' : '失败');
if (!userResult) {
throw new Error('用户数据库初始化失败');
}
// 更新初始化状态
console.log('更新数据库初始化状态...');
const systemDb = await exports.getDbConnection(getSystemDbPath());
await systemDb.runAsync('UPDATE config SET value = ? WHERE key = ?', ['true', 'initialized']);
console.log('数据库初始化状态更新成功');
console.log('数据库整体初始化成功');
global.initResult = true;
return { success: true };
} catch (error) {
console.error('数据库初始化失败:', error);
global.initResult = false;
return { success: false, error: error.message };
} finally {
global.isInitializing = false;
}
};
// 应用退出时关闭所有连接
process.on('exit', exports.closeAllConnections);

53
src/background/db/path.js Normal file
View File

@ -0,0 +1,53 @@
const path = require('path');
const fs = require('fs');
const { app } = require('electron');
// 判断是否为开发环境
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
// 检测是否为便携模式
let isPortable = false;
const exePath = app.getPath('exe');
const appDir = path.dirname(exePath);
const portableFlagPath = path.join(appDir, 'portable.txt');
// 如果应用目录中存在portable.txt文件则启用便携模式
if (!isDev && fs.existsSync(portableFlagPath)) {
isPortable = true;
console.log('启用便携模式');
}
// 根据模式选择数据目录
let dataDir;
if (isDev) {
// 开发环境数据存储在工程的data目录
dataDir = path.join(process.cwd(), 'data');
} else if (isPortable) {
// 便携模式数据存储在应用目录下的data文件夹
dataDir = path.join(appDir, 'data');
} else {
// 非便携模式数据存储在应用同级的data目录
dataDir = path.join(appDir, 'data');
}
// 确保数据目录存在
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
console.log(`创建数据目录: ${dataDir}`);
}
// 系统数据库路径
function getSystemDbPath() {
return path.join(dataDir, 'system.db');
}
// 用户数据库路径
function getUserDbPath() {
return path.join(dataDir, 'user.db');
}
// 导出函数供其他模块使用
module.exports = {
getSystemDbPath,
getUserDbPath
};

411
src/background/db/schema.js Normal file
View File

@ -0,0 +1,411 @@
// 执行SQL语句的工具函数
const runAsync = (db, sql, params = []) => {
return new Promise((resolve, reject) => {
db.run(sql, params, function (err) {
if (err) reject(err);
else resolve(this);
});
});
};
// 系统数据库表结构
systemSchema = {
config: `
CREATE TABLE IF NOT EXISTS config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
value TEXT NOT NULL,
protected INTEGER DEFAULT 0
);
`,
dictTypes: `
CREATE TABLE IF NOT EXISTS dict_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type_code TEXT NOT NULL UNIQUE,
type_name TEXT NOT NULL,
description TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
dictItems: `
CREATE TABLE IF NOT EXISTS dict_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type_code TEXT NOT NULL,
item_code TEXT NOT NULL,
item_name TEXT NOT NULL,
item_description TEXT DEFAULT '',
parent_code TEXT,
is_active BOOLEAN DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
UNIQUE(type_code, item_code),
FOREIGN KEY (type_code) REFERENCES dict_types(type_code)
);
`,
questions: `
CREATE TABLE IF NOT EXISTS questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_type TEXT NOT NULL,
question_name TEXT NOT NULL DEFAULT '',
question_description TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_type) REFERENCES dict_types(type_code)
);
`,
questionDatasets: `
CREATE TABLE IF NOT EXISTS question_datasets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
dataset_name TEXT NOT NULL DEFAULT '',
dataset_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionImages: `
CREATE TABLE IF NOT EXISTS question_images (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
image_name TEXT NOT NULL DEFAULT '',
image_base64 TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionFillTable: `
CREATE TABLE IF NOT EXISTS question_fill_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
table_name TEXT NOT NULL DEFAULT '',
table_data TEXT NOT NULL,
table_description TEXT NOT NULL DEFAULT '',
score REAL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionFillTableBlanks: `
CREATE TABLE IF NOT EXISTS question_fill_table_blanks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
table_id INTEGER NOT NULL,
cell_position TEXT NOT NULL,
cell_type TEXT NOT NULL DEFAULT 'number',
correct_answer TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id),
FOREIGN KEY (table_id) REFERENCES question_fill_table(id)
);
`,
questionChoices: `
CREATE TABLE IF NOT EXISTS question_choices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
choice_description TEXT NOT NULL DEFAULT '',
choice_type TEXT NOT NULL DEFAULT 'single',
choice_options TEXT NOT NULL,
correct_answers TEXT NOT NULL,
score REAL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionFillBlanks: `
CREATE TABLE IF NOT EXISTS question_fill_blanks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
blank_description TEXT NOT NULL DEFAULT '',
blank_count INTEGER NOT NULL DEFAULT 0,
correct_answers TEXT NOT NULL DEFAULT '',
score REAL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionJudge: `
CREATE TABLE IF NOT EXISTS question_judge (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
judge_ask TEXT NOT NULL DEFAULT '',
judge_answer INTEGER NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
questionShort: `
CREATE TABLE IF NOT EXISTS question_short (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
short_ask TEXT NOT NULL DEFAULT '',
short_answer_ref TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`,
exam: `
CREATE TABLE IF NOT EXISTS exam (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exam_name TEXT NOT NULL DEFAULT '',
exam_description TEXT NOT NULL DEFAULT '',
exam_examinee_type TEXT NOT NULL DEFAULT '',
exam_notice TEXT NOT NULL DEFAULT '',
exam_minutes INTEGER NOT NULL DEFAULT 0,
exam_minutes_min INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
examinee: `
CREATE TABLE IF NOT EXISTS examinee (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_name TEXT NOT NULL DEFAULT '',
examinee_gender TEXT NOT NULL DEFAULT '',
examinee_unit TEXT NOT NULL DEFAULT '',
written_exam_room TEXT NOT NULL DEFAULT '',
written_exam_seat TEXT NOT NULL DEFAULT '',
computer_exam_room TEXT NOT NULL DEFAULT '',
computer_exam_seat TEXT NOT NULL DEFAULT '',
examinee_id_card TEXT NOT NULL DEFAULT '',
examinee_admission_ticket TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`
};
// 用户数据库表结构
userSchema = {
examinee: `
CREATE TABLE IF NOT EXISTS examinee (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_name TEXT NOT NULL DEFAULT '',
examinee_gender TEXT,
examinee_unit TEXT,
written_exam_room TEXT,
written_exam_seat TEXT,
computer_exam_room TEXT,
computer_exam_seat TEXT,
examinee_id_card TEXT NOT NULL DEFAULT '',
examinee_admission_ticket TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
examinee_papers: `
CREATE TABLE IF NOT EXISTS examinee_papers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
paper_minutes INTEGER NOT NULL DEFAULT 0,
paper_minutes_min INTEGER NOT NULL DEFAULT 0,
paper_start_time TEXT,
paper_last_time TEXT,
paper_submit_time TEXT,
paper_end_time TEXT,
paper_status INTEGER NOT NULL DEFAULT 0, -- 试卷状态0未开始1进行中2已交卷
paper_score_real REAL DEFAULT 0,
paper_score REAL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paper_questions: `
CREATE TABLE IF NOT EXISTS paper_questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
paper_id INTEGER NOT NULL,
question_type TEXT NOT NULL,
question_name TEXT NOT NULL DEFAULT '',
question_description TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_datasets: `
CREATE TABLE IF NOT EXISTS question_datasets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
dataset_name TEXT NOT NULL DEFAULT '',
dataset_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_images: `
CREATE TABLE IF NOT EXISTS question_images (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
image_name TEXT NOT NULL DEFAULT '',
image_base64 TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_choices: `
CREATE TABLE IF NOT EXISTS question_choices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
choice_description TEXT NOT NULL DEFAULT '',
choice_type TEXT NOT NULL DEFAULT 'single',
choice_options TEXT NOT NULL,
correct_answers TEXT NOT NULL,
examinee_answers TEXT NOT NULL DEFAULT '',
score REAL,
score_real REAL DEFAULT 0, -- 本题得分
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_fill_blanks: `
CREATE TABLE IF NOT EXISTS question_fill_blanks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
blank_description TEXT NOT NULL DEFAULT '',
blank_count INTEGER NOT NULL DEFAULT 0,
correct_answers TEXT NOT NULL DEFAULT '',
examinee_answers TEXT NOT NULL DEFAULT '',
score REAL,
score_real REAL DEFAULT 0, -- 本题得分
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`
};
// 初始化默认数据
// 系统配置默认数据
const plainPassword = "t2t6a9"; // 明文密码变量定义
// 注意在实际初始化数据库时需要使用bcryptjs对plainPassword进行哈希
// 这里只定义默认数据结构哈希操作应在index.js中的初始化函数中完成
defaultData = {
config: [
{ key: "admin_password", value: plainPassword, protected: 1 },
{ key: "question_bank_version", value: "1", protected: 1 },
{ key: "exam_version", value: "1", protected: 1 },
{ key: "initialized", value: "1", protected: 1 },
],
// 字典类型默认数据
dictTypes: [
{
type_code: "question_category",
type_name: "题型分类",
description: "用于区分客观题和主观题",
},
{
type_code: "question_type",
type_name: "题型",
description: "存储所有题型(选择题、填空题等)",
},
{
type_code: "user_role",
type_name: "用户角色",
description: "区分不同用户类型",
},
],
// 字典项默认数据
dictItems: [
// 题型分类
{
type_code: "question_category",
item_code: "objective",
item_name: "客观题",
item_description: "有固定答案,机器可自动评分",
is_active: 1,
parent_code: null,
},
{
type_code: "question_category",
item_code: "subjective",
item_name: "主观题",
item_description: "需人工评分,答案不唯一",
is_active: 1,
parent_code: null,
},
// 题型
{
type_code: "question_type",
item_code: "choice",
item_name: "选择题",
item_description: "包含单选和多选",
is_active: 1,
parent_code: "objective",
},
{
type_code: "question_type",
item_code: "fill_blank",
item_name: "填空题",
item_description: "填写空白处的答案",
is_active: 1,
parent_code: "objective",
},
{
type_code: "question_type",
item_code: "fill_table",
item_name: "填表题",
item_description: "填写表格内容",
is_active: 0,
parent_code: "objective",
},
{
type_code: "question_type",
item_code: "true_false",
item_name: "判断题",
item_description: "判断对错",
is_active: 0,
parent_code: "objective",
},
{
type_code: "question_type",
item_code: "short_answer",
item_name: "问答题",
item_description: "简短回答问题",
is_active: 0,
parent_code: "subjective",
},
{
type_code: "question_type",
item_code: "analysis",
item_name: "分析题",
item_description: "需要分析问题",
is_active: 0,
parent_code: "subjective",
},
{
type_code: "question_type",
item_code: "essay",
item_name: "论述题",
item_description: "详细论述",
is_active: 0,
parent_code: "subjective",
},
// 用户角色
{
type_code: "user_role",
item_code: "admin",
item_name: "管理员",
item_description: "系统管理员",
is_active: 1,
parent_code: null,
},
{
type_code: "user_role",
item_code: "student",
item_name: "考生",
item_description: "参加考试的用户",
is_active: 1,
parent_code: null,
},
],
};
// 导出函数和常量供其他模块使用
module.exports = {
runAsync,
systemSchema,
userSchema,
defaultData
};

108
src/background/db/utils.js Normal file
View File

@ -0,0 +1,108 @@
const sqlite3 = require('sqlite3');
const { promisify } = require('util');
sqlite3.verbose();
// 数据库连接池简单实现
const dbConnections = new Map();
// 打开数据库并添加到连接池
async function openDatabase(dbPath) {
console.log(`打开数据库连接: ${dbPath}`);
// 检查是否已有连接
if (dbConnections.has(dbPath)) {
console.log(`使用现有数据库连接: ${dbPath}`);
return dbConnections.get(dbPath);
}
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error(`打开数据库失败: ${err.message}`);
reject(err);
return;
}
console.log(`成功打开数据库: ${dbPath}`);
// promisify数据库方法
db.getAsync = promisify(db.get).bind(db);
db.allAsync = promisify(db.all).bind(db);
// 自定义实现runAsync以获取lastID
db.runAsync = function(sql, params) {
return new Promise((resolve, reject) => {
this.run(sql, params, function(err) {
if (err) {
reject(err);
} else {
resolve({
lastID: this.lastID,
changes: this.changes
});
}
});
});
};
db.execAsync = promisify(db.exec).bind(db);
// 添加到连接池
dbConnections.set(dbPath, db);
resolve(db);
});
});
}
// 添加重试机制的执行函数
async function executeWithRetry(db, operation, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
return await operation();
} catch (error) {
if (error.code === 'SQLITE_BUSY' && retries < maxRetries - 1) {
retries++;
console.log(`数据库忙,正在重试 (${retries}/${maxRetries})...`);
await new Promise(resolve => setTimeout(resolve, 500 * retries)); // 指数退避
} else {
throw error;
}
}
}
}
// 修改batchInsert函数使用重试机制
async function batchInsert(db, table, data) {
if (!data || data.length === 0) {
return;
}
const columns = Object.keys(data[0]).join(', ');
const placeholders = data.map(() => {
return '(' + Object.keys(data[0]).map(() => '?').join(', ') + ')';
}).join(', ');
const values = [];
data.forEach(item => {
Object.values(item).forEach(value => {
values.push(value);
});
});
const sql = `INSERT INTO ${table} (${columns}) VALUES ${placeholders}`;
return executeWithRetry(db, async () => {
await db.runAsync(sql, values);
console.log(`成功插入 ${data.length} 条记录到 ${table}`);
});
}
// 导出函数供其他模块使用
module.exports = {
openDatabase,
batchInsert,
executeWithRetry
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,6 +7,9 @@ import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const bcrypt = require('bcryptjs')
const isDevelopment = process.env.NODE_ENV !== 'production'
// 导入数据库相关函数
const { checkDatabaseInitialized, initializeDatabase } = require('./db/index.js');
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
@ -18,7 +21,11 @@ async function createWindow() {
width: 800,
height: 600,
webPreferences: {
// 修改这一行
preload: require('path').join(__dirname, 'preload.js'),
// 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'),
nodeIntegration: false,
contextIsolation: true
}
@ -142,3 +149,22 @@ ipcMain.handle('hashTest', async (event, inputString) => {
throw error
}
})
// 数据库相关IPC接口
ipcMain.handle('check-database-initialized', async () => {
try {
return await checkDatabaseInitialized()
} catch (error) {
console.error('Failed to check database initialization:', error)
return false
}
})
ipcMain.handle('initialize-database', async () => {
try {
return await initializeDatabase()
} catch (error) {
console.error('Failed to initialize database:', error)
return false
}
})

View File

@ -1,7 +1,19 @@
// Preload script runs in a context that has access to both Node.js and browser APIs
const { contextBridge, ipcRenderer } = require('electron')
// Expose ipcRenderer to the renderer process through contextBridge
// 暴露API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 数据库相关
checkDatabaseInitialized: () => ipcRenderer.invoke('check-database-initialized'),
initializeDatabase: () => ipcRenderer.invoke('initialize-database'),
// 保留原有的ipcRenderer接口确保兼容性
ipcRenderer: {
invoke: (channel, data) => ipcRenderer.invoke(channel, data)
}
})
// 也保留原来的electron对象确保现有功能正常
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
invoke: (channel, data) => ipcRenderer.invoke(channel, data)

View File

@ -75,6 +75,61 @@
</span>
</el-dialog>
<!-- 数据库初始化测试 -->
<div class="database-test">
<h3>数据库初始化测试</h3>
<el-card class="card-test">
<template slot="header">
<div class="card-header">
<span>数据库状态</span>
</div>
</template>
<div class="database-status">
<el-alert
:title="`数据库状态: ${dbInitialized ? '已初始化' : '未初始化'}`"
:type="dbInitialized ? 'success' : 'warning'"
show-icon
:closable="false"
/>
</div>
<div v-if="!dbInitialized" class="initialize-button-container">
<el-button
type="primary"
size="large"
@click="initializeDatabase"
:loading="initializing"
style="margin-top: 20px;"
>
{{ initializing ? '正在初始化...' : '初始化数据库' }}
</el-button>
</div>
<div v-if="dbInitResult" class="init-result">
<el-alert
:title="dbInitResult.success ? '初始化成功' : '初始化失败'"
:type="dbInitResult.success ? 'success' : 'error'"
show-icon
:closable="false"
>
<template slot="desc" v-if="!dbInitResult.success">
失败原因: {{ dbInitResult.error }}
</template>
</el-alert>
</div>
<div v-if="dbInitError" class="init-error">
<el-alert
:title="`获取数据库状态失败: ${dbInitError}`"
type="error"
show-icon
:closable="false"
/>
</div>
</el-card>
</div>
<!-- Bootstrap组件测试 -->
<div class="bootstrap-test">
<h3>Bootstrap 组件测试</h3>
@ -200,7 +255,12 @@ export default {
{ id: 'search', label: '搜索', elementIcon: 'el-icon-search' },
{ id: 'edit', label: '编辑', elementIcon: 'el-icon-edit' },
{ id: 'delete', label: '删除', elementIcon: 'el-icon-delete' }
]
],
//
dbInitialized: null,
dbInitResult: null,
dbInitError: null,
initializing: false
}
},
methods: {
@ -243,7 +303,55 @@ export default {
// Bootstrap
showBootstrapModal() {
$('#bootstrapModal').modal('show')
},
//
async checkDatabaseInitialized() {
try {
this.dbInitError = null
// window.electronAPI
if (typeof window !== 'undefined' && window.electronAPI) {
const initialized = await window.electronAPI.checkDatabaseInitialized()
this.dbInitialized = initialized
} else {
throw new Error('Electron API 不可用')
}
} catch (err) {
this.dbInitError = err.message
console.error('检查数据库初始化状态失败:', err)
}
},
//
async initializeDatabase() {
try {
this.dbInitResult = null
this.initializing = true
// window.electronAPI
if (typeof window !== 'undefined' && window.electronAPI) {
const result = await window.electronAPI.initializeDatabase()
this.dbInitResult = result
//
if (result && result.success) {
this.dbInitialized = true
}
} else {
throw new Error('Electron API 不可用')
}
} catch (err) {
this.dbInitResult = { success: false, error: err.message }
console.error('数据库初始化失败:', err)
} finally {
this.initializing = false
}
}
},
//
async mounted() {
await this.checkDatabaseInitialized()
}
}
</script>
@ -311,6 +419,26 @@ pre {
margin-right: 10px;
}
/* 数据库测试样式 */
.database-test {
margin-top: 40px;
}
.database-status {
margin-bottom: 10px;
}
.initialize-button-container {
margin-top: 20px;
display: flex;
justify-content: center;
}
.init-result,
.init-error {
margin-top: 20px;
}
.bootstrap-test {
margin-top: 40px;
padding: 20px;

View File

@ -4,6 +4,8 @@ module.exports = {
nodeIntegration: false,
contextIsolation: true,
preload: 'src/preload.js',
// 添加这一行,指定主进程文件路径
mainProcessFile: 'src/background/main.js',
// If you want to use ESLint for your preload script,
// set lintPreloadFiles to true
lintPreloadFiles: false