如何为已存在的文件名添加递增序号避免重复?支持日期+序号格式
解决文件名重复的递增序号生成方案
我来帮你搞定这个文件名去重的问题,给你两种实用的实现方案,你可以按需选择:
方案一:原文件名后加递增序号(比如 file.jpg → file(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




