跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

如何设计MongoDB的表单审批流_节点状态流转与责任人数组

审批节点状态字段应使用原子化字符串枚举值(如"pending"),单独存储并建立复合索引;责任人存为有序ID数组配合currentApproverIndex字段;状态流转校验必须在应用层通过条件更新严格控制。 审批节点状态字段怎么建才不翻车 状态字段必须用原子化字符串,别存对象或嵌套结构。MongoDB 查询和更新都依赖字段路径的确定性,
status
字段直接设为
"pending"
、
"approved"
、
"rejected"
、
"cancelled"
这类固定枚举值,避免用
{ current: "pending", step: 3 }
这种结构。 常见错误是把状态逻辑塞进数组里,比如
workflow: [{ step: 1, status: "done" }, { step: 2, status: "pending" }]
——这会让「查当前卡在哪一步」变成聚合管道噩梦,且无法利用索引高效过滤。
status
单独成字段,加
compound index
(如
{ status: 1, updatedAt: -1 }
)支撑列表页分页查询 如果真要记录历史,另建
approval_history
子集合,用
nodeId
+
timestamp
关联主文档 状态变更必须用
findOneAndUpdate
带
upsert: false
和明确
filter
(比如只允许从
"pending"
变成
"approved"
),防并发误刷 责任人数组怎么存、怎么查、怎么更新 责任人不是“谁审过”,而是“谁该审”——所以
approvers
字段应存待审人 ID 列表,顺序即审批顺序,比如
["u_abc", "u_def", "u_ghi"]
。不要存已审人,也不要用 Map 或对象包装。 常见错误是把责任人存在嵌套文档里:
approvers: [{ id: "u_abc", role: "manager", status: "approved" }]
。这导致无法用
$in
快速定位某人是否在当前待审队列中,也难以原子更新“下一个待审人”。 用
approvers
数组 +
currentApproverIndex
数字字段配合使用,比维护一个“下一个ID”更可靠(避免ID重复或失效时逻辑断裂) 更新当前节点时,用
$inc: { currentApproverIndex: 1 }
,再用
$arrayElemAt: ["$approvers", "$currentApproverIndex"]
在聚合中取下一人——注意索引越界要兜底 如果某人离职或被替换,直接
$set: { "approvers.1": "u_new" }
即可,无需重写整个数组 状态流转校验必须在应用层做,不能靠 MongoDB 触发器 MongoDB 本身没有事务级状态机钩子,4.2+ 的变更流(change stream)延迟高、不可靠,且无法阻止非法状态写入。所有流转规则——比如“只有上一节点通过才能激活下一节点”“拒绝后不允许再批准”——必须在业务代码里用条件更新硬校验。 典型翻车场景:前端传个
{ status: "approved", approvers: ["u_xyz"] }
直接调
updateOne
,后端没检查当前
status
是否真是
"pending"
,结果跳过中间人直接终审。 每次更新前,先
findOne
拿出旧文档,校验
status
、
currentApproverIndex
、
approvers
长度三者是否匹配预期流转路径 用
updateOne
的
filter
把校验条件全写进去,例如:
{ _id: id, status: "pending", currentApproverIndex: 0, "approvers.0": "u_current" }
不要依赖
version
字段做乐观锁——MongoDB 没内置 version,手加容易漏更新,不如用
updatedAt
+
$lt
组合判断是否被并发修改 复杂分支(会签/或签/跳过)别硬塞进单文档模型 当流程出现“三人中两人同意即通过”或“财务部不填则自动跳过”,就说明单文档数组模型已经撑不住了。强行在
approvers
里加标记字段(如
required: true
)会让查询爆炸,索引失效,且无法表达动态条件。 这时候得切出去:主文档只存当前活跃节点类型(
nodeType: "or_sign"
),具体规则和投票状态放独立子文档或集合,用
processId
关联。 会签场景建
sign_votes
子集合,每条记录包含
processId
、
approverId
、
vote
、
timestamp
,方便查“已有几票”“谁还没投” 跳过逻辑由服务端根据配置实时计算,而不是在文档里预埋
skipIf: { field: "amount", lt: 10000 }
这种表达式——执行权不在数据库 所有分支判定必须有明确的“决策点文档”,避免状态散落在多个字段里互相耦合 状态和责任人看着简单,但一旦流程变长、角色变多、规则变活,单文档嵌套模型就会在索引、查询、更新三方面同时失守。真正难的不是存,是让每一次
findOneAndUpdate
都能准确回答:“此刻,谁该干啥,以及为啥是他”。

相关文章