Repository navigation
Database
dxx114514-stack edited this page Oct 16, 2026
·
5 revisions
| 项目 | 说明 |
|---|---|
| 引擎 |
Node.js 内置 node:sqlite(v1.8.0 起,原生同步直写、零 npm 依赖;此前为 sql.js WASM) |
| 文件 |
backend/data/NoldOJ.db(路径由 backend/config/config.js 的 database.path 决定) |
| 迁移 | 启动时 initDB() 自动执行全部 schema 迁移(新增表/列),无需手工改库 |
| 驱动要求 | Node >= 24(node:sqlite 为内置模块,无需 npm install 数据库驱动) |
运行后数据库目录会出现 NoldOJ.db-wal / NoldOJ.db-shm 文件(WAL 模式,崩溃安全、读写更快),属正常现象,请勿手动删除。
backend/data/
├── NoldOJ.db # SQLite 主库(用户/题目/提交/比赛等全部结构化数据)
├── NoldOJ.db-wal # WAL 日志(勿删)
├── NoldOJ.db-shm # WAL 共享内存索引(勿删)
└── problems/ # 题目测试数据(.in/.out 等,与库同级便于整体备份)
- 上传文件等运行时数据在仓库根
data/(自动创建)。 - 首次启动若根目录存在旧的
problems/且backend/data/problems/不存在,start.bat会自动迁移。
- 建库并建全部默认表、语言表,预置 10 个算法标签与成就(不含示例题目,题库从空开始)。
- 创建默认管理员
admin:随机初始口令,仅启动日志log/<时间戳>/server.log显示一次;测试/CI 可用NoldOJ_INIT_ADMIN_PASSWORD指定(见 配置说明)。 -
config/jwt.txt缺失时自动生成 JWT 密钥。
用户组与实体可见性由三张新表 + 四张实体表各一列 构成,全部由 initDB() 在启动时建出,老库升级同样自动完成,无需手工改库。
| 表 | 字段 | 说明 |
|---|---|---|
groups |
id / name / description / kind / owner_id / created_at
|
用户组。kind 取 custom(管理员自建)、personal(个人组,每人一个,显示名=用户名)、all_users(全体注册用户系统组,全局唯一一个) |
user_groups |
user_id / group_id / joined_at,主键 (user_id, group_id)
|
用户与组的成员关系 |
resource_group_access |
resource_type / resource_id / group_id,主键三元组 |
实体与组的可见性绑定。resource_type 取 problem / contest / exam / problem_set,仅在实体 visibility='groups' 时生效
|
索引(均为 IF NOT EXISTS 幂等建):
| 索引 | 定义 | 作用 |
|---|---|---|
idx_groups_personal |
groups(owner_id) WHERE kind='personal'(唯一) |
保证每人至多一个个人组 |
idx_groups_all_users |
groups(kind) WHERE kind='all_users'(唯一) |
保证全体组全局只有一个 |
idx_user_groups_group |
user_groups(group_id) |
按组反查成员 |
idx_rga_resource |
resource_group_access(resource_type, resource_id) |
按实体取绑定组 |
三张表之间没有外键:删组时由
repositories/groups.js在一个事务里手工删除user_groups与resource_group_access的对应行后再删groups。
problems / contests / exams / problem_sets 四张表各新增同一列:
visibility TEXT NOT NULL DEFAULT 'public' CHECK(visibility IN ('public','groups'))-
public(默认):所有人可见,匿名访客也算。 -
groups:仅resource_group_access中绑定的组成员可见;teacher/admin/su 一律放行(教师必须能看到实体,才能把实体授权给某个组)。 - 旧的
is_public(题目/题单的公开标记)与is_hidden(试卷的隐藏开关)原样保留,未被替换或删除。 - 迁移对四张表逐一检查列是否存在再
ALTER TABLE ... ADD COLUMN(幂等);DEFAULT 'public'本身就让存量实体保持公开,不需要额外的回填语句。
CREATE TRIGGER IF NOT EXISTS trg_users_default_groups AFTER INSERT ON users
BEGIN
-- 1) 建个人组(name=NEW.username, kind='personal', owner_id=NEW.id)
-- 2) UPDATE 把复用同号时残留的旧用户名改回新用户名
-- 3) 把自己加进个人组
-- 4) 把自己加进全体组(kind='all_users')
END;- 触发器挂在
users的AFTER INSERT上,任何建号途径(注册接口、管理员建号、初始admin、后台脚本、测试直接 INSERT)都会自动加入「所有用户」与个人两个组,不依赖某一条注册代码路径的配合。 - 用
INSERT OR IGNORE+ 按唯一键回查组 id(而不是last_insert_rowid()):users.id可被复用(删号后新建会取回同号),重复入组不该让注册语句因唯一索引冲突而失败。 - 系统组的成员关系因此没有自助变更入口 ——
routes/groups.js对系统组的改名、删除、增减成员一律返回 403ERR_FORBIDDEN。
initDB() 每次启动按固定顺序执行,全部幂等、可反复运行:
-
tableExists检查三张新表,缺失则建表(DDL 与schema.sql逐字一致;两处都放:新库走schema.sql全量建表,老库走这段兜底自愈)。 -
无条件重建索引与触发器(
CREATE INDEX IF NOT EXISTS/CREATE TRIGGER IF NOT EXISTS),覆盖老库缺表自愈、以及触发器随版本更新的路径。 -
补
visibility列:四张实体表缺列时逐表ALTER TABLE。 -
回填存量成员关系:不存在则建「所有用户」组,再遍历
users为每个存量用户建个人组并写入两条成员关系(个人组 + 全体组)。触发器只覆盖本次启动之后的新建号,这里负责存量用户(含本次启动创建的admin)。
备份(务必先停服):
- 关闭
start.bat窗口(或执行stop.bat)。 - 整体复制
backend/data/目录(至少包含NoldOJ.db*与problems/)。
恢复:停服后用备份覆盖回 backend/data/,再启动。
单库校验:
node scripts\migrate-sqlite.js该脚本会自动备份 NoldOJ.db → NoldOJ.db.backup-<时间戳>、执行 PRAGMA integrity_check 完整性校验、统计全部业务表行数。
sql.js 导出的文件本身就是标准 SQLite 格式,旧数据无需转换,node:sqlite 可直接打开、原样保留。
- 停止服务(务必先停,避免文件占用)。
git pull-
cd backend && npm install(无需数据库驱动) - (可选但推荐)
node scripts\migrate-sqlite.js—— 备份 + 完整性校验 + 数据统计 -
start.bat—— 启动时自动完成 schema 迁移
回滚:用第 4 步生成的备份覆盖回 NoldOJ.db 即可。
- 需要数据库的测试不读写生产库:自动使用
%TEMP%\NoldOJ-test\下的独立库,测试中断也不留脏数据。 - 可用
DB_PATH环境变量指定其他库路径(见 测试与 CI)。
NoldOJ · Windows 在线评测系统 · MIT License