Skip to content

数据库与 Redis 数据字典 ​

本页整理 Neuro OJ 在 PostgreSQL 中的各表、字段,以及 Redis 中各键族的具体作用,供运营者排查、审计与数据维护时参考。

免责声明:本文档不保证完全准确

本文档由开发资料整理而成,不保证与当前部署的代码、Schema 完全一致,且会随开发迭代而变动,恕不另行通知。

  • 本文档是尽力而为(best-effort)的运维参考,不是契约、不是自动化校验产物,也不随版本发布同步保证。
  • 实际字段、约束、默认值一律以线上数据库的 \d+ <表名> 与源码 noj-core/src/shared/db/schema/、noj-llm-gateway/src/db/schema.ts 为准。
  • 执行任何 UPDATE / DELETE / ALTER / DROP / TRUNCATE 或 Redis 的 DEL / FLUSHDB 等操作前,必须二次复核验证,并先完成备份。

如何使用本页 ​

  • 字段表读法:列 为数据库中的真实列名;类型 为 PostgreSQL 类型;约束 / 默认 汇总主键(PK)、唯一(UK)、外键(FK)、CHECK、NOT NULL 与默认值;说明 给出业务语义。
  • 索引清单读法:各表小节中的 索引: / 「复合索引:」行只列主要索引(不完整)——为避免逐表穷举带来的漂移,这里只给出排障最常用的几条;完整清单以 \d+ <表名> 为准(例如 submissions 实际有 8 个索引,该节只列了 3 个)。
  • 风险标记:
    • 红色列名 表示敏感/危险列(凭据、密钥、密文、用户内容、风控数据),读取或导出前需脱敏。
    • DANGER ZONE 徽标表示该表整体属于高风险区,误操作可能造成凭据泄露、账号接管或不可逆数据损坏。
  • 时间字段约定:本项目的所有时间戳以 ISO 8601 文本存储(如 2026-09-24T12:00:00.000Z),而非原生 timestamptz。竞赛时间字段受 CHECK 约束强制为「UTC + 毫秒 + Z」形态。
  • 分数约定:score 为 ×100 的整数(例如 85.5 分存为 8550),应用层通过 scoreToDb / scoreFromDb 转换,避免浮点误差。
  • 多数据库连接:noj-core、noj-llm-gateway 共用同一个 PostgreSQL 库。LLM 三张表物理上在同库,但逻辑所有权归 noj-llm-gateway(迁移由 noj-llm-gateway/drizzle/ 管理),详情见下文「LLM 网关表(noj-llm-gateway)」。

DANGER ZONE 一览 ​

以下对象属于一碰就要格外小心的高风险区。完整字段见后文对应小节,这里先给出清单与后果。

对象风险点误操作后果
llm_providersencrypted_api_key 为上游 LLM 服务的 API Key(AES-256-GCM 信封加密)泄露即上游账单被盗刷;误删导致所有 LLM 题评测失败
userspassword_hash、tfa_secret_encrypted、email_verify_token泄露可离线爆破 / 绕过二次验证;误改 session_version 会强制该用户全部会话下线
password_reset_tokenstoken_hash(密码重置令牌哈希)泄露可接管账号
tfa_recovery_codescode_hash(TFA 恢复码哈希)泄露可绕过 TOTP
oauth_accounts第三方身份 ↔ 本地账号映射误删导致 OAuth 用户失去登录入口
system_settings含 is_secret=true 的密钥类配置行泄露密钥;误改影响全局运行时行为(缓存需「重新加载」而非仅删缓存)
audit_logs全站审计留痕误删破坏合规证据链
submissions / self_testscode、artifact_storage_url用户代码/产物可能含隐私与密钥
ip_bans / user_bans / contest_clarifications风控与申诉数据误删放开攻击者;误加封禁误伤用户
search_entriesbody 含私信原文(含撤回前内容)整表导出等同于导出用户私信,必须脱敏
community_posts / community_comments / community_reports用户生成内容与举报数据泄露用户隐私;误删破坏审核证据
messages私信原文与撤回状态泄露私信;误删破坏会话完整性
Redis 会话/限流键见下文「Redis 键族」DEL jwt:revoked:* 会让已撤销令牌复活;DEL 评测队列会丢评测任务

PostgreSQL 表 ​

以下按业务域分组。表名即数据库中的真实表名(public schema,除非另有说明)。

身份与 RBAC ​

users DANGER ZONE ​

注册用户主表,存储账号凭据、资料、TFA 与软删除状态。

