Skip to content
页面信息
📝 描述题目管理模型,提供增删改查操作、测试数据管理和导入导出
📥 导入import { ProblemModel } from 'hydrooj'

ProblemModel

题目管理模型,提供增删改查操作、测试数据/附件管理、导入导出以及每用户状态跟踪。

ProblemModel 是一个纯静态类。所有方法直接在类上调用(如 ProblemModel.get(...))。它封装了 document 子系统,文档类型为 TYPE_PROBLEM


类型导出

ProblemDoc

主要题目文档类型。继承 Document(提供 _iddocIddocTypedomainIdownermaintainer?)。通过 interface.ts 中的模块扩充声明的额外字段:

字段类型说明
pidstring题目标识符(如 "A""abc-123"
titlestring题目标题
contentstring题面(纯文本、HTML 或多语言 JSON)
nSubmitnumber总提交数
nAcceptnumber总通过数
tagstring[]标签/分类
dataFileInfo[]测试数据文件元数据
additional_fileFileInfo[]附加文件元数据
hiddenboolean?是否隐藏题目
htmlboolean?内容是否为原始 HTML
statsany?统计对象
difficultynumber?难度等级
sortstring?用于排序的排序键
configstring?评测配置(YAML 字符串)
reference{ domainId: string, pid: number }?源题目引用(用于复制的题目)

ProblemDict

typescript
type ProblemDict = NumericDictionary<ProblemDoc>

docId(数字)和 pid(字符串)为键的字典。

ProblemStatusDoc

每用户题目状态文档。继承 StatusDocBase

字段类型说明
docIdnumber题目 docId
docType10始终为 TYPE_PROBLEM
uidnumber用户 ID
ridObjectId?最佳/最新提交的记录 ID
scorenumber?最佳分数
statusnumber?最佳状态码
starboolean?用户是否收藏了该题目

Field

typescript
type Field = keyof ProblemDoc;

所有 ProblemDoc 字段名的联合类型。用于投影数组。


常量

投影常量

为常见查询模式预构建的字段投影数组。

常量字段用途
PROJECTION_LIST_id, domainId, docType, docId, pid, owner, title, nSubmit, nAccept, difficulty, tag, hidden, stats题目列表页
PROJECTION_CONTEST_LISTPROJECTION_BASE + config比赛题目列表
PROJECTION_CONTEST_DETAILPROJECTION_CONTEST_LIST + content, html, data, additional_file, reference, maintainer比赛题目详情
PROJECTION_PUBLICPROJECTION_LIST + content, html, data, config, additional_file, reference, maintainer完整公开题目视图

方法

增删改查

add(domainId: string, pid: string = '', title: string, content: string, owner: number, tag?: string[], meta?: ProblemCreateOptions): Promise<number>

创建新题目,docId 自动递增。触发 problem/before-addproblem/add 事件。

参数类型默认值说明
domainIdstring域 ID
pidstring''题目标识符
titlestring题目标题
contentstring题面
ownernumber拥有者用户 ID
tagstring[][]标签
metaProblemCreateOptions{}附加选项
返回值Promise<number>docId
typescript
// 创建新题目
const docId = await ProblemModel.add(
  'system',             // domainId
  'A',                  // pid
  '两数之和',            // title
  '给定两个整数 a 和 b', // content
  uid,                  // owner
  ['数学', '入门'],      // tag
);

// 创建隐藏题目
const hiddenDocId = await ProblemModel.add(
  'system', 'B', '隐藏题', '题面内容', uid,
  [],
  { hidden: true },
);

addWithId(domainId: string, docId: number, pid: string = '', title: string, content: string, owner: number, tag?: string[], meta?: ProblemCreateOptions): Promise<number>

使用指定 docId 创建题目。由 add 和导入逻辑内部使用。

参数类型默认值说明
domainIdstring域 ID
docIdnumber指定的 docId
pidstring''题目标识符
titlestring题目标题
contentstring题面
ownernumber拥有者用户 ID
tagstring[][]标签
metaProblemCreateOptions{}附加选项
返回值Promise<number>docId

get(domainId: string, pid: string | number, projection?: Projection<ProblemDoc>, rawConfig?: boolean): Promise<ProblemDoc | null>

通过数字 docId 或字符串 pid 获取单个题目。除非 rawConfigtrue,否则自动解析评测配置。

参数类型默认值说明
domainIdstring域 ID
pidstring | number题目 ID 或 pid
projectionProjection<ProblemDoc>PROJECTION_PUBLIC要返回的字段
rawConfigbooleanfalse跳过配置解析
返回值Promise<ProblemDoc | null>

getMulti(domainId: string, query: Filter<ProblemDoc>, projection?: Field[]): MongoDB.Cursor<ProblemDoc>

获取查询多个题目的 MongoDB 游标,按 sort 字段排序。

参数类型默认值说明
domainIdstring域 ID
queryFilter<ProblemDoc>MongoDB 过滤器
projectionField[]PROJECTION_LIST要返回的字段
返回值MongoDB.Cursor<ProblemDoc>

list(domainId: string, query: Filter<ProblemDoc>, page: number, pageSize: number, projection?: Field[]): Promise<[ProblemDoc[], number, number]> (已弃用)

分页题目列表。返回 [docs, count, page]

参数类型默认值说明
domainIdstring域 ID
queryFilter<ProblemDoc>MongoDB 过滤器
pagenumber页码
pageSizenumber每页数量
projectionField[]PROJECTION_LIST要返回的字段
返回值Promise<[ProblemDoc[], number, number]>[docs, count, page]

edit(domainId: string, _id: number, $set: Partial<ProblemDoc>): Promise<ProblemDoc>

更新题目字段。pid 变更时重新计算 sort 键。触发 problem/before-editproblem/edit 事件。

参数类型默认值说明
domainIdstring域 ID
_idnumber题目 docId
$setPartial<ProblemDoc>要更新的字段
返回值Promise<ProblemDoc>更新后的文档

del(domainId: string, docId: number): Promise<boolean>

删除题目、其状态和关联的存储文件。触发 problem/before-delproblem/delete 事件。

参数类型默认值说明
domainIdstring域 ID
docIdnumber题目 docId
返回值Promise<boolean>是否有内容被删除

count(domainId: string, query: Filter<ProblemDoc>): Promise<number>

统计匹配过滤条件的题目数量。

参数类型默认值说明
domainIdstring域 ID
queryFilter<ProblemDoc>MongoDB 过滤器
返回值Promise<number>

copy(domainId: string, _id: number, target: string, pid?: string, hidden?: boolean): Promise<number>

将题目复制到另一个域,创建指向原始题目的引用链接。返回新题目的 docId

参数类型默认值说明
domainIdstring源域 ID
_idnumber题目 docId
targetstring目标域 ID
pidstring目标域中的题目 PID
hiddenboolean是否在目标域中隐藏

random(domainId: string, query: Filter<ProblemDoc>): Promise<string | number | null>

获取匹配过滤条件的随机题目 piddocId。未找到则返回 null

参数类型默认值说明
domainIdstring域 ID
queryFilter<ProblemDoc>MongoDB 过滤器
返回值Promise<string | number | null>

批量查找

getList(domainId: string, pids: number[], canViewHidden?: number | boolean, doThrow?: boolean, projection?: Field[], indexByDocIdOnly?: boolean): Promise<ProblemDict>

获取多个题目作为 ProblemDict。解析引用、解析配置,可选对缺失题目抛出异常。

参数类型默认值说明
domainIdstring域 ID
pidsnumber[]docId 数组
canViewHiddennumber | booleanfalseUID(检查拥有者/维护者关系)或 true 跳过检查
doThrowbooleantrue题目缺失时抛出异常
projectionField[]PROJECTION_PUBLIC要返回的字段
indexByDocIdOnlybooleanfalse仅按 docId 索引,跳过 pid
返回值Promise<ProblemDict>

状态跟踪

getStatus(domainId: string, docId: number, uid: number): Promise<ProblemStatusDoc | null>

获取指定用户在特定题目上的状态记录。

参数类型默认值说明
domainIdstring域 ID
docIdnumber题目 docId
uidnumber用户 ID
返回值Promise<ProblemStatusDoc | null>

getMultiStatus(domainId: string, query: Filter<ProblemStatusDoc>): MongoDB.Cursor<ProblemStatusDoc>

获取查询题目状态文档的游标。

参数类型默认值说明
domainIdstring域 ID
queryFilter<ProblemStatusDoc>MongoDB 过滤器
返回值MongoDB.Cursor<ProblemStatusDoc>

getListStatus(domainId: string, uid: number, pids: number[]): Promise<NumericDictionary<ProblemStatusDoc>>

获取多个题目的状态记录,以 docId 为键的字典。

参数类型默认值说明
domainIdstring域 ID
uidnumber用户 ID
pidsnumber[]docId 数组
返回值Promise<NumericDictionary<ProblemStatusDoc>>

updateStatus(domainId: string, pid: number, uid: number, rid: ObjectId, status: number, score: number): Promise<boolean>

更新用户在题目上的状态。仅在新状态更优时更新(通过始终优先)。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
uidnumber用户 ID
ridObjectId记录 ID
statusnumber状态码
scorenumber分数
返回值Promise<boolean>状态是否被更新

incStatus(domainId: string, pid: number, uid: number, key: NumberKeys<ProblemStatusDoc>, count: number): Promise<void>

递增用户题目状态上的数字字段。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
uidnumber用户 ID
keyNumberKeys<ProblemStatusDoc>要递增的字段名
countnumber递增量

setStar(domainId: string, pid: number, uid: number, star: boolean): Promise<void>

设置或取消用户题目状态上的收藏标记。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
uidnumber用户 ID
starbooleantrue 收藏,false 取消收藏

测试数据管理

addTestdata(domainId: string, pid: number, name: string, f: Readable | Buffer | string, operator?: number): Promise<void>

上传测试数据文件。更新题目文档中的 data 数组。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
namestring文件名
fReadable | Buffer | string文件内容或路径
operatornumber1操作者用户 ID

renameTestdata(domainId: string, pid: number, file: string, newName: string, operator?: number): Promise<void>

在存储和文档元数据中重命名测试数据文件。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
filestring原文件名
newNamestring新文件名
operatornumber1操作者用户 ID

delTestdata(domainId: string, pid: number, name: string | string[], operator?: number): Promise<void>

删除一个或多个测试数据文件。name 可以是单个字符串或数组。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
namestring | string[]要删除的文件名
operatornumber1操作者用户 ID

附加文件管理

addAdditionalFile(domainId: string, pid: number, name: string, f: Readable | Buffer | string, operator?: number, skipUpload?: boolean): Promise<void>

上传附加文件(如附件)。类似 addTestdata,但操作 additional_file 数组。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
namestring文件名
fReadable | Buffer | string文件内容或路径
operatornumber1操作者用户 ID
skipUploadbooleanfalse跳过存储上传(仅元数据)

renameAdditionalFile(domainId: string, pid: number, file: string, newName: string, operator?: number): Promise<void>

重命名附加文件。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
filestring原文件名
newNamestring新文件名
operatornumber1操作者用户 ID

delAdditionalFile(domainId: string, pid: number, name: string | string[], operator?: number): Promise<void>

删除一个或多个附加文件。name 接受 string | string[]

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
namestring | string[]要删除的文件名
operatornumber1操作者用户 ID

子文档辅助

push(domainId: string, _id: number, key: ArrayKeys<ProblemDoc>, value: ProblemDoc[typeof key][0]): Promise<[Doc, ObjectId]>

向数组字段(dataadditional_file)追加元素。

参数类型默认值说明
domainIdstring域 ID
_idnumber题目 docId
keyArrayKeys<ProblemDoc>数组字段名
valueProblemDoc[typeof key][0]要追加的元素
返回值Promise<[Doc, ObjectId]>更新后的文档和新元素 ID

pull(domainId: string, pid: number, key: ArrayKeys<ProblemDoc>, values: ProblemDoc[typeof key][0][]): Promise<DocType[typeof key]>

按值从数组字段中移除元素。

参数类型默认值说明
domainIdstring域 ID
pidnumber题目 docId
keyArrayKeys<ProblemDoc>数组字段名
valuesProblemDoc[typeof key][0][]要移除的值
返回值Promise<DocType[typeof key]>更新后的文档

inc(domainId: string, _id: number, field: NumberKeys<ProblemDoc> | string, n: number): Promise<ProblemDoc>

递增数字字段(如 nSubmitnAccept)。

参数类型默认值说明
domainIdstring域 ID
_idnumber题目 docId
fieldNumberKeys<ProblemDoc> | string要递增的字段名
nnumber递增量(负数为递减)
返回值Promise<ProblemDoc>更新后的文档

权限检查

canViewBy(pdoc: ProblemDoc, udoc: User): boolean

检查用户是否可以查看题目。若用户拥有 PERM_VIEW_PROBLEM 且拥有/维护该题目,或拥有 PERM_VIEW_PROBLEM_HIDDEN,或题目未隐藏,则返回 true

参数类型默认值说明
pdocProblemDoc题目文档
udocUser用户文档
返回值boolean

导入 / 导出

import(domainId: string, filepath: string, options?: ProblemImportOptions): Promise<void>

从 ZIP 归档或目录导入题目。支持 Hydro、ICPC 和 DOMjudge 包格式。触发进度回调并处理配置合并。

参数类型默认值说明
domainIdstring目标域
filepathstring.zip 文件或目录路径
options.preferredPrefixstring?导入时替换 PID 前缀
options.progressFunction?进度回调
options.overridebooleanfalse覆盖已有题目
options.operatornumber1操作者用户 ID
options.delSourceboolean?导入后删除源文件
options.hiddenboolean?将导入的题目标记为隐藏
typescript
// 从 ZIP 文件导入题目
await ProblemModel.import('system', '/tmp/problems.zip');

// 导入时指定前缀和进度回调
await ProblemModel.import('system', '/tmp/problems.zip', {
  preferredPrefix: 'contest-',
  override: true,
  progress: (current, total) => {
    console.log(`导入进度: ${current}/${total}`);
  },
});

// 导入目录并标记为隐藏
await ProblemModel.import('system', '/data/problems/', {
  hidden: true,
  operator: uid,
});

export(domainId: string, pidFilter?: string): Promise<void>

导出所有题目(或匹配 PID 正则过滤器的题目)为 ZIP 归档,保存到当前工作目录。输出文件路径打印到控制台。

参数类型默认值说明
domainIdstring域 ID
pidFilterstring''PID 正则过滤器(留空导出全部)
返回值Promise<void>无返回值
typescript
// 导出域内所有题目
const zipPath = await ProblemModel.export('system');

// 仅导出匹配前缀的题目
const partialPath = await ProblemModel.export('system', '^contest-');

// zipPath/partialPath 为生成的 ZIP 文件路径

属性

属性类型说明
defaultProblemDoc模板 ProblemDoc,所有字段设为安全默认值
deletedProblemDoc哨兵 ProblemDoc,用作已删除题目的占位符

事件

以下事件在 ProblemModel 操作期间通过 bus 触发:

事件参数触发时机
problem/before-adddomainId, content, owner, docId, args创建题目前
problem/addargs, result创建题目后
problem/before-edit$set, $unset编辑题目前
problem/editresult编辑题目后
problem/before-deldomainId, docId删除题目前
problem/deletedomainId, docId删除题目后
problem/addTestdatadomainId, pid, name, payload添加测试数据后
problem/renameTestdatadomainId, pid, file, newName重命名测试数据后
problem/delTestdatadomainId, pid, names删除测试数据后
problem/addAdditionalFiledomainId, pid, name, payload添加附加文件后
problem/renameAdditionalFiledomainId, pid, file, newName重命名附加文件后
problem/delAdditionalFiledomainId, pid, names删除附加文件后