MongoDB bulkWrite 默认不抛异常,必须手动检查 result.writeErrors 才能捕获 E11000 等错误;Koa 全局错误流仅识别带 status 属性的 Error 实例,需主动构造并调用 next(err)。

不能靠 try/catch 包一层就完事——MongoDB 驱动的 bulkWrite 默认不抛异常,next() 执行完后错误已经“静默”了,必须手动检查返回结果才能进 Koa 全局错误流。
bulkWrite 的 writeErrors 是唯一可靠入口
bulkWrite 即使遇到 E11000 duplicate key,只要设置了 { ordered: false },驱动仍会返回 result 对象且 err === null。此时 try/catch 完全失效。
-
result.writeErrors是个数组,每个元素含code(如11000)、index(对应原始操作下标)、errmsg - 别依赖
result.ok或result.nInserted判断是否出错——它们只反映执行状态,不反映语义冲突 - 若用 Mongoose,
save()或insertMany()会自动 throwMongoServerError,这时try/catch才有效
构造带 status 的 Error 并调用 next(err)
Koa 错误中间件只认有 status 属性的 Error 实例,否则会 fallback 到 app.on('error'),无法统一响应格式。
- 对
bulkWrite:检查result.writeErrors,遇到code === 11000时,用正则从errmsg提取字段名(如"username"),再构造错误:const err = new Error(`用户名已存在`);<br>err.status = 409;<br>err.field = 'username';<br>next(err);
- 对单文档
save():直接catch MongoServerError,避免硬编码判断err.code,用正则匹配errmsg更健壮 - 确保所有错误对象都有
status,否则中间件里ctx.status = err.status || 500就会兜底成 500
索引没生效?先查 ObjectId 类型一致性
看似重复插入却没触发 E11000,大概率是唯一索引没建对,或类型不匹配导致索引失效。
- 确认字段已建独立唯一索引:
db.users.createIndex({ username: 1 }, { unique: true }) - 前端传来的 ID(如
"507f1f77bcf86cd799439011")若未转成ObjectId就直接插入,MongoDB 会存为字符串,而ObjectId("...") !== "...",索引就形同虚设 - 用
console.log(typeof doc._id)和db.users.findOne().pretty()检查实际存储类型
真正难的不是写中间件,而是意识到 MongoDB 的错误不是“抛出来”的,而是“藏在 result 里的”。漏掉 writeErrors 检查,等于把重复键错误主动吞掉。











