推荐用枚举值并在schema中定义允许值列表,配合default和deleted状态;需记录status_history快照;关键字段售出后冻结,辅助字段可更新;状态迁移走白名单校验,更新时加前置条件防并发。

商品状态字段该用字符串还是枚举值?
直接用 "on_sale"、"sold_out" 这类字符串最常见,但容易拼错、难维护。MongoDB 本身不强制枚举,所以必须靠应用层约束——推荐在 Schema 中定义明确的允许值列表,并在插入/更新前校验。
例如在 Node.js + Mongoose 中,可这样声明:
status: {
type: String,
enum: ['draft', 'on_sale', 'sold_out', 'archived', 'deleted'],
default: 'draft'
}
别省略 default,否则新商品没设状态就存进去了;也别漏掉 deleted,软删除比物理删除更安全,尤其涉及订单关联时。
状态变更要不要记录历史?
要,尤其是二手平台:买家可能投诉“说好在售却秒没”,客服需要回溯;运营要分析“从上架到售出平均耗时”。硬编码一堆 updated_at 字段不现实,推荐用嵌入式数组存状态快照:
-
status_history字段类型为[{ status: String, at: Date, by: ObjectId }] - 每次调用状态变更函数(比如
updateStatus())时,先 push 新记录,再更新当前status,避免竞态丢失 - 查最新状态仍用
status字段,查轨迹才遍历status_history,兼顾查询性能
“已售出”后还能改价格或描述吗?
不能无条件放开。二手平台常有“售出后协商修改发货时间”等例外,但价格和核心描述必须冻结,否则破坏交易一致性。
实际做法是分层校验:
- 对
price、title、main_image等关键字段,检查当前status是否为"sold_out"或"archived",是则拒绝更新 - 对
logistics_note、contact_info等辅助字段,允许在"sold_out"状态下更新,但需记录操作人和时间 - 所有写操作都走统一的
updateProduct()函数,而不是裸调collection.updateOne(),把校验逻辑收口
状态机逻辑该放应用层还是数据库触发器?
MongoDB 没有原生触发器(4.2+ 的 change stream 是被动监听,不是主动拦截),所以状态流转规则必须实现在应用代码里。
一个轻量但有效的方案是定义一个状态迁移白名单:
const TRANSITIONS = {
draft: ['on_sale', 'deleted'],
on_sale: ['sold_out', 'archived', 'deleted'],
sold_out: ['archived', 'deleted'],
archived: ['deleted']
};
更新前查表:if (!TRANSITIONS[oldStatus].includes(newStatus)) throw new Error('Invalid transition')。比写一堆 if-else 清晰,也方便后续加审批流或异步钩子。
真正容易被忽略的是并发场景:两个客服同时点“上架”,可能让商品从 draft 变两次 on_sale,导致库存或通知重复。务必在更新语句里加上 { status: 'draft' } 作为前置条件,用原子操作兜底。











