Separate base database initialization from test data seeding

This commit is contained in:
biss committed 2026-07-20 19:49:29 +08:00
1 parent 5b86cdaec2
commit b8f2799ed6
11 files changed
+345 -43

No files matched your search

+5
View File
@@ -5,6 +5,11 @@ SQLITE_PATH=./data/exam.sqlite
HOST=127.0.0.1 HOST=127.0.0.1
PORT=4173 PORT=4173
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
INITIAL_ADMIN_USERNAME=admin
INITIAL_ADMIN_PASSWORD=Admin123!
INITIAL_ADMIN_DISPLAY_NAME=系统管理员
# MySQL 8.4 生产环境:将 DATABASE_CLIENT 改为 mysql,并配置以下变量。 # MySQL 8.4 生产环境:将 DATABASE_CLIENT 改为 mysql,并配置以下变量。
# NODE_ENV=production # NODE_ENV=production
# DATABASE_CLIENT=mysql # DATABASE_CLIENT=mysql
+9 -5
View File
@@ -87,9 +87,9 @@ npm start
打开 <http://127.0.0.1:4173>。 打开 <http://127.0.0.1:4173>。
本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构。当前数据库结构版本为 v15;v15 将线下缴费确认从报名审核中拆分,并增加缴费确认人、确认时间及名单导出。开发阶段可直接运行 `npm run reset-db` 重建完整数据库,不要求兼容旧数据;低于 v15 的开发库会提示重建,不执行兼容迁移。 本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构,但不会导入学校、考生、考试或报名测试数据。首次建库只写入系统基础配置和一个超级管理员;账号、密码和显示名可通过 `INITIAL_ADMIN_USERNAME`、`INITIAL_ADMIN_PASSWORD`、`INITIAL_ADMIN_DISPLAY_NAME` 设置。当前数据库结构版本为 v15;低于 v15 的开发库会提示重建,不执行兼容迁移。
需要清空并重建本地演示库时运行 `npm run reset-db`。省市区县下拉数据位于 `src/data/china-regions.mjs`,当前版本为国家地名信息库截至 2025-12-31 的三级快照,并补入和康县(653228)与和安县(653229);从新版 CSV 更新时可运行 `node scripts/build-regions.mjs <CSV路径> src/data/china-regions.mjs`。 需要清空并重建空业务库时运行 `npm run reset-db`。需要测试数据时再手动运行 `npm run seed-test-data`;该命令会覆盖本地 SQLite 数据库,生成 4 所学校、360 名批量考生及 360 条不同状态的报名数据,并明确不生成考场编排计划和准考证。省市区县下拉数据位于 `src/data/china-regions.mjs`,当前版本为国家地名信息库截至 2025-12-31 的三级快照,并补入和康县(653228)与和安县(653229);从新版 CSV 更新时可运行 `node scripts/build-regions.mjs <CSV路径> src/data/china-regions.mjs`。
## 数据库配置 ## 数据库配置
@@ -115,7 +115,7 @@ CREATE USER 'exam_app'@'%' IDENTIFIED BY 'replace-with-a-strong-password';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ON exam_information.* TO 'exam_app'@'%'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ON exam_information.* TO 'exam_app'@'%';
``` ```
启动应用时设置连接信息,应用会自动创建以下关系表并初始化演示数据: 启动应用时设置连接信息,应用会自动创建以下关系表和系统基础配置,但不会自动写入测试业务数据:
- `schools`、`school_classes`、`users`、`candidate_profiles` - `schools`、`school_classes`、`users`、`candidate_profiles`
- `exams`、`exam_subjects` - `exams`、`exam_subjects`
@@ -144,7 +144,9 @@ npm start
也可以只设置标准连接地址 `DATABASE_URL=mysql://user:password@host:3306/database`。完整模板见 `.env.example`;将模板复制为 `.env` 后取消 MySQL 配置项的注释并填写实际连接信息即可。生产部署仍建议由部署平台注入环境变量,避免在服务器文件中保存密码。 也可以只设置标准连接地址 `DATABASE_URL=mysql://user:password@host:3306/database`。完整模板见 `.env.example`;将模板复制为 `.env` 后取消 MySQL 配置项的注释并填写实际连接信息即可。生产部署仍建议由部署平台注入环境变量,避免在服务器文件中保存密码。
## 演示账号 ## 手动测试数据账号
测试数据脚本会提供以下账号;其中初始超级管理员也可能由正常首次建库创建,并可通过环境变量改名、改密,其余校级、班级和考生账号不会在正常启动时创建:
| 角色 | 账号 | 密码 | | 角色 | 账号 | 密码 |
| --- | --- | --- | | --- | --- | --- |
@@ -176,7 +178,9 @@ server.mjs HTTP 服务启动、模块装配与静态文
database.mjs 数据仓储与数据库模块装配 database.mjs 数据仓储与数据库模块装配
excel.mjs Excel 模板、导入解析与导出工作簿 excel.mjs Excel 模板、导入解析与导出工作簿
src/data/seed.mjs 演示数据 src/data/base.mjs 空业务库与系统基础配置
src/data/seed.mjs 手动测试数据生成器
scripts/import-test-data.mjs 独立测试数据导入脚本
src/http/responses.mjs JSON、文件与请求体处理 src/http/responses.mjs JSON、文件与请求体处理
src/security/session.mjs Cookie 会话与当前用户 src/security/session.mjs Cookie 会话与当前用户
src/security/authorization.mjs 管理层级、权限和数据范围 src/security/authorization.mjs 管理层级、权限和数据范围
-6
View File
@@ -120,12 +120,6 @@ document.addEventListener('click', async event => {
state.user = null; state.profile = null; state.pageData = null; state.permissions = []; state.scopeLabel = ''; state.user = null; state.profile = null; state.pageData = null; state.permissions = []; state.scopeLabel = '';
await refreshPublic(); navigate('home'); toast('已安全退出', '期待下次见面'); return; await refreshPublic(); navigate('home'); toast('已安全退出', '期待下次见面'); return;
} }
if (action === 'fill-demo') {
const form = document.querySelector('[data-form="login"]');
const accounts = { admin: ['admin', 'Admin123!'], school: ['school_admin', 'School123!'], class: ['class_admin', 'Class123!'], candidate: ['2026-HZ01-F-0001', 'Candidate123!'] };
[form.username.value, form.password.value] = accounts[target.dataset.type] || accounts.candidate;
return;
}
if (action === 'open-notice') { if (action === 'open-notice') {
const notice = state.publicData.notices.find(item => item.id === target.dataset.id) || (await api(`/api/public/notices/${target.dataset.id}`)).notice; const notice = state.publicData.notices.find(item => item.id === target.dataset.id) || (await api(`/api/public/notices/${target.dataset.id}`)).notice;
const contentHtml = notice.contentHtml || `<p>${h(notice.content).replace(/\r?\n/g, '</p><p>')}</p>`; const contentHtml = notice.contentHtml || `<p>${h(notice.content).replace(/\r?\n/g, '</p><p>')}</p>`;
Loaded 3 of 11 files, more files were not shown because too many files have changed in this diff. Show more