列类型约束 / 默认说明
idtextPK用户 UUID;系统 root 用户固定为 0
usernametextNOT NULL;部分唯一索引 users_active_username_unique(仅 deleted_at IS NULL)用户名;已注销用户名可被重新占用
emailtextNOT NULL;UNIQUE登录邮箱
email_verifiedbooleanNOT NULL;DEFAULT true本地注册邮箱是否已验证;历史迁移账号默认 true
email_verify_tokentextNULL当前验证链接的 SHA-256 哈希(不存明文令牌)
email_verify_expires_attextNULL验证链接过期时间(ISO 8601)
password_hashtextNULLbcrypt(cost 12) 哈希;OAuth 新用户补设密码前为 NULL
session_versionintegerNOT NULL;DEFAULT 0凭据变更时原子递增,用于撤销该用户此前签发的全部会话
biotextNOT NULL;DEFAULT ''个人简介(Markdown,上限 5000 字符)
must_change_passwordbooleanNOT NULL;DEFAULT false引导管理员等账号下次登录须改密;中间件对非白名单请求返 403
community_activity_visibilitytextNOT NULL;DEFAULT 'following';CHECK hidden/following/everyone社区活动可见范围
avatar_urltextNULL头像存储地址(noj-storage:// 格式)
tfa_secret_encryptedtextNULLTOTP secret 的 AES-256-GCM 密文
tfa_enabledbooleanNOT NULL;DEFAULT false是否已启用二次验证
deleted_attextNULL软删除时间;非 NULL 表示账号已注销、不可登录
created_at / updated_attextNOT NULL创建 / 更新时间
search_vectortsvectorGENERATED ALWAYS(username A + email B)全文检索列,GIN 索引 idx_users_search_vector

运维注意

  • 不要直接改写 password_hash 或 session_version:前者会破坏登录校验,后者会强制目标用户全部会话失效。
  • 注销(软删除)走 deleted_at,不要物理删除:部分表为 CASCADE(会连带删除用户内容),另有 submissions / self_tests / messages.sender_id / conversations 等为 NO ACTION,物理删除会因外键违约直接失败回滚,无法作为清理手段。

oauth_accounts DANGER ZONE ​

第三方(OAuth)身份与本地账号的关联表,不保存 provider 的 access/refresh token。

列类型约束 / 默认说明
idtextPK关联记录 UUID
providertextNOT NULL;与 provider_user_id 共同 UNIQUEprovider 标识
provider_user_idtextNOT NULLprovider 侧稳定用户 ID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引 idx_oauth_accounts_user_id本地用户
provider_usernametextNULLprovider 侧用户名快照
provider_emailtextNULLprovider 侧邮箱快照
email_verifiedbooleanNOT NULL;DEFAULT falseprovider 侧邮箱是否已验证
created_at / updated_attextNOT NULL创建 / 更新时间

check_ins ​

每日签到记录,每用户每天一条。

列类型约束 / 默认说明
idtextPK记录 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE用户
checkin_datetextNOT NULL;与 user_id 共同 UNIQUE签到日期(YYYY-MM-DD,UTC)
streakintegerNOT NULL;DEFAULT 1连续签到天数
created_attextNOT NULL签到时间

password_reset_tokens DANGER ZONE ​

密码重置短期令牌;DB 只存哈希,URL 传明文。

列类型约束 / 默认说明
idtextPK记录 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引用户
token_hashtextNOT NULL;UNIQUE令牌 SHA-256 hex 哈希(不存明文)
expires_attextNOT NULL过期时间(created_at + 15 分钟)
used_attextNULL使用时间;NULL=未使用(单 SQL 原子消耗)
created_attextNOT NULL创建时间

tfa_recovery_codes DANGER ZONE ​

TFA 一次性恢复码;只存哈希。

列类型约束 / 默认说明
idtextPK记录 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引用户
code_hashtextNOT NULL恢复码 SHA-256 hex 哈希
used_attextNULL使用时间;NULL=未使用
created_attextNOT NULL创建时间

user_bans DANGER ZONE ​

用户封禁记录,每条代表一次封禁操作(历史可追溯)。

列类型约束 / 默认说明
idtextPK记录 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引被封禁用户
reasontextNOT NULL;DEFAULT ''封禁原因
scopetextNOT NULL;DEFAULT 'platform';CHECK platform/socialplatform=限制登录/评测等一切写操作;social=仅限制社区发布
banned_untiltextNULL解封时间;NULL=永久
banned_attextNOT NULL封禁时间
banned_bytextFK→users(id) ON DELETE SET NULL操作人
updated_attextNOT NULL更新时间
unbanned_attextNULL解封时间;NULL=当前活跃封禁
unbanned_bytextFK→users(id) ON DELETE SET NULL解封操作人

索引:idx_user_bans_user、部分索引 idx_user_bans_active(unbanned_at IS NULL)。

roles ​

RBAC 角色定义,支持继承与标记位。

列类型约束 / 默认说明
idtextPK角色 UUID
nametextNOT NULL;UNIQUE角色名
descriptiontextNOT NULL;DEFAULT ''描述
is_systembooleanNOT NULL;DEFAULT false系统保护角色,不可删除
is_defaultbooleanNOT NULL;DEFAULT false注册时自动分配
parent_idtextFK→roles(id) ON DELETE SET NULL;索引父角色(继承)
created_at / updated_attextNOT NULL创建 / 更新时间

管理员判定

roles.is_admin 列已于迁移 0032 删除。当前「管理员」判定 = 用户权限集包含 admin:full_access。

permissions ​

RBAC 权限定义表,权限以 resource:action 唯一标识。

列类型约束 / 默认说明
idtextPK权限 UUID
resourcetextNOT NULL;与 action 共同 UNIQUE资源域(如 problem、system)
actiontextNOT NULL动作(如 create、settings)
descriptiontextNOT NULL;DEFAULT ''描述

role_permissions ​

角色-权限多对多关联。

列类型约束 / 默认说明
role_idtextNOT NULL;FK→roles(id) ON DELETE CASCADE;与 permission_id 共同 PK角色
permission_idtextNOT NULL;FK→permissions(id) ON DELETE CASCADE权限

user_roles ​

用户-角色多对多关联。

列类型约束 / 默认说明
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;与 role_id 共同 PK用户
role_idtextNOT NULL;FK→roles(id) ON DELETE CASCADE角色

题目、标签与题单 ​

problems ​

题目主表。测试数据由支持包 zip 内的评测脚本自行管理,不在本表。

列类型约束 / 默认说明
idtextPK题目 UUID
titletextNOT NULL标题
descriptiontextNOT NULL题面(Markdown)
difficultytextNOT NULL;DEFAULT 'medium'难度
support_package_storage_urltextNULL支持包存储地址(noj-storage://)
runtime_configjsonbNULL;CHECK 为对象双容器运行时配置(Evaluator + Solution);客观题套卷为 NULL
numberintegerNOT NULL;与 type 共同 UNIQUE题号(同 type 内自增)
owner_idtextNOT NULL;DEFAULT '0'题目所有者;默认 root
typetextNOT NULL;DEFAULT 'U';CHECK U/PU=用户题库,P=主题库
is_objectivebooleanNOT NULL;DEFAULT falsetrue=客观题套卷(服务端即时判定,无评测容器)
visibilitytextNOT NULL;DEFAULT 'public';CHECK public/private(且 P 型恒 public)可见性
submission_modetextNOT NULL;DEFAULT 'code';CHECK code/artifact提交模式:单文件代码 / zip 产物
artifact_max_size_mbintegerNULLartifact 提交大小上限(MB);NULL=使用系统硬上限
llm_configjsonbNULL题目 LLM 能力与预算声明(provider_id/model/max_calls/max_tokens)
created_at / updated_attextNOT NULL创建 / 更新时间
search_vectortsvectorGENERATED ALWAYS(title A + type/number B)全文检索列,GIN 索引

objective_questions ​

客观题小题,必须绑定所属套卷。

列类型约束 / 默认说明
idtextPK小题 UUID
paper_idtextNOT NULL;FK→problems(id) ON DELETE CASCADE;索引所属套卷(is_objective=true)
sort_orderintegerNOT NULL;DEFAULT 0;与 paper_id 共同 UNIQUE卷内排序
typetextNOT NULL;CHECK single/multiple/judge单选 / 多选 / 判断
prompttextNOT NULL题干(Markdown)
optionsjsonbNOT NULL;DEFAULT []选项数组 [{key,text}];判断题为空数组
answerjsonbNOT NULL标准答案,如 ["A"] / ["A","C"] / [true]
explanationtextNOT NULL;DEFAULT ''答案解析(判卷后展示)
created_at / updated_attextNOT NULL创建 / 更新时间

judge_images ​

评测镜像白名单,管理员维护。

列类型约束 / 默认说明
idtextPK记录 UUID
imagetextNOT NULL镜像名(可含标签)
modetextNOT NULL;DEFAULT 'exact';CHECK exact/all_versions精确匹配 / 匹配同名所有版本
kindtextNOT NULL;DEFAULT 'evaluator';CHECK evaluator/solution镜像角色:Evaluator / Solution
descriptiontextNOT NULL;DEFAULT ''题目编辑器下拉中展示的介绍
created_at / updated_attextNOT NULL创建 / 更新时间

tags ​

双类扁平标签(category 系统已退役)。

列类型约束 / 默认说明
idtextPK标签 UUID
nametextNOT NULL;UNIQUE(全局唯一,跨 kind)标签名
kindtextNOT NULL;CHECK problem/algorithmproblem=题目标签(人人可见);algorithm=算法标签(通过题目后可见)
created_at / updated_attextNOT NULL创建 / 更新时间

problem_tags ​

题目-标签多对多关联。

列类型约束 / 默认说明
problem_idtextNOT NULL;FK→problems(id) ON DELETE CASCADE;与 tag_id 共同 PK题目
tag_idtextNOT NULL;FK→tags(id) ON DELETE CASCADE标签

trainings ​

题单主表。

列类型约束 / 默认说明
idtextPK题单 UUID
public_idtextNOT NULL;UNIQUE;DEFAULT tr- + 8 位随机对外短 ID
titletextNOT NULL标题
descriptiontextNOT NULL;DEFAULT ''描述
visibilitytextNOT NULL;DEFAULT 'private';CHECK private/unlisted/public可见性
is_pinnedbooleanNOT NULL;DEFAULT false是否置顶
created_bytextNOT NULL;FK→users(id) ON DELETE CASCADE;索引创建者
created_at / updated_attextNOT NULL创建 / 更新时间

training_problems ​

题单-题目关联。

列类型约束 / 默认说明
training_idtextNOT NULL;FK→trainings(id) ON DELETE CASCADE;与 problem_id 共同 PK题单
problem_idtextNOT NULL;FK→problems(id) ON DELETE CASCADE题目
positionintegerNOT NULL;DEFAULT 0;与 training_id 共同 UNIQUE题单内位置

提交与评测 ​

submissions DANGER ZONE ​

提交记录,状态流转 pending → judging → finished/error。

列类型约束 / 默认说明
idtextPK提交 UUID
public_idtextNOT NULL;UNIQUE;DEFAULT sub- + 8 位随机对外短 ID
user_idtextNOT NULL;FK→users(id);索引提交者
problem_idtextNOT NULL;FK→problems(id);索引题目
contest_idtextFK→contests(id) ON DELETE SET NULL;索引竞赛上下文;练习为 NULL
languagetextNOT NULL语言标识
codetextNOT NULL提交的代码原文(上限 100KB)
file_nametextNULL提交文件名
artifact_storage_urltextNULLartifact 提交的存储地址(noj-storage://);code 模式为 NULL
client_iptextNULL风控用来源 IP(可信代理解析,无法安全解析时为 NULL)
statustextNOT NULL;DEFAULT 'pending'评测状态
rejudge_seqintegerNOT NULL;DEFAULT 0重测序列号,防止新旧结果竞态覆盖
judge_started_attextNULL开始评测时间
judge_finished_attextNULL完成评测时间
created_attextNOT NULL创建时间

主要索引(不完整,完整清单见 \d+ submissions):idx_submissions_user_id_created_at、idx_submissions_contest_problem_user、idx_submissions_contest_client_ip;另有 idx_submissions_user_id / idx_submissions_problem_id / idx_submissions_status / idx_submissions_created_at / idx_submissions_contest_id。

artifact 提交

artifact 提交在评测完成后会立即删除存储对象,不支持重测。注意 evaluation_results.submission_id 外键为 NO ACTION(drizzle 源码未声明 onDelete,0007_fk_cascade.sql 未进 _journal.json、从未生效):若提交仍有评测结果行,直接 DELETE submissions 会因外键违约失败;需先删除对应 evaluation_results(或走管理端删除流程)。

evaluation_results ​

评测结果,与提交 1:1。

列类型约束 / 默认说明
idtextPK结果 UUID
submission_idtextNOT NULL;FK→submissions(id);唯一索引 idx_eval_results_submission_id对应提交(1:1)
statustextNOT NULL结果状态(finished / error)
scoreintegerNOT NULL;DEFAULT 0分数 ×100
outputtextNOT NULL;DEFAULT ''评测输出(API 返回时截断至 8KB,DB 保留完整内容)
detailstextNOT NULL;DEFAULT '{}'逐用例详情(JSON 字符串),含 cases[].hidden 隐藏标记
time_msintegerNULL耗时(毫秒)
memory_kbintegerNULL内存(KB)
created_attextNOT NULL创建时间

结果写入使用 UPSERT(onConflictDoUpdate)语义,并配合 rejudge_seq 防止乱序覆盖。

self_tests ​

自测记录,与正式提交完全隔离(不参与统计/榜单/活动)。

列类型约束 / 默认说明
idtextPK自测 UUID
user_idtextNOT NULL;FK→users(id);索引用户
problem_idtextNOT NULL;FK→problems(id);索引题目
languagetextNOT NULL语言
codetextNOT NULL自测代码原文
file_nametextNULL文件名
statustextNOT NULL;DEFAULT 'pending';CHECK pending/judging/finished/error自测状态
result_statustextNULL结果状态(finished / error)
scoreintegerNOT NULL;DEFAULT 0分数 ×100
outputtextNOT NULL;DEFAULT ''输出
detailstextNOT NULL;DEFAULT '{}'详情 JSON 字符串
time_ms / memory_kbintegerNULL耗时 / 内存
judge_started_at / judge_finished_attextNULL评测起止时间
created_attextNOT NULL创建时间

sse_events ​

SSE 事件日志,供断线重连回放(id 作为 Last-Event-ID)。

列类型约束 / 默认说明
idserialPK全局单调自增 ID
channeltextNOT NULL;索引 idx_sse_events_channel_id(channel, id)事件频道
payloadjsonbNOT NULL事件负载
created_attextNOT NULL创建时间

保留策略

超过 7 天的事件由后台任务每日清理(硬编码常量 SSE_EVENT_RETENTION_DAYS,不可配置);超期客户端改以 REST 全量校准。

竞赛与客观题提交 ​

contests ​

竞赛主表,状态由 start_time / end_time 动态计算,不持久化状态字段。

列类型约束 / 默认说明
idtextPK竞赛 UUID
public_idtextNOT NULL;UNIQUE;DEFAULT ct- + 8 位随机对外短 ID
titletextNOT NULL标题
descriptiontextNOT NULL;DEFAULT ''描述
start_time / end_timetextNOT NULL;CHECK 形态(UTC+毫秒+Z)且 end_time > start_time起止时间
ranking_visibilitytextNOT NULL;DEFAULT 'public';CHECK public/participants/hidden榜单可见性
freeze_start_timetextNULL;CHECK 形态封榜开始时间
freeze_duration_secondsintegerNOT NULL;DEFAULT 0;CHECK >= 0结束前冻结时长(秒);0=不封榜
typetextNOT NULL;CHECK kaggle竞赛类型
kindtextNOT NULL;DEFAULT 'public';CHECK public/invite公开 / 邀请制
configjsonbNOT NULL;DEFAULT {};CHECK 为对象竞赛配置(含 submission_limits 等)
is_publicbooleanNOT NULL;DEFAULT true是否公开可见
passwordtextNULL邀请码 / 口令
affect_global_rankingbooleanNOT NULL;DEFAULT false是否计入全局排行
created_bytextFK→users(id) ON DELETE SET NULL;索引创建者
announcementtextNOT NULL;DEFAULT ''竞赛公告
created_at / updated_attextNOT NULL创建 / 更新时间

contest_problems ​

竞赛-题目关联,含题号标签与分值。

列类型约束 / 默认说明
contest_idtextNOT NULL;FK→contests(id) ON DELETE CASCADE;与 problem_id 共同 PK竞赛
problem_idtextNOT NULL;FK→problems(id) ON DELETE CASCADE题目
sort_orderintegerNOT NULL;DEFAULT 0;与 contest_id 共同 UNIQUE排序
labeltextNOT NULL;与 contest_id 共同 UNIQUE题号标签(如 A、B)
scoreintegerNOT NULL分值

contest_participants ​

竞赛参与者。

列类型约束 / 默认说明
contest_idtextNOT NULL;FK→contests(id) ON DELETE CASCADE;与 user_id 共同 PK竞赛
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引用户
registered_attextNOT NULL报名时间

contest_clarifications DANGER ZONE ​

竞赛答疑。

列类型约束 / 默认说明
idtextPK答疑 UUID
contest_idtextNOT NULL;FK→contests(id) ON DELETE CASCADE;索引竞赛
problem_idtextFK→problems(id) ON DELETE SET NULL关联题目(可选)
sender_idtextNOT NULL;FK→users(id)发送者
contenttextNOT NULL内容
reply_to_idtextFK→contest_clarifications(id)引用回复
is_publicbooleanNOT NULL;DEFAULT false是否公开
created_attextNOT NULL创建时间

contest_ranking_snapshots ​

正式竞赛成绩快照,每次发布修订新增版本,历史版本不可变。

列类型约束 / 默认说明
idtextPK快照 UUID
contest_idtextNOT NULL;FK→contests(id) ON DELETE CASCADE;与 version 共同 UNIQUE竞赛
versionintegerNOT NULL版本号
statustextNOT NULL;DEFAULT 'published'状态
notetextNOT NULL;DEFAULT ''备注
rowsjsonbNOT NULL榜单行数据
created_bytextFK→users(id) ON DELETE SET NULL发布人
created_attextNOT NULL创建时间

objective_submissions ​

客观题提交,服务端即时判定(不走评测队列)。

列类型约束 / 默认说明
idtextPK提交 UUID
paper_idtextNOT NULL;FK→problems(id) ON DELETE CASCADE;索引套卷
user_idtextNOT NULL;FK→users(id);索引用户
contest_idtextFK→contests(id) ON DELETE SET NULL;索引竞赛上下文;练习为 NULL
submission_typetextNOT NULL;CHECK practice/contest提交模式
answersjsonbNOT NULL用户答案 {question_id: [...]}
statustextNOT NULL;DEFAULT 'finished'即时判定完成
scoreintegerNOT NULL;DEFAULT 0卷面分 ×100(0–10000)
detailsjsonbNOT NULL;DEFAULT {}逐题判定 {question_id:{correct,expected,given}}
created_attextNOT NULL创建时间

唯一约束 objective_submissions_contest_unique(paper_id, user_id, contest_id):同一竞赛内同一用户对同一套卷仅一条。

私信 ​

conversations ​

私信会话;每对用户一个会话。

列类型约束 / 默认说明
idtextPK会话 UUID
user1_idtextNOT NULL;FK→users(id);与 user2_id 共同 UNIQUE;CHECK user1_id < user2_id参与者(字典序小)
user2_idtextNOT NULL;FK→users(id)参与者(字典序大)
last_message_attextNOT NULL;索引最后消息时间(列表排序用,反范式缓存)
created_attextNOT NULL创建时间

messages DANGER ZONE ​

私信消息,支持文本/图片、回复、转发、编辑、撤回。

列类型约束 / 默认说明
idtextPK消息 UUID
conversation_idtextNOT NULL;FK→conversations(id) ON DELETE CASCADE;索引会话
sender_idtextNOT NULL;FK→users(id);索引发送者
typetextNOT NULL;DEFAULT 'text';CHECK text/image消息类型
image_urltextNULL图片存储地址(noj-storage://);type=image 时必填
reply_to_message_idtextFK→messages(id) ON DELETE SET NULL引用的消息
forwarded_from_user_idtextFK→users(id) ON DELETE SET NULL转发来源
contenttextNOT NULL文本内容
created_attextNOT NULL创建时间
edited_attextNULL编辑时间
recalled_attextNULL撤回时间
edit_historytextNULL编辑历史 JSON 数组(仅后台保存,不对外展示)

message_reactions ​

消息表情反应;同一用户对同一消息可加多个不同表情。

列类型约束 / 默认说明
message_idtextNOT NULL;FK→messages(id) ON DELETE CASCADE;索引消息
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE用户
emojitextNOT NULL表情(取自固定集合)
created_attextNOT NULL创建时间

PK 为 (message_id, user_id, emoji)。

conversation_reads ​

会话已读位置。

列类型约束 / 默认说明
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE用户
conversation_idtextNOT NULL;FK→conversations(id) ON DELETE CASCADE会话
last_read_message_idtextNULL最后已读消息 ID;NULL=从未阅读
updated_attextNOT NULL更新时间

PK 为 (user_id, conversation_id)。

message_deletions ​

单用户视角的消息删除记录(原始消息仍保留)。

列类型约束 / 默认说明
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE用户
message_idtextNOT NULL;FK→messages(id) ON DELETE CASCADE;索引消息
deleted_attextNOT NULL删除时间

PK 为 (user_id, message_id)。

conversation_preferences ​

每用户每会话的偏好(备注名 / 免打扰)。

列类型约束 / 默认说明
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE用户
conversation_idtextNOT NULL;FK→conversations(id) ON DELETE CASCADE;索引会话
remark_nametextNULL备注名;NULL/空=使用真实用户名
is_mutedbooleanNOT NULL;DEFAULT false消息免打扰
updated_attextNOT NULL更新时间

PK 为 (user_id, conversation_id)。

社区 ​

社区共 13 张表。内容类表(community_posts / community_comments)含用户生成内容,属敏感数据。

community_boards ​

社区讨论板块。

列类型约束 / 默认说明
idtextPK板块 UUID
slugtextNOT NULL;UNIQUE短标识
nametextNOT NULL名称
descriptiontextNOT NULL;DEFAULT ''描述
sort_orderintegerNOT NULL;DEFAULT 0排序
is_archivedbooleanNOT NULL;DEFAULT false是否归档
created_at / updated_attextNOT NULL创建 / 更新时间

community_board_role_grants ​

板块级角色授权;无记录时沿用全局社区 RBAC。

列类型约束 / 默认说明
board_idtextNOT NULL;FK→community_boards(id) ON DELETE CASCADE;与 role_id 共同 PK板块
role_idtextNOT NULL;FK→roles(id) ON DELETE CASCADE;索引角色
can_readbooleanNOT NULL;DEFAULT true可读
can_postbooleanNOT NULL;DEFAULT false可发帖
can_moderatebooleanNOT NULL;DEFAULT false可管理

community_posts DANGER ZONE ​

题解 / 讨论 / 动态统一内容表。

列类型约束 / 默认说明
idtextPK内容 UUID
public_idtextNOT NULL;UNIQUE;DEFAULT post- + 8 位随机对外短 ID
typetextNOT NULL;CHECK solution/discussion/moment内容类型
author_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引作者
problem_idtextFK→problems(id) ON DELETE CASCADE;索引关联题目(题解)
board_idtextFK→community_boards(id) ON DELETE SET NULL;索引关联板块(讨论)
titletextNULL标题(题解/讨论必填,动态为空)
contenttextNOT NULL正文(Markdown)
statustextNOT NULL;DEFAULT 'published';CHECK draft/pending/published/hidden/deleted状态
is_lockedbooleanNOT NULL;DEFAULT false是否锁定
is_pinnedbooleanNOT NULL;DEFAULT false是否置顶
is_officialbooleanNOT NULL;DEFAULT false官方题解标记
moderation_reasontextNULL审核原因
published_attextNULL发布时间
created_at / updated_attextNOT NULL创建 / 更新时间

CHECK community_posts_context_check 约束三种类型的字段组合(题解须有 problem_id 且无 board_id,讨论反之,动态两者皆空且无标题)。

community_comments DANGER ZONE ​

社区评论,仅允许一级回复。

列类型约束 / 默认说明
idtextPK评论 UUID
post_idtextNOT NULL;FK→community_posts(id) ON DELETE CASCADE;索引所属帖子
author_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引作者
parent_idtextFK→community_comments(id) ON DELETE CASCADE;索引父评论(一级)
contenttextNOT NULL评论内容
statustextNOT NULL;DEFAULT 'published';CHECK pending/published/hidden/deleted状态
moderation_reasontextNULL审核原因
created_at / updated_attextNOT NULL创建 / 更新时间

community_post_likes / community_comment_likes / community_bookmarks ​

点赞与收藏关联表,结构一致。

列类型约束 / 默认说明
post_id / comment_idtextNOT NULL;FK→对应内容表 ON DELETE CASCADE;与 user_id 共同 PK目标内容
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引用户
created_attextNOT NULL创建时间

community_follows ​

用户关注关系。

列类型约束 / 默认说明
follower_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;与 followee_id 共同 PK关注者
followee_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引被关注者
created_attextNOT NULL创建时间

CHECK community_follows_not_self_check:不能关注自己。

community_activity_events ​

可展示在动态流的系统活动(去重)。

列类型约束 / 默认说明
idtextPK事件 UUID
actor_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引触发者
typetextNOT NULL;CHECK first_accepted/solution_published/contest_joined事件类型
subject_type / subject_idtextNOT NULL事件对象
metadatajsonbNOT NULL;DEFAULT {}附加数据
created_attextNOT NULL创建时间

唯一约束 community_activity_events_dedupe_unique(actor_id, type, subject_type, subject_id)。

community_reports DANGER ZONE ​

举报记录;目标须为帖子/评论/私信之一。

列类型约束 / 默认说明
idtextPK举报 UUID
reporter_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引举报人
post_idtextFK→community_posts(id) ON DELETE SET NULL;索引被举报帖子(三选一)
comment_idtextFK→community_comments(id) ON DELETE SET NULL;索引被举报评论(三选一)
message_idtextFK→messages(id) ON DELETE SET NULL;索引被举报私信(三选一)
content_typetextNOT NULL;DEFAULT 'post'内容类型
sanction_idtextFK→community_sanctions(id) ON DELETE SET NULL关联处罚
ban_idtextFK→user_bans(id) ON DELETE SET NULL关联封禁
categorytextNOT NULL;DEFAULT '其他';CHECK 8 类枚举举报分类
reasontextNOT NULL举报理由
content_snapshottextNOT NULL内容快照(留证)
statustextNOT NULL;DEFAULT 'pending';CHECK pending/resolved/dismissed状态
resolutiontextNULL处理说明
resolved_bytextFK→users(id) ON DELETE SET NULL处理人
resolved_attextNULL处理时间
updated_at / created_attextNOT NULL时间

CHECK community_reports_target_check:num_nonnulls(post_id, comment_id, message_id) = 1。

community_moderation_actions ​

审核动作历史。

列类型约束 / 默认说明
idtextPK动作 UUID
moderator_idtextFK→users(id) ON DELETE SET NULL;索引操作人
actiontextNOT NULL动作
target_type / target_idtextNOT NULL;联合索引目标
reasontextNOT NULL;DEFAULT ''原因
metadatajsonbNOT NULL;DEFAULT {}附加数据
created_attextNOT NULL创建时间

community_sanctions ​

社区写操作处罚(不影响评测与登录)。

列类型约束 / 默认说明
idtextPK处罚 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;部分索引(活跃)用户
reasontextNOT NULL原因
expires_attextNULL到期时间;NULL=永久
created_bytextFK→users(id) ON DELETE SET NULL;索引创建人
created_at / updated_attextNOT NULL时间
revoked_attextNULL撤销时间;NULL=有效
revoked_bytextFK→users(id) ON DELETE SET NULL撤销人

community_notifications ​

社区通知。

列类型约束 / 默认说明
idtextPK通知 UUID
recipient_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引(含未读部分索引)接收者
actor_idtextFK→users(id) ON DELETE SET NULL;索引触发者
typetextNOT NULL;CHECK reply/like/follow/moderation/clarification/report/ban类型
post_idtextFK→community_posts(id) ON DELETE SET NULL;索引关联帖子
comment_idtextFK→community_comments(id) ON DELETE SET NULL;索引关联评论
datajsonbNOT NULL;DEFAULT {}附加数据
read_attextNULL读取时间;NULL=未读
created_attextNOT NULL创建时间

系统、审计与邮件 ​

system_settings DANGER ZONE ​

系统设置 KV 表(运行时可变配置的持久化层)。

列类型约束 / 默认说明
keytextPK设置键
valuetextNOT NULLJSON 编码字符串(boolean/string/text 共存)
descriptiontextNOT NULL;DEFAULT ''描述
is_secretbooleanNOT NULL;DEFAULT false敏感字段标记(读取时掩码)
updated_attextNOT NULL;索引更新时间
updated_bytextFK→users(id) ON DELETE SET NULL最后修改人

不要直接改库绕过服务层

system_settings 由 services/system-settings.ts 做严格类型校验与敏感字段掩码;直接 UPDATE 会绕过校验。配置失效必须走「重新加载」而非仅删缓存(缓存未命中不回查 DB,只删缓存会读到 env/默认值而非真实新值)。

announcements ​

站内公告。

列类型约束 / 默认说明
idtextPK公告 UUID
public_idtextNOT NULL;UNIQUE;DEFAULT ann- + 8 位随机对外短 ID
titletextNOT NULL标题(1–100 字符)
contenttextNOT NULL正文(Markdown,1–50000 字符)
banner_texttextNULL横幅文字(独立于正文,为空则不出横幅)
is_pinnedbooleanNOT NULL;DEFAULT false是否置顶
is_activebooleanNOT NULL;DEFAULT true是否发布中(false=已下架)
created_bytextNOT NULL;FK→users(id)创建者
created_at / updated_attextNOT NULL时间

audit_logs DANGER ZONE ​

审计日志,记录管理员与认证类操作。

列类型约束 / 默认说明
idtextPK日志 UUID
admin_idtextNULL;FK→users(id);索引操作人;认证类事件可能无 actor
actiontextNOT NULL;CHECK 枚举(见下);索引动作
target_typetextNULL目标类型
target_idtextNULL目标 ID
detailjsonbNOT NULL;DEFAULT {}详情
ip_addresstextNOT NULL来源 IP
created_attextNOT NULL;索引时间

action 的 CHECK 覆盖 users.*、roles.*、problems.*、submissions.*、settings.update、auth.*、community.*、announcement.*、carousel.*、review.*、contest.*、ip_ban.*、judge_images.*、email_delivery.clear_suppression、llm_provider.*、llm_quota.upsert 等。

保留策略

超过 AUDIT_LOG_RETENTION_DAYS(默认 90 天,0=禁用清理)的记录由后台任务清理。

ip_bans DANGER ZONE ​

IP 黑名单,支持裸 IP 与 CIDR。

列类型约束 / 默认说明
idtextPK记录 UUID
ip_or_cidrtextNOT NULL;索引IP 或 CIDR(如 10.0.0.0/8)
reasontextNOT NULL;DEFAULT ''原因
expires_attextNULL;索引到期时间;NULL=永久
created_at / updated_attextNOT NULL时间
created_bytextFK→users(id) ON DELETE SET NULL创建人

email_delivery_events ​

邮件回调归一化事件;只保存收件地址哈希与脱敏展示值。

列类型约束 / 默认说明
idtextPK事件 UUID
providertextNOT NULL;与 provider_event_id 共同 UNIQUE邮件服务商
provider_event_idtextNOT NULL服务商事件 ID
event_typetextNOT NULL;CHECK delivery/temporary_failure/permanent_bounce/complaint事件类型
recipient_hashtextNOT NULL;索引收件地址哈希
recipient_maskedtextNOT NULL脱敏地址
reason_codetextNULL原因码
reasontextNULL原因
occurred_attextNOT NULL;索引发生时间
received_attextNOT NULL接收时间

email_suppressions ​

永久退信/投诉抑制清单。

列类型约束 / 默认说明
idtextPK记录 UUID
recipient_hashtextNOT NULL;索引;部分唯一(cleared_at IS NULL)收件地址哈希
recipient_maskedtextNOT NULL脱敏地址
reasontextNOT NULL原因
providertextNOT NULL服务商
source_event_idtextNOT NULL来源事件 ID
suppressed_attextNOT NULL;索引抑制时间
cleared_attextNULL解除抑制时间
cleared_bytextNULL解除人

搜索 ​

search_entries DANGER ZONE ​

统一搜索索引表,唯一写者是 search domain(源域通过 Redis 事件异步维护)。私信条目会写入 body 原文,整表导出等同于导出用户私信,必须脱敏/加密。

列类型约束 / 默认说明
idtextPK;DEFAULT gen_random_uuid()索引 UUID
entity_typetextNOT NULL;与 entity_id 共同唯一(idx_search_entries_entity)实体类型
entity_idtextNOT NULL实体 ID
titletextNOT NULL;DEFAULT ''标题(权重 A)
bodytextNOT NULL;DEFAULT ''正文(权重 B);私信条目为私信原文(含撤回前内容)
search_vectortsvectorGENERATED ALWAYS全文向量,GIN 索引
metadatajsonbNOT NULL;DEFAULT {}元数据
owner_idtextNULL;索引所有者
participant_idstext[]NOT NULL;DEFAULT '{}';GIN 索引参与者 ID 列表
deleted_by_user_idstext[]NOT NULL;DEFAULT '{}'已删除该消息的用户列表(其搜索结果中不可见)
is_publicbooleanNOT NULL;DEFAULT false;索引是否公开
admin_onlybooleanNOT NULL;DEFAULT false是否仅 admin 可见
is_activebooleanNOT NULL;DEFAULT true是否有效
created_at / updated_attextNOT NULL;updated_at 有索引时间

可选扩展:idx_search_entries_title_trgm / idx_search_entries_body_trgm(pg_trgm GIN,生产迁移创建)。

内容审核 ​

content_review_queue ​

内容合规审核队列(UGC 同步 + 私信异步)。

列类型约束 / 默认说明
idtextPK记录 UUID
content_typetextNOT NULL;CHECK post/comment/message目标类型
target_idtextNOT NULL;索引目标内容主键
channeltextNOT NULL;CHECK ugc/dm来源渠道
statustextNOT NULL;CHECK pending_review/approved/rejected/reviewed/dismissed处理状态
review_providertextNOT NULL判定 Provider(mock/aliyun/tencent/none)
verdicttextNOT NULL;CHECK pass/review/block/error机器结论
labeltextNULL命中分类标签(JSON 数组字符串)
hit_wordstextNULL命中词(JSON 数组字符串)
risk_leveltextNULL风险级别(low/medium/high)
content_snapshottextNOT NULL;DEFAULT ''送审内容快照(私信仅文本)
metatextNOT NULL;DEFAULT '{}'上下文 JSON
reviewed_bytextFK→users(id) ON DELETE SET NULL人工处置人
reviewed_attextNULL处置时间
resolutiontextNULL处置说明
action_takentextNULL处置动作(留痕)
created_at / updated_attextNOT NULL时间

法律与合规 ​

法律文档身份表;每类文档一行,current_version 指向最新已发布版本。

列类型约束 / 默认说明
idtextPK文档 UUID
kindtextNOT NULL;UNIQUE;CHECK privacy/terms隐私政策 / 服务条款
current_versionintegerNOT NULL;DEFAULT 0最新已发布版本;0=未发布
created_at / updated_attextNOT NULL时间

法律文档版本(不可变历史,只追加)。

列类型约束 / 默认说明
idtextPK版本 UUID
document_idtextNOT NULL;FK→legal_documents(id) ON DELETE CASCADE;与 version 共同 UNIQUE文档
versionintegerNOT NULL版本号(单调递增)
contenttextNOT NULLMarkdown 正文
content_hashtextNOT NULL规范化内容 SHA-256(同意记录据此绑定)
change_summarytextNULL变更摘要
is_materialbooleanNOT NULL;DEFAULT false;索引是否重大变更(触发重新同意)
published_attextNOT NULL发布时间
created_bytextFK→users(id) ON DELETE SET NULL创建人
tsa_providertextNULL时间戳 Provider 标识
tsa_tokentextNULLRFC 3161 TimeStampToken(base64)
tsa_chaintextNULL时间戳证书链(base64)
tsa_querytextNULL原始 RFC 3161 请求(base64)
tsa_timestamptextNULL时间戳签发时间

user_consents ​

用户同意记录(PIPL 履责证据),保留历史多行。

列类型约束 / 默认说明
idtextPK记录 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;与后两者共同 UNIQUE用户
document_kindtextNOT NULL文档类型(与 legal_documents.kind 对齐)
versionintegerNOT NULL同意版本
content_hashtextNOT NULL同意时该版本内容哈希
agreed_attextNOT NULL同意时间
iptextNULL同意来源 IP
user_agenttextNULL同意时 User-Agent

data_requests ​

PIPL 删除/更正请求(内容类),状态机 pending → processing → resolved/rejected。

列类型约束 / 默认说明
idtextPK请求 UUID
user_idtextNOT NULL;FK→users(id) ON DELETE CASCADE;索引申请人
kindtextNOT NULL;CHECK delete/correct删除 / 更正
target_type / target_idtextNOT NULL / NULL请求对象
detailtextNOT NULL申请人说明
statustextNOT NULL;DEFAULT 'pending';CHECK pending/processing/resolved/rejected;索引状态
handled_bytextFK→users(id) ON DELETE SET NULL处理人
handled_attextNULL处理时间
resolutiontextNULL处理结果说明
created_at / updated_attextNOT NULL时间

首页轮播 ​

首页轮播(与公告解耦)。

列类型约束 / 默认说明
idtextPK幻灯片 UUID
kindtextNOT NULL;CHECK image/text图片 / 渐变文案
image_storage_urltextNULL图片地址(kind=image 必填)
titletextNULL主标题(kind=text 必填)
subtitletextNULL副标题
gradient_keytextNULL渐变预设键(kind=text)
link_urltextNULL跳转地址;为空则整卡不可点
sort_orderintegerNOT NULL;DEFAULT 0;索引排序(升序)
is_enabledbooleanNOT NULL;DEFAULT true是否启用
created_at / updated_attextNOT NULL时间

LLM 网关表(noj-llm-gateway) ​

这三张表物理上位于同一 PostgreSQL 库,但逻辑所有权与迁移归 noj-llm-gateway(迁移目录 noj-llm-gateway/drizzle/,记账表 llm_schema_migrations)。它们与 noj-core 表同库不同"迁移系统",排查时不要指望在 noj-core/drizzle/ 里找到它们最新的定义。

llm_providers DANGER ZONE ​

上游 LLM Provider(OpenAI 兼容服务)配置。

列类型约束 / 默认说明
idtextPKProvider UUID
nametextNOT NULL;索引 idx_llm_providers_name名称
base_urltextNOT NULL上游 API Base URL
cost_per_1k_tokensdouble precisionNOT NULL;DEFAULT 0每 1K token 费用(用于用量估算;0=不计费)
encrypted_api_keytextNOT NULL上游 API Key 的 AES-256-GCM 信封加密密文(主密钥 NOJ_LLM_STORE_KEY)
enabledbooleanNOT NULL;DEFAULT true是否启用;停用后新评测不能选用
created_at / updated_attextNOT NULL时间

为什么它是首要 DANGER ZONE

encrypted_api_key 是平台调用上游 LLM 的付费凭据。虽然落库为密文(AES-256-GCM),但一旦连同 NOJ_LLM_STORE_KEY 一起泄露(例如整库导出 + 环境变量泄露),攻击者即可解密并盗刷上游账户。

  • 导出/备份该表时必须按密文级敏感数据处理。
  • 误删 Provider 行会导致所有引用该 Provider 的 LLM 题评测失败。
  • model 列已于迁移 0003 删除:默认模型改由 noj-core 平台设置 llm_default_model 统一决定。

llm_usage ​

LLM 调用审计表;完整保留 request_messages,不自动清理。

列类型约束 / 默认说明
idtextPK用量 UUID
submission_idtextNOT NULL;索引提交 ID
problem_idtextNOT NULL;索引题目 ID
user_idtextNOT NULL;索引用户 ID
provider_idtextNOT NULL;索引Provider ID
modeltextNOT NULL模型
request_messagesjsonbNOT NULL发给上游的完整原始 messages(可能含题目上下文与用户输入)
request_paramsjsonbNOT NULL;DEFAULT {}生成参数快照(temperature / max_tokens 等)
prompt_tokensintegerNOT NULL;DEFAULT 0提示 token 数
completion_tokensintegerNOT NULL;DEFAULT 0生成 token 数
total_tokensintegerNOT NULL;DEFAULT 0总 token 数
cached_prompt_tokensintegerNOT NULL;DEFAULT 0上游返回的缓存命中 prompt token 数
billed_prompt_tokensintegerNOT NULL;DEFAULT 0实际计费 prompt token(prompt_tokens - 缓存)
billed_total_tokensintegerNOT NULL;DEFAULT 0实际计费总 token
estimated_costintegerNOT NULL;DEFAULT 0估算费用
latency_msintegerNOT NULL;DEFAULT 0延迟(毫秒)
statustextNOT NULL;DEFAULT 'ok'状态
error_codetextNULL错误码
prompt_hashtextNOT NULL请求内容哈希(去重 / 风控)
created_attextNOT NULL;索引时间

llm_quotas ​

LLM 配额配置(按用户 / 题目 / 全局维度,日或月窗口)。

列类型约束 / 默认说明
idtextPK配额 UUID
scope_typetextNOT NULL;与 scope_id、window_type 联合索引作用域:user/problem/global/user_problem
scope_idtextNOT NULL;DEFAULT ''作用域 ID;global 时为空串
window_typetextNOT NULL;DEFAULT 'day'窗口:day/month
max_callsintegerNOT NULL;DEFAULT 0最大调用次数
max_tokensintegerNOT NULL;DEFAULT 0最大 token 数
max_costintegerNOT NULL;DEFAULT 0最大费用
created_at / updated_attextNOT NULL时间

配额缺省

表中无对应记录时,网关使用安全 fallback(环境变量 NOJ_LLM_DEFAULT_<SCOPE>_<WINDOW>_<FIELD>,共 24 个),缺失配额不会视为无限。

物化视图 ​

user_rankings ​

全局排行榜物化视图(非普通表),由迁移 0020 / 0028 / 0059 重建。

列类型说明
user_idtext用户 ID(唯一索引 idx_user_rankings_user_id)
usernametext用户名
total_submissionsint总提交数
solved_countint去重后的解题数(仅计非竞赛或 affect_global_ranking=true 的竞赛)
acceptedint通过提交数(status='finished' AND score>0)
acceptance_ratefloat通过率
rankint排名(idx_user_rankings_rank)

刷新与依赖

该视图在 noj-core 侧通过 REFRESH MATERIALIZED VIEW CONCURRENTLY user_rankings 刷新(排名相关操作后、带节流)。PGlite 测试模式不支持物化视图,会自动跳过。只读:不要对其执行 UPDATE/DELETE。

迁移记账表 ​

以下表由迁移系统自动维护,不要手工修改:

表所有者说明
drizzle.__drizzle_migrationsnoj-coreDrizzle 迁移记录(schema 默认 drizzle;测试分片下随 TEST_SCHEMA 移动)
llm_schema_migrationsnoj-llm-gateway网关自有迁移记录(避免与 core 迁移互相干扰)

Redis 键族 ​

Redis 承担 MQ、限流、撤销、观测与跨副本事件分发。以下按用途分组;类型 为 Redis 数据结构;写入方 指出哪个进程会创建/修改该键;能否删除 给出运维判断。

通用警告

  • 不要对评测队列执行 DEL:会直接丢弃待评测任务。
  • 不要随意 DEL jwt:revoked:*:会让已撤销的令牌"复活"。
  • 限流键删除等同于临时放宽限流,可用于误伤恢复,但会影响正在进行的攻击防护。
  • 跨副本共享状态全部走 Redis;本页中的多数键在重启后会以自然过期方式恢复,但队列类键携带的是真实待处理数据。

评测 MQ(noj-core ↔ noj-judge) ​

键模式类型写入方TTL作用能否删除
noj:judge:queue:high / :medium / :lowListnoj-core LPUSH;noj-judge RPOPLPUSH无三级优先级评测任务队列(前缀由 JUDGE_QUEUE 决定)否(丢任务)
noj:judge:queue:{high,medium,low}:processingListnoj-judge(RPOPLPUSH/BRPOPLPUSH)无已领取未确认任务;崩溃残留在 core sweeper 超时(约 10 分钟起,随最大时限放宽)后重投否
noj:judge:queue:{high,medium,low}:deadListnoj-judge(坏消息)无反序列化失败的死信,便于审计谨慎(审计后可清)
noj:judge:resultsListnoj-judge LPUSH;noj-core BRPOPLPUSH(先入 :processing 再确认)无评测结果队列否(丢结果)
noj:judge:results:processingListnoj-core(消费者移入)无已取未确认结果;超时 2 分钟由 sweeper 重投否

背压

每级队列有容量上限(high 5000、medium 10000、low 20000),入队前用 Lua 原子检查;满则拒绝新提交。主队列不设 TTL(空列表 Redis 自动删除 key;对非空列表设 TTL 会连未消费任务一起删)。

评测公平调度与观测(noj-judge) ​

键模式类型写入方TTL作用能否删除
noj:judge:active_users:<user_id>ZSetnoj-judge(Lua 原子 claim)claim 级 + key 级兜底过期(约 2×claim TTL,最小 60s)每用户评测并发占用(member=<instance>:<submission>,score=占用时刻);保证同一用户同时最多 1 个评测。旧 claim 按时间戳清理,崩溃可自愈可(最坏是瞬时放宽互斥)
noj:observability:judge:<instance_id>Stringnoj-judge(SET ... EX)30 秒(每 10 秒续期)Judge worker 心跳快照(活跃任务、完成/失败计数、缓存大小等),供 core 聚合平台指标可(下一轮心跳重建)

跨副本事件与 SSE(noj-core) ​

键模式类型写入方TTL作用能否删除
noj:events:submission:<id>Pub/Sub 频道noj-core publish瞬时单提交状态变更通知(SSE 推送)不适用(Pub/Sub)
noj:events:queuePub/Sub 频道noj-core瞬时全局队列变更通知不适用
noj:events:user:<id>Pub/Sub 频道noj-core瞬时用户私信通知不适用
noj:events:contest:<id>:rankingPub/Sub 频道noj-core瞬时竞赛排名变更不适用
noj:events:contest:<id>:submissionPub/Sub 频道noj-core瞬时竞赛新提交不适用
noj:events:statsPub/Sub 频道noj-core瞬时统计数据变更不适用
noj:events:announcementsPub/Sub 频道noj-core瞬时公告变更不适用
noj:events:settingsPub/Sub 频道noj-core瞬时系统设置变更(跨副本缓存失效)不适用(FLUSHALL 会中断缓存失效通知)

配置失效语义

收到 noj:events:settings 后,副本执行的是「重新加载」而非仅 cache.delete():缓存未命中不回查 DB,只删缓存会读到 env/默认值而非真实新值。

异步消费者队列(noj-core) ​

键模式类型写入方TTL作用能否删除
noj:search:indexList源域写库后 fire-and-forget LPUSH无异步搜索索引投影队列;崩溃残留在 :processing 由启动期重投与 sweeper 兜底(默认 15 分钟)自动恢复,坏消息进 :dead;极端情况下仍可能落后,可用 noj-cli search reindex 对账谨慎(丢索引事件→索引落后)
noj:search:index:processingListsearch 消费者无已取未确认索引事件谨慎
noj:review:dmListmessaging 域 RPUSH无私信异步内容审核队列谨慎
noj:review:dm:processingListreview 消费者无已取未确认审核任务谨慎

(各消费者自动登记进 sweeper 兜底表;坏消息进入对应 :dead 队列。)

登录 / 改密 / TFA 限流(noj-core) ​

namespace 默认 login;改密用 pwchange;TFA 用 tfa。

键模式类型TTL作用
ratelimit:<ns>:ip:<ip>String登录窗口(默认 30s)IP 维度固定窗口计数(默认 10 次)
ratelimit:<ns>:acc:<user>String登录窗口(默认 30s)账号维度固定窗口计数(默认 5 次)
<ns>fail:<user>String1 小时连续登录失败计数;达阈值(默认 10)触发锁定
<ns>lock:<user>String锁定时长(默认 3600s)账号锁定标记;登录成功时清除

(<ns>fail / <ns>lock 的实际前缀为 login / pwchange / tfa,后接 fail: / lock:。)

Redis 抖动

限流相关路径在 Redis 不可用时 fail-closed(返回 503),避免被绕过。误伤恢复可删除对应键。

搜索限流(noj-core) ​

键模式类型TTL作用
ratelimit:search:ip:<ip>String搜索窗口(默认 30s)匿名 IP 维度计数(默认 60 次);认证用户也用它做粗粒度 IP 兜底(rate_limit_search_max_ip_total)
ratelimit:search:user:<user_id>String搜索窗口(默认 30s)登录用户维度计数(默认 120 次);admin 跳过

写操作加固限流(noj-core) ​

统一前缀 ratelimit:hardening:<key>(key 已被小写化并截断至 128 字符)。

键模式示例窗口 / 阈值
ratelimit:hardening:register:ip:<ip>3600s / 100
ratelimit:hardening:password-reset:ip:<ip> / :email:<email>3600s / 30、3600s / 10
ratelimit:hardening:email-verification:ip:<ip> / :user:<id>60s / 1
ratelimit:hardening:message:user:<id>60s / 60
ratelimit:hardening:submission:ip:<ip> / :user:<id>60s / 120
ratelimit:hardening:self-test:ip:<ip> / :user:<id>60s / 30、60s / 4
ratelimit:hardening:contest-submission:...60s / 120
ratelimit:hardening:objective-submit:...60s / 60
ratelimit:hardening:problem-create:... / problem-import:...60s / 30、60s / 10
ratelimit:hardening:post-like:... / comment-like:... / bookmark:... / follow:...60s / 120
ratelimit:hardening:report:...60s / 30
ratelimit:hardening:contest-register:ip:<ip>:c:<contestId>30s / 5

竞赛提交预算(noj-core) ​

键模式类型TTL作用
contest:lim:<contestId>:u:<userId>:p:<problemId>String至竞赛结束竞赛内单题提交次数预算(仅当题目配置了 submission_limits);超限返回 429,所有提交(含 error)均计入;DEL 该键会清零已用次数,等价于临时放宽该题预算

JWT 撤销(noj-core) ​

键模式类型TTL作用
jwt:revoked:<jti>String与 token 剩余有效期一致已撤销 JWT 的 jti;值=撤销时间戳(ISO)。来源:登出、改密、封禁/降级等

DANGER

删除该键会让已撤销令牌重新可用。Redis 不可用时校验侧 fail-closed(503)。

LLM 网关限流与额度(noj-llm-gateway) ​

键模式类型TTL作用
llm:rate:<user_id>:<minuteKey>String60s用户维度分钟速率计数(默认 60/min)
llm:rate:ip:<ip>:<minuteKey>String60s有真实客户端 IP 时的 IP 维度分钟计数
llm:rate:sub:<submission_id>:<minuteKey>String60s无真实 IP(Evaluator 直连)时按 submission 隔离的分钟计数
llm:sub:<submission_id>:calls / :tokens / :costString评测 token TTL单次提交的调用/ token /费用计数
llm:global:day:<YYYY-MM-DD>:calls/tokens/costString至当日 UTC 结束全局日额度计数
llm:global:month:<YYYY-MM>:...String至当月 UTC 结束全局月额度计数
llm:user:<user_id>:day:<date>:... / :month:<month>:...String至窗口结束用户维度额度计数
llm:problem:<problem_id>:day:<date>:... / :month:...String至窗口结束题目维度额度计数
llm:user_problem:<user_id>:<problem_id>:day:<date>:... / :month:...String至窗口结束用户×题目组合额度(防单用户打满共享题目桶)
llm:token-ips:<submission_id>Set与 eval_token 剩余有效期监控同一 submission 的 token 被多少个来源 IP 调用(仅告警不阻断)

(各额度键后缀为 :calls / :tokens / :cost。)

说明

分钟窗口键以 minuteKey(YYYY-MM-DDTHH:MM)结尾;日/月键以日期结尾并在窗口结束时整键过期。这些键均为计数缓存,删除只会临时放宽限流/额度,不损坏数据。


运维速查 ​

  • 备份范围:完整 PostgreSQL 备份应覆盖本文全部表;Redis 并非纯缓存——jwt:revoked:*、登录/TFA/改密失败锁定等安全状态只存在于 Redis,生产必须开启 AOF(docker-compose.prod.yml 已默认 --appendonly yes),否则重启会复活已撤销令牌并清空锁定。评测队列也会在积压时因重启丢任务,维护窗口前建议先排空队列。
  • 不可手工维护的对象:drizzle.__drizzle_migrations、llm_schema_migrations、user_rankings 物化视图(刷新由应用触发)。
  • 改动前复核:任何 ALTER / DROP / 批量 UPDATE / Redis DEL 前,先用 \d+ <表名>、redis-cli TYPE <key> 与源码二次确认,并在变更窗口内先行备份。
  • 敏感数据导出:涉及 DANGER ZONE 表(尤其 llm_providers、users、oauth_accounts、system_settings、audit_logs)必须脱敏/加密传输。

再次提醒:本文档不保证完全准确,可能随开发变动且不另行通知。以实际 Schema 与源码为准。

Neuro OJ 是一个独立社区项目,与 CCF、LMCC、IOAI 及 NOAI 无官方关系。