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

如何在MongoDB多文档间使用$inc实现Discord moderation系统案件号自增

实现方案

1. 新增全局计数器集合

要实现跨文档的全局连续自增,MongoDB官方推荐的最佳实践是单独维护一个存放自增序列的集合,通过原子操作避免并发场景下的序号冲突问题。
首先创建计数器的Schema和模型:

// countersSchema.js
const counterSchema = database.Schema({
  _id: {
    type: String,
    required: true
  },
  sequenceValue: {
    type: Number,
    default: 0
  }
})
module.exports = database.model('counters', counterSchema)

首次使用前需要初始化Case序列的起始值,执行一次即可:

const Counter = require('./countersSchema')
// 如果已有历史案件,sequenceValue填当前已有案件的最大Case号,没有则填0
await Counter.findOneAndUpdate(
  { _id: 'caseId' },
  { $setOnInsert: { sequenceValue: 0 } },
  { upsert: true }
)

2. 封装全局Case号获取工具函数

通过findOneAndUpdate的原子性保证多请求并发时不会出现重复、断号问题:

const Counter = require('./countersSchema')
async function getNextCaseId() {
  const counter = await Counter.findOneAndUpdate(
    { _id: 'caseId' },
    { $inc: { sequenceValue: 1 } },
    { new: true, upsert: true }
  )
  return counter.sequenceValue
}

3. 调整原有Schema和业务代码

首先修正你原有Schema的逻辑问题:原Schema没有定义UserId字段,且MessageID设为必填但第二个业务分支没有传值,会导致插入失败:

const warnsSchema = database.Schema({
  UserId: {
    type: String,
    required: true
  },
  Username: {
    type: String,
    required: true
  },
  MessageID: {
    type: String,
    required: false // 第二个分支不需要的话设为非必填,也可以补充传值逻辑
  },
  Case: {
    type: Number,
    required: true
  },
  WarnedBy: {
    type: String,
    required: true
  },
  warning: {
    type: [Object],
    required: true,
    default: []
  }
})
module.exports = database.model('punishments', warnsSchema)

然后调整业务逻辑,插入新处罚记录前先调用工具函数获取全局自增的Case号:

async run(client, message, args, Discord) {
  const targetMention = message.mentions.users.first()
  const userid = args[0]
  const targetId = client.users.cache.find(user => user.id === userid)

  if(targetMention){
    args.shift()
    const userId = targetMention.id
    const username = targetMention.tag
    const WarnedBy = message.author.tag
    const reason = args.join(' ')

    if(!reason) {
      message.delete()
      message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
        message.delete({ timeout: 6000})
      });
      return;
    }

    const warningApplied = new Discord.MessageEmbed()
    .setColor('#ffd200')
    .setDescription(`A warning has been applied to ${targetMention.tag} :shield:`)
    let reply = await message.reply(warningApplied)
    let replyID = reply.id

    const warning = {
      UserId: userId,
      WarnedBy: WarnedBy,
      Timestamp: new Date().getTime(),
      Reason: reason,
    }
    // 获取全局唯一自增Case号
    const newCaseId = await getNextCaseId()
    await database().then(async database => {
      try{
        await warnsSchema.findOneAndUpdate({
          Username: username,
          MessageID: replyID
        }, {
          Case: newCaseId,
          WarnedBy: WarnedBy,
          $push: {
            warning: warning
          }
        }, {
          upsert: true
        })
      } finally {
        database.connection.close()
      }
    })
  }
  if(targetId){
    args.shift()
    const userId = targetId.id
    const username = targetId.tag
    const WarnedBy = message.author.tag
    const reason = args.join(' ')

    if(!reason) {
      message.delete()
      message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
        message.delete({ timeout: 6000})
      });
      return;
    }

    const warning = {
      UserId: userId,
      WarnedBy: WarnedBy,
      timestamp: new Date().getTime(),
      reason: reason
    }
    // 获取全局唯一自增Case号
    const newCaseId = await getNextCaseId()
    await database().then(async database => {
      try{
        await warnsSchema.findOneAndUpdate({
          UserId: userId,
          Username: username
        }, {
          Case: newCaseId,
          WarnedBy: WarnedBy,
          $push: {
            warning: warning
          }
        }, {
          upsert: true
        })
      } finally {
        database.connection.close()
      }
      const warningApplied = new Discord.MessageEmbed()
      .setColor('#ffd200')
      .setDescription(`A warning has been applied to ${targetId.tag} :shield:`)
      message.reply(warningApplied)
      message.delete();
    })
  }
}

注意事项

  • 计数器的递增操作为原子操作,哪怕同时触发多个处罚请求,也不会出现Case号重复、断号的问题
  • 原代码第二个分支的查询条件存在逻辑错误,原本误用了操作人的ID作为查询条件,上面的代码已经修正为被处罚用户的ID
  • 如果已有存量处罚数据,初始化计数器时要把sequenceValue设为存量数据中最大的Case号,避免和历史案件号冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.18 12:06:04