Skip to content
页面信息
📝 描述通用文档存储模型,提供跨所有文档类型的类型化 CRUD 和子文档操作
📥 导入import { DocumentModel } from 'hydrooj'

DocumentModel

通用文档存储模型,提供跨所有文档类型(题目、比赛、训练计划、讨论等)的类型化 CRUD、子文档操作和每用户状态追踪。

DocumentModel 是导出函数的普通模块(非类)。所有函数直接调用。它是高级模型(ProblemModel、ContestModel、TrainingModel、DiscussionModel)委托的基础数据访问层。


类型导出

DocType

将类型常量映射到对应的文档接口:

typescript
interface DocType {
    [TYPE_PROBLEM]: ProblemDoc;
    [TYPE_PROBLEM_SOLUTION]: any;
    [TYPE_PROBLEM_LIST]: any;
    [TYPE_DISCUSSION_NODE]: any;
    [TYPE_DISCUSSION]: DiscussionDoc;
    [TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc;
    [TYPE_CONTEST]: Tdoc;
    [TYPE_CONTEST_PRINT]: ContestPrintDoc;
    [TYPE_CONTEST_CLARIFICATION]: ContestClarificationDoc;
    [TYPE_TRAINING]: TrainingDoc;
}

DocStatusType

将类型常量映射到状态文档接口(如 TYPE_PROBLEM 对应 ProblemStatusDoc)。


常量

DocType 常量

常量说明
TYPE_PROBLEM10题目文档
TYPE_PROBLEM_SOLUTION11题解题
TYPE_PROBLEM_LIST12题单 / 作业
TYPE_DISCUSSION_NODE20讨论分类节点
TYPE_DISCUSSION21讨论帖
TYPE_DISCUSSION_REPLY22讨论回复
TYPE_CONTEST30比赛文档
TYPE_CONTEST_CLARIFICATION31比赛答疑请求
TYPE_CONTEST_PRINT32比赛打印任务
TYPE_TRAINING40训练计划

方法

文档 CRUD

add(domainId: string, content: string, owner: number, docType: number, docId?: ObjectId, parentType?: number, parentId?: ObjectId | number | string, args?: any): Promise<ObjectId>

插入新文档。如果提供了 docId 则返回 docId,否则返回生成的 ObjectId。插入前触发 document/add 总线事件。

参数类型默认值说明
domainIdstring域 ID
contentstring文档内容
ownernumber所有者 UID
docTypenumber文档类型常量
docIdObjectId指定文档 ID(可选)
parentTypenumber父文档类型
parentIdObjectId | number | string父文档 ID
argsany附加字段
返回值Promise<ObjectId>新文档 ID

get(domainId: string, docType: number, docId: ObjectId, projection?: any): Promise<Doc | null>

按组合键 (domainId, docType, docId) 获取单个文档。未找到时返回 null。接受可选字段投影。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
projectionany字段投影
返回值Promise<Doc | null>

getMulti(domainId: string, docType: number, query?: Filter<Doc>, projection?: any): FindCursor<Doc>

返回匹配查询的多文档 FindCursor。接受可选过滤器和投影。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryFilter<Doc>过滤条件
projectionany字段投影
返回值FindCursor<Doc>

set(domainId: string, docType: number, docId: ObjectId, $set?: any, $unset?: any, $push?: any): Promise<Doc>

使用 $set$unset 和/或 $push 操作符原子性更新文档。返回更新后的文档。更新前触发 document/set 总线事件。文档不存在时执行 upsert。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
$setany要设置的字段
$unsetany要移除的字段
$pushany要推入数组的元素
返回值Promise<Doc>更新后的文档

inc(domainId: string, docType: number, docId: ObjectId, key: string, value: number): Promise<Doc | null>

原子性递增数值字段 value。返回更新后的文档(文档不存在时返回 null)。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring要递增的字段名
valuenumber递增量
返回值Promise<Doc | null>更新后的文档(文档不存在时返回 null

incAndSet(domainId: string, docType: number, docId: ObjectId, key: string, value: number, args: any): Promise<Doc | null>

在单次操作中原子性递增数值字段并设置附加字段。返回更新后的文档(文档不存在时返回 null)。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring要递增的字段名
valuenumber递增量
argsany附加设置字段
返回值Promise<Doc | null>更新后的文档(文档不存在时返回 null

count(domainId: string, docType: number, query?: Filter<Doc>): Promise<number>

统计匹配过滤器的文档数量。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryFilter<Doc>过滤条件
返回值Promise<number>匹配的文档数量

deleteOne(domainId: string, docType: number, docId: ObjectId): Promise<[DeleteResult, DeleteResult]>

删除单个文档及其关联的状态记录。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
返回值Promise<[DeleteResult, DeleteResult]>文档删除结果和状态删除结果

deleteMulti(domainId: string, docType: number, query?: Filter<Doc>): Promise<DeleteResult>

删除匹配过滤器的多个文档。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryFilter<Doc>过滤条件
返回值Promise<DeleteResult>

子文档操作

这些方法操作文档内的数组字段(如回复、标签)。

push(domainId: string, docType: number, docId: ObjectId, key: string, ...): Promise<[Doc, ObjectId]> (两个重载)

对象形式: push(domainId, docType, docId, key, value) —— 将对象推入数组字段。 内容形式: push(domainId, docType, docId, key, content, owner, args?) —— 推入带自动生成 _id 的新子文档。 返回 [updatedDoc, subId]

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring数组字段名
valueany要推入的对象(对象形式)
contentstring子文档内容(内容形式)
ownernumber子文档所有者 UID(内容形式)
argsany附加字段(内容形式)
返回值Promise<[Doc, ObjectId]>更新后的文档和子文档 ID

pull(domainId: string, docType: number, docId: ObjectId, setKey: string, contents: any): Promise<Doc>

从数组字段中移除匹配给定过滤器的子文档。返回更新后的文档。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
setKeystring数组字段名
contentsany移除过滤器
返回值Promise<Doc>更新后的文档

getSub(domainId: string, docType: number, docId: ObjectId, key: string, subId: ObjectId): Promise<[Doc | null, SubDoc | null]>

从数组字段中按 _id 获取特定子文档。返回 [parentDoc, subDoc],未找到时返回 [null, null]

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring数组字段名
subIdObjectId子文档 ID
返回值Promise<[Doc | null, SubDoc | null]>

setSub(domainId: string, docType: number, docId: ObjectId, key: string, subId: ObjectId, args: any): Promise<Doc>

更新按 _id 标识的特定子文档字段。使用位置 $ 操作符。返回更新后的父文档。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring数组字段名
subIdObjectId子文档 ID
argsany要设置的字段
返回值Promise<Doc>更新后的父文档

deleteSub(domainId: string, docType: number, docId: ObjectId, key: string, subId: ObjectId | ObjectId[]): Promise<Doc>

从数组字段中按 _id 移除一个或多个子文档。接受单个 ID 或 ID 数组。返回更新后的文档。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
keystring数组字段名
subIdObjectId | ObjectId[]子文档 ID(单个或数组)
返回值Promise<Doc>更新后的文档

addToSet(domainId: string, docType: number, docId: ObjectId, setKey: string, content: string): Promise<Doc>

仅在数组字段中不存在时添加字符串值(通过 $addToSet 去重)。返回更新后的文档。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
setKeystring数组字段名
contentstring要添加的值
返回值Promise<Doc>更新后的文档

状态 CRUD

状态记录追踪每用户状态(分数、提交、报名),以 (domainId, docType, docId, uid) 为键。

getStatus(domainId: string, docType: number, docId: ObjectId, uid: number): Promise<StatusDoc | null>

获取单个状态记录。未找到时返回 null

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
返回值Promise<StatusDoc | null>

getMultiStatus(domainId: string, docType: number, args: any): FindCursor<StatusDoc>

返回匹配过滤器的状态记录 FindCursor(限定域范围)。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
argsany过滤条件
返回值FindCursor<StatusDoc>

getMultiStatusWithoutDomain(docType: number, args: any): FindCursor<StatusDoc>

返回匹配过滤器的状态记录 FindCursor(跨域)。用于系统级查询。

参数类型默认值说明
docTypenumber文档类型常量
argsany过滤条件
返回值FindCursor<StatusDoc>

setStatus(domainId: string, docType: number, docId: ObjectId, uid: number, args: any, returnDocument?: 'before' | 'after'): Promise<StatusDoc>

设置状态记录字段,不存在时 upsert。returnDocument 控制返回的文档是更新 'before' 还是 'after'(默认 'after')。返回 'before' 时使用 readConcern: 'majority'

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
argsany要设置的字段
returnDocument'before' | 'after''after'返回更新前还是更新后的文档
返回值Promise<StatusDoc>
typescript
// 设置用户对题目的状态
const status = await document.setStatus(
  'system',                          // domainId
  document.TYPE_PROBLEM,             // docType
  problemDocId,                      // docId
  12345,                             // uid
  { score: 100, accept: true },      // args
);

// 获取更新前的状态(用于检测变更)
const before = await document.setStatus(
  'system',
  document.TYPE_PROBLEM,
  problemDocId,
  12345,
  { star: true },
  'before',                          // returnDocument
);

setMultiStatus(domainId: string, docType: number, query: any, args: any): Promise<void>

批量更新匹配过滤器的多个状态记录。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryany过滤条件
argsany要设置的字段
返回值Promise<void>

countStatus(domainId: string, docType: number, query?: any): Promise<number>

统计匹配过滤器的状态记录数量。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryany过滤条件
返回值Promise<number>

deleteMultiStatus(domainId: string, docType: number, query?: any): Promise<void>

删除匹配过滤器的状态记录。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
queryany过滤条件
返回值Promise<void>

incStatus(domainId: string, docType: number, docId: ObjectId, uid: number, key: string, value: number): Promise<StatusDoc>

原子性递增状态记录上的数值字段。不存在时 upsert。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
keystring要递增的字段名
valuenumber递增量
返回值Promise<StatusDoc>

setStatusIfCondition(domainId: string, docType: number, docId: ObjectId, uid: number, filter: any, args?: any, returnDocument?: 'before' | 'after'): Promise<StatusDoc | false>

条件性设置状态字段 —— 仅在附加 filter 匹配时更新。出错时返回 false(捕获异常)。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
filterany额外过滤条件
argsany要设置的字段
returnDocument'before' | 'after''after'返回更新前还是更新后的文档
返回值Promise<StatusDoc | false>条件不满足时返回 false

setIfNotStatus(domainId: string, docType: number, docId: ObjectId, uid: number, key: string, value: any, ifNot: any, args: any, returnDocument?: 'before' | 'after'): Promise<StatusDoc | false>

仅当字段当前值不为 ifNot 时设置为 value。委托 setStatusIfCondition 使用 $ne 过滤器。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
keystring字段名
valueany目标值
ifNotany排除的当前值
argsany附加设置字段
returnDocument'before' | 'after''after'返回更新前还是更新后的文档
返回值Promise<StatusDoc | false>条件不满足时返回 false

cappedIncStatus(domainId: string, docType: number, docId: ObjectId, uid: number, key: string, value: number, minValue?: number, maxValue?: number, setPayload?: any): Promise<StatusDoc>

原子性递增数值字段但限制在 [minValue, maxValue] 范围内(默认 [-1, 1])。字段将超出上限时递增被静默跳过。可选择在同一操作中设置附加字段。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
keystring要递增的字段名
valuenumber递增量
minValuenumber-1最小值
maxValuenumber1最大值
setPayloadany同步设置的附加字段
返回值Promise<StatusDoc>
typescript
// 比赛签到:attendance 字段从 -1 → 0 → 1,上限为 1
const status = await document.cappedIncStatus(
  'system',                          // domainId
  document.TYPE_CONTEST,             // docType
  contestDocId,                      // docId
  12345,                             // uid
  'attendance',                      // key
  1,                                 // value
  -1,                                // minValue
  1,                                 // maxValue
  { attendAt: new Date() },          // setPayload
);

基于修订号的状态操作

这些方法在状态记录上维护 rev(修订计数器)用于乐观并发控制。

revInitStatus(domainId: string, docType: number, docId: ObjectId, uid: number): Promise<StatusDoc>

初始化或递增状态记录上的 rev 计数器。不存在时 upsert。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
返回值Promise<StatusDoc>

revPushStatus(domainId: string, docType: number, docId: ObjectId, uid: number, key: string, value: any, id?: ObjectId): Promise<StatusDoc>

将值推入带修订追踪的状态数组字段。如果已存在匹配 id 的元素,则替换而非推入。两种情况均递增 rev

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
keystring数组字段名
valueany要推入/替换的值
idstring'_id'子元素 ID 字段名(匹配则替换)
返回值Promise<StatusDoc>
typescript
// 比赛排行榜:推入每题提交详情,带修订追踪
const status = await document.revPushStatus(
  'system',                          // domainId
  document.TYPE_CONTEST,             // docType
  contestDocId,                      // docId
  12345,                             // uid
  'detail',                          // key
  { pid: problemId, score: 100 },    // value
  detailId,                          // id(如果匹配则替换,否则推入)
);

revSetStatus(domainId: string, docType: number, docId: ObjectId, uid: number, rev: number, args: any): Promise<StatusDoc>

仅当当前 rev 匹配提供的值时设置状态字段,然后递增 rev。用于乐观锁。

参数类型默认值说明
domainIdstring域 ID
docTypenumber文档类型常量
docIdObjectId文档 ID
uidnumber用户 ID
revnumber期望的当前修订号
argsany要设置的字段
返回值Promise<StatusDoc>

属性

属性类型说明
collCollection<Doc>MongoDB document 集合 —— 存储所有文档记录
collStatusCollection<StatusDoc>MongoDB document.status 集合 —— 存储每用户状态记录

备注

  • apply(ctx) 注册数据库索引和 domain/delete 清理处理器。在应用启动时调用一次。
  • 插入前触发 document/add 总线事件;更新前触发 document/set 总线事件。
  • push 方法有两个重载:对象形式和内容形式。