You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为已存在的文件名添加递增序号避免重复?支持日期+序号格式

解决文件名重复的递增序号生成方案

我来帮你搞定这个文件名去重的问题,给你两种实用的实现方案,你可以按需选择:

方案一:原文件名后加递增序号(比如 file.jpgfile(1).jpg

这种方式保留原文件名的主体,只在重复时添加括号包裹的递增数字,完美契合你现有代码的逻辑,修改后的完整代码如下:

const checkFilenameIFExists = () => async (context) => { 
  const record = await context.app.service('people')
    .Model.findByPk(context.data.peopleId);
  if (!record) {
    throw new errors.BadRequest('People record does not exist.');
  }

  // 工具函数:拆分文件名的主体和扩展名
  const splitFileName = (filename) => {
    const dotPos = filename.lastIndexOf('.');
    if (dotPos === -1) {
      return { name: filename, ext: '' };
    }
    return {
      name: filename.slice(0, dotPos),
      ext: filename.slice(dotPos)
    };
  };

  let originalName = context.data.filename;
  let { name, ext } = splitFileName(originalName);
  let counter = 1;
  let newFilename = originalName;

  // 循环检查直到找到不重复的文件名
  while (true) {
    const existingFile = await context.service.Model.findOne({
      where: { peopleId: record.id, filename: newFilename },
    });
    if (!existingFile) break;
    // 生成带序号的新文件名
    newFilename = `${name}(${counter})${ext}`;
    counter++;
  }

  context.data.filename = newFilename;
}

逻辑拆解:

  • 先写个小工具函数拆分文件名和扩展名,处理没有扩展名的情况(比如纯文本文件README
  • 从序号1开始,每次生成新文件名后检查数据库中是否存在
  • 直到找到不存在的文件名,直接替换到context.data.filename里就行

方案二:用「日期+序号」格式命名(比如 20240520-1-file.jpg

如果想更直观地区分文件版本,这种带日期前缀的格式会更实用,代码实现如下:

const checkFilenameIFExists = () => async (context) => { 
  const record = await context.app.service('people')
    .Model.findByPk(context.data.peopleId);
  if (!record) {
    throw new errors.BadRequest('People record does not exist.');
  }

  // 生成YYYYMMDD格式的日期字符串
  const today = new Date().toISOString().split('T')[0].replace(/-/g, '');
  const originalFilename = context.data.filename;
  let counter = 1;
  let newFilename = `${today}-${counter}-${originalFilename}`;

  // 循环检查直到找到可用文件名
  while (true) {
    const existingFile = await context.service.Model.findOne({
      where: { peopleId: record.id, filename: newFilename },
    });
    if (!existingFile) break;
    counter++;
    newFilename = `${today}-${counter}-${originalFilename}`;
  }

  context.data.filename = newFilename;
}

逻辑拆解:

  • 先把当前日期转成YYYYMMDD的格式,作为文件名前缀
  • 从序号1开始,组合成「日期-序号-原文件名」的格式
  • 同样循环检查数据库,直到找到不重复的文件名

性能优化小技巧

如果你的文件数量比较多,循环查询可能会有点慢,可以改成一次查询获取当前最大的序号,减少数据库请求次数。比如方案一可以优化成:

// 记得先导入Sequelize的Op操作符
const { Op } = require('sequelize');

// ... 前面的代码不变 ...

let newFilename = originalName;
const { name, ext } = splitFileName(originalName);

// 查询当前该用户下,同文件名前缀的最大序号文件
const maxSeqFile = await context.service.Model.findOne({
  where: { 
    peopleId: record.id,
    filename: { [Op.like]: `${name}(%)${ext}` } // 匹配带序号的文件名
  },
  order: [['filename', 'DESC']] // 按文件名倒序,取最大的序号
});

if (maxSeqFile) {
  // 从现有文件名中提取最大序号,加1作为新序号
  const seqMatch = maxSeqFile.filename.match(/\((\d+)\)\./);
  if (seqMatch) {
    const currentMax = parseInt(seqMatch[1]);
    newFilename = `${name}(${currentMax + 1})${ext}`;
  }
}

// 最后再检查一次这个新文件名是否存在(防止并发场景下的冲突)
const finalCheck = await context.service.Model.findOne({
  where: { peopleId: record.id, filename: newFilename },
});
if (finalCheck) {
  // 极端并发情况下如果还是重复,再进入循环重试
  let counter = parseInt(seqMatch[1]) + 2;
  while (true) {
    newFilename = `${name}(${counter})${ext}`;
    const check = await context.service.Model.findOne({ where: { peopleId: record.id, filename: newFilename } });
    if (!check) break;
    counter++;
  }
}

context.data.filename = newFilename;

这种方式大部分情况下只需要1-2次查询,比循环查询高效很多,还能处理并发场景下的小概率冲突。

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

火山引擎 最新活动