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

MongoDB集合中重复文件名自动添加数字序号的实现方案

解决方案

以下两种方案均完全满足你提出的3项附加要求,适配MongoDB 5.0 + 原生Node.js驱动环境,适合Atlas Serverless使用。


方案一:无需修改现有表结构,适合写入频率较低的场景

核心思路是先拆分待插入文件名的前缀和后缀,通过正则匹配同系列文件,再用聚合直接提取最大序号,全程仅需1次查询+1次写入。

步骤1:文件名拆分工具函数

/**
 * 拆分文件名前缀和后缀
 * @param {string} filename 原始文件名
 * @returns {Object} { base: 前缀, ext: 后缀(带点号)}
 */
function splitFilename(filename) {
  const lastDotPos = filename.lastIndexOf('.');
  if (lastDotPos === -1) {
    return { base: filename, ext: '' };
  }
  return {
    base: filename.slice(0, lastDotPos),
    ext: filename.slice(lastDotPos)
  };
}

步骤2:序号计算与插入逻辑

async function insertFile(rawFilename) {
  const { base, ext } = splitFilename(rawFilename);
  // 正则转义特殊字符,避免匹配错误
  const escapedBase = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const escapedExt = ext.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  // 构造同系列文件名匹配正则
  const seqPattern = ext ? 
    new RegExp(`^${escapedBase}\\((\\d+)\\)${escapedExt}$`) 
    : new RegExp(`^${escapedBase}\\((\\d+)$`);

  // 聚合查询最大序号
  const aggResult = await db.collection('file').aggregate([
    {
      $match: {
        $or: [
          { name: rawFilename },
          { name: { $regex: seqPattern } }
        ]
      }
    },
    {
      $addFields: {
        seq: {
          $let: {
            vars: { matchRes: { $regexFind: { input: '$name', regex: seqPattern } } },
            in: {
              $cond: [
                { $eq: ['$$matchRes', null] },
                1, // 原始无序号文件默认序号为1
                { $toInt: { $arrayElemAt: ['$$matchRes.captures', 0] } }
              ]
            }
          }
        }
      }
    },
    { $group: { _id: null, maxSeq: { $max: '$seq' } } }
  ]).toArray();

  // 生成最终文件名
  let finalName = rawFilename;
  if (aggResult.length) {
    const nextSeq = aggResult[0].maxSeq + 1;
    finalName = `${base}(${nextSeq})${ext}`;
  }

  // 插入文档
  await db.collection('file').insertOne({ name: finalName });
  return finalName;
}

方案优缺点

  • 优点:无需修改现有数据结构,兼容存量数据
  • 缺点:正则匹配计算序号的性能略低于新增字段方案,适合单系列文件数量不超过1万、写入QPS低于10的场景

方案二:新增冗余字段,高性能适合高频写入场景

核心思路是新增3个冗余字段,将文件名拆分、序号计算的工作提前到写入时完成,配合联合索引实现毫秒级查询,全程仅需1次查询+1次写入,性能最优。

新增字段说明

字段名类型说明
file_basestring文件名前缀
file_extstring文件名后缀(带点号)
file_seqint文件序号,原始无序号文件为1,带序号文件对应括号内的数字

前置操作:创建联合索引

// 给file_base和file_ext创建联合索引,查询直接命中
await db.collection('file').createIndex({ file_base: 1, file_ext: 1 });
// 给name创建唯一索引,避免并发插入重复
await db.collection('file').createIndex({ name: 1 }, { unique: true });

插入逻辑代码

async function insertFile(rawFilename) {
  const { base, ext } = splitFilename(rawFilename);
  let nextSeq = 1;
  let finalName = rawFilename;

  try {
    // 查询同系列最大序号
    const maxSeqRes = await db.collection('file').aggregate([
      { $match: { file_base: base, file_ext: ext } },
      { $group: { _id: null, maxSeq: { $max: '$file_seq' } } }
    ]).toArray();

    if (maxSeqRes.length) {
      nextSeq = maxSeqRes[0].maxSeq + 1;
      finalName = `${base}(${nextSeq})${ext}`;
    }

    // 插入文档
    await db.collection('file').insertOne({
      name: finalName,
      file_base: base,
      file_ext: ext,
      file_seq: nextSeq
    });
  } catch (e) {
    // 唯一键冲突说明并发插入,重试一次即可
    if (e.code === 11000) {
      return insertFile(rawFilename);
    }
    throw e;
  }
  return finalName;
}

方案优缺点

  • 优点:查询性能拉满,无正则计算开销,支持高频率写入,完全适配Atlas Serverless的按量付费模式,IO成本极低
  • 缺点:需要修改表结构,存量数据需要补全3个冗余字段(仅需一次批量更新即可完成)

扩展:补全最小缺失序号的实现

如果需要补全中间缺失的最小序号(比如你的示例中生成sample(3).txt而非sample(6).txt),只需将方案二的聚合查询修改为如下即可:

const seqListRes = await db.collection('file').aggregate([
  { $match: { file_base: base, file_ext: ext } },
  { $group: { _id: null, seqs: { $push: '$file_seq' } } }
]).toArray();

let nextSeq = 1;
if (seqListRes.length) {
  const seqSet = new Set(seqListRes[0].seqs);
  // 从2开始找第一个缺失的序号
  for (let i = 2; i <= seqSet.size + 2; i++) {
    if (!seqSet.has(i)) {
      nextSeq = i;
      break;
    }
  }
}

该方案适合单系列文件数量较少的场景,单系列文件超过1万时建议优先使用最大序号+1的逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 22:51:03