E11000 duplicate key 错误不会被Koa错误中间件自动捕获,因MongoDB驱动bulkWrite默认不抛异常;需手动检查result.writeErrors并调用next(err)才能进入统一错误处理流程。

MongoDB 的 duplicate key 错误(E11000)不会自动被 Koa 的错误中间件捕获——必须手动检查驱动返回的 writeErrors 或监听 ValidationError/CastError 等 Mongoose 错误,再显式调用 next(err) 才能进统一错误处理流程。
为什么 try/catch 无法直接捕获 bulkWrite 的重复键错误
Node.js MongoDB 驱动对 bulkWrite 的设计是“失败不抛异常”,即使有 E11000 duplicate key,只要设置了 { ordered: false },操作仍会完成并返回结果对象,err 为 null。此时 try/catch 完全无效。
-
result.writeErrors是唯一可靠入口:它是个数组,每个元素含code(如11000)、index(对应原始操作下标)、errmsg - 别依赖
result.ok或result.nInserted判断是否出错——它们只反映执行状态,不反映语义冲突 - 若用 Mongoose,
save()或insertMany()会自动 throwMongoServerError,这时try/catch才有效
如何把 E11000 映射成业务可读的错误并交由 Koa 错误中间件处理
不能让控制器自己 console.error 或吞掉错误,必须构造一个带状态码和语义信息的 Error 实例,并调用 next(err) 推入 Koa 错误流。
- 对
bulkWrite:检查result.writeErrors,遇到code === 11000时,提取errmsg中的字段名(如"username"),构造新错误:const err = new Error(`用户名已存在`); err.status = 409; err.field = 'username'; next(err); - 对单文档
save():直接catchMongoServerError,用正则匹配errmsg提取冲突字段,避免硬编码判断err.code - 确保错误对象有
status属性(如409),Koa 错误中间件才能正确设置ctx.status
容易被忽略的索引与类型一致性问题
看似重复却没触发 E11000?大概率是唯一索引没生效或类型不匹配。
- 确认
username和email字段已分别建了独立唯一索引:db.users.createIndex({ username: 1 }, { unique: true }) - 前端传来的
_id或字符串 ID(如"507f1f77bcf86cd799439011")若未转成ObjectId就直接插入,MongoDB 会存为字符串,导致唯一索引失效——因为ObjectId("...")≠"..." - Mongoose 模型中定义
_id: { type: Schema.Types.ObjectId, required: true }并开启strict: true,可提前拦截类型错误
真正麻烦的不是怎么 catch 错误,而是怎么让错误在进中间件前就携带足够上下文:字段名、冲突值、原始操作意图。否则统一处理只剩下一个模糊的 “500 Internal Server Error”。

















