You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mongoose的Save中间件中实现自增ID/计数器?

如何在Mongoose中间件中实现自增计数器(仅新增文档时生效)

核心问题拆解

  • Mongoose自动生成的_id字段不可覆盖,必须用自定义字段(比如pageIndex)存储自增索引
  • 直接查询集合总长度的方式有并发风险:多个请求同时新增时会拿到相同数值,导致计数重复
  • 你的Schema里没声明计数器字段,所以中间件里赋值不会生效

解决方案:独立计数器集合+原子操作

用单独的集合存储当前自增数值,借助MongoDB的原子操作findOneAndUpdate确保计数唯一递增,彻底解决并发冲突问题。

步骤1:定义计数器模型

先创建一个专门存计数器的集合,用来记录Page文档的最新索引值:

import mongoose from 'mongoose';

const counterSchema = new mongoose.Schema({
  _id: { type: String, required: true }, // 计数器唯一标识
  seq: { type: Number, default: 0 } // 当前计数
});

const Counter = mongoose.model('Counter', counterSchema);

步骤2:修改Page模型与中间件

  • 在Page的Schema里添加自定义自增字段pageIndex
  • 仅在新增文档时(通过this.isNew判断)触发计数逻辑
  • 用原子操作获取并递增计数器值,赋值给新文档

完整修正代码

import mongoose from 'mongoose'
import { updateIfCurrentPlugin } from 'mongoose-update-if-current'

// 修正TypeScript接口:继承mongoose.Document而非mongoose.Page
interface PageDoc extends mongoose.Document {
    pageIndex: number // 自定义自增索引字段
    name: string
}

interface PageAttrs {
    name: string
}

interface PageModel extends mongoose.Model<PageDoc> {
    build(attrs: PageAttrs): PageDoc
}

const PageSchema = new mongoose.Schema<PageDoc>(
    {
        name: {
            type: String,
            required: true
        },
        pageIndex: {
            type: Number,
            unique: true, // 强制唯一,避免重复
            required: true
        }
    },
    {
        toJSON: {
            transform(doc, ret) {
                ret.id = ret._id
                delete ret._id
                delete ret.__v
            }
        }
    }
)

// 仅在新增文档时执行计数逻辑
PageSchema.pre('save', async function (next) {
    if (!this.isNew) return next() // 更新文档时跳过

    try {
        // 原子操作:获取并递增计数器,不存在则自动创建
        const counter = await Counter.findOneAndUpdate(
            { _id: 'pageCounter' },
            { $inc: { seq: 1 } }, // 每次递增1
            { new: true, upsert: true }
        )
        // 从0开始计数:第一次递增后seq为1,减1得到初始值0
        this.pageIndex = counter.seq - 1
        next()
    } catch (err) {
        next(err)
    }
})

PageSchema.set('versionKey', 'version')
PageSchema.plugin(updateIfCurrentPlugin)

PageSchema.statics.build = (attrs: PageAttrs) => {
    return new Page({
        name: attrs.name
    })
}

const Page = mongoose.model<PageDoc, PageModel>('Page', PageSchema)

export { Page, Counter }

关键细节说明

  • 并发安全:findOneAndUpdate是MongoDB原子操作,多个请求同时触发也会保证计数唯一,不会重复
  • 仅新增生效:通过this.isNew判断,更新现有文档时不会改动计数器
  • 初始值调整:如果要从1开始计数,直接赋值counter.seq即可,不需要减1

内容的提问来源于stack exchange,提问作者ShadowGames

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 23:20:25