如何在Node.js中为上传的文件生成短且唯一的名称

22

我需要用类似nYrnfYEva4vhAoFGhwX6aOr7这样的短唯一标识为上传的文件命名。 我该如何确保文件的唯一性?


1
请参见:https://dev59.com/dnM_5IYBdhLWcg3wgja2 - zdszsdasd
1
从这篇文章的第一条评论中可以看到:“警告:没有一个答案会有真正的随机结果!它们只是伪随机。当使用随机字符串进行保护或安全性时,请不要使用其中任何一个!!!尝试使用这些API之一”。我需要非常独特的名称。 - Erik
@Erik,你现在可以检查我的答案了。 - Dineshaws
@Erik 不,它仍然是一些概率性的东西。这并不一定是坏事,取决于需求。 - ItalyPaleAle
@Erik,你可以将ID与数据库(或文件系统)进行比对,或者在文件名开头添加时间戳。像shortid这样的模块被设计成碰撞的几率极低,几乎为0,如果你在前面加上类似时间戳的东西,你可以100%确定它永远不会导致碰撞(而且很难猜测)。 - ItalyPaleAle
显示剩余4条评论
7个回答

24

更新:shortid已弃用,请使用Nano ID代替。下面的答案同样适用于Nano ID。


我将在回答中发表我的评论,并回应你的关注。
你可能想查看shortid NPM模块,它会生成类似于你作为示例发布的短标识符。结果是可配置的,但默认情况下是一个随机长度在7到14个字符之间的字符串,全部适用于URL(在正则表达式中表示为A-Za-z0-9\_-)。
回答你(和其他帖子作者)的关注:
  • 除非你的服务器有真正的随机数生成器(几乎不可能),否则每个解决方案都将使用伪随机数生成器(PRNG)。shortid使用Node.js加密模块生成PRNG数字,这比Math.random()更好。
  • shortid不是顺序的,这使得猜测它们变得更加困难。
  • 虽然不能保证shortid是唯一的,但碰撞的可能性极小。除非你每年生成数十亿条记录,否则可以安全地假设永远不会发生碰撞。
  • 对于大多数情况,依靠概率来信任不会发生碰撞已经足够了。如果你的数据太重要而无法冒险,你可以通过在shortid前面添加时间戳使其基本上100%唯一。作为额外的好处,文件名也更难猜测。(注意:我写“基本上100%唯一”,因为理论上仍然可能存在碰撞,如果在同一时间戳(即同一秒钟)生成两个项目。但是,我从不担心这个问题。要获得真正的100%确定性,你唯一的选择是针对数据库或文件系统运行检查,但这需要更多资源。)
  • shortid不自动执行此操作的原因是,对于大多数应用程序,碰撞的可能性太小而不值得关注,而且拥有尽可能短的ID更为重要。

追加时间戳并非必要。shortid 确保在同一秒内的多次调用生成唯一的ID - Richard Poole
@RichardPoole 这正是我建议将时间戳作为一个选项的原因(绝对不是必需的)。在不同的秒钟内,理论上可以重新生成相同的ID。当然,这取决于您生成了多少个ID,并且我上面提出的所有观点都是有效的。 - ItalyPaleAle
我一直在使用的方法,不管使用的生成器是什么(例如shortid或xxhash),都是每次上传文件时递增一个整数计数器,该计数器基本上跟踪自服务器运行以来上传的文件数量,并将此计数器插入到文件名的种子中。结合插入Unix时间戳的这种方法,我认为不可能发生任何冲突,除非您上传2^53个文件而从未重新启动服务器。 - Gaboik1
事实上,要使用这种方法发生碰撞,必须在一秒钟内上传2^53个文件,我相当确定这是不可能的。 - Gaboik1
@Gaboik1,这个问题在于你需要一种存储唯一索引的方式 - 例如数据库。每次生成ID都需要进行两次数据库调用:一次读取索引,另一次更新索引。使用shortId(等同于)加上时间戳,即使在大规模情况下,碰撞的可能性已经非常小了。它也是无状态的,因此适用于分布式系统,而不需要集中式的数据库/存储库,这将成为瓶颈。 - ItalyPaleAle

18

一个选择是生成唯一标识符(UUID)并相应地重命名文件。

请查看kelektiv/node-uuid npm 模块。


示例:

$ npm install uuid

...那么在你的JavaScript文件中:

const uuidv4 = require('uuid/v4'); // I chose v4 ‒ you can select others
var filename = uuidv4(); // '110ec58a-a0f2-4ac4-8393-c866d813b8d1'

每次执行 uuidv4() 时,您都会得到一个非常新鲜的UUID。

注意:还有其他类型的UUID可供选择。请阅读该模块的文档以熟悉它们。


我需要为上传的文件命名。 - Erik
@Erik:这就是原因。你从某个地方接收到一些流,当你保存它时,你给它命名。即使如此,你仍然可以重命名它。 - x80486
@ Machina 是的,但我仍然需要生成唯一的名称。 - Erik
1
为什么要使用sudo?在我看来,npm install --save node-uuid。 - Guy
1
缩短UUID是一个不好的想法,因为当你缩短它们时,不能保证唯一性。 - eedrah

5
非常简单的代码。生成一个几乎独一无二的文件名,如果这还不够你可以检查该文件是否存在。
function getRandomFileName() {
var timestamp = new Date().toISOString().replace(/[-:.]/g,"");  
var random = ("" + Math.random()).substring(2, 8); 
var random_number = timestamp+random;  
return random_number;
}

2
export default generateRandom = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(23).substring(2, 5);

就是这么简单!

这对于一些简单的使用情况来说很好用且功能齐全。 - doc-han

0
function uniqueFileName( filePath, stub)
{
    let id = 0;
    let test = path.join(filePath, stub + id++);
    while (fs.existsSync(test))
    {
        test = path.join(filePath, stub + id++);
    }
    return test;
}

-5

我认为您可能对真随机和伪随机感到困惑。

伪随机字符串“通常表现出统计上的随机性,同时由完全确定性的随机过程生成”。这意味着,如果您将这些随机值用作加密应用程序中的熵,则不应使用伪随机生成器。

然而,就您的使用而言,我认为这样做没问题——只需检查可能存在的(极少发生的)冲突即可。

您所要做的只是创建一个随机字符串——而不是确保它是100%安全和完全随机的。


除非您有一个TRNG硬件模块,这是非常不可能的,否则每个软件解决方案都使用PRNG。然而,有些解决方案比较"随机":例如使用Node.js crypto模块比Math.random()更好的PRNG. - ItalyPaleAle

-6
尝试以下代码片段:
    function getRandomSalt() {
    var milliseconds = new Date().getTime();
    var timestamp = (milliseconds.toString()).substring(9, 13)
    var random = ("" + Math.random()).substring(2, 8);
    var random_number = timestamp+random;  // string will be unique because timestamp never repeat itself
    var random_string = base64_encode(random_number).substring(2, 8); // you can set size here of return string
    var return_string = '';
    var Exp = /((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+[0-9a-z]+$/i;
    if (random_string.match(Exp)) {                 //check here whether string is alphanumeric or not
        return_string = random_string;
    } else {
        return getRandomSalt();  // call recursivley again
    }
    return return_string;
}

文件名可能具有根据您的要求唯一性的字母数字名称。基于当前时间戳的概念,唯一名称是因为当前时间在未来不会重复,并且为了使其更强大,我应用了base64encode,它将被转换为字母数字。

   var file = req.files.profile_image;
   var tmp_path = file.path;
   var fileName = file.name;
   var file_ext = fileName.substr((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
   var newFileName = getRandomSalt() + '.' + file_ext;

谢谢


getRandomSalt 总是生成相同的字符串。 - Erik
谢谢你的代码。我们出现重复id的几率有多大? - Erik
不。它是时间戳和随机数的组合。接着进行了Base64编码。你可以在那之后检查数十亿条数据,并验证我的回答。 - Dineshaws
如果您选择将大字符串作为getRandomSalt函数的返回字符串,那将是非常好的。 - Dineshaws
有关时间戳永远不会重复的评论可能有误导性。当我键入 for(i=0; i < 60; ++i) console.log(new Date().getTime()); 时,我可以看到时间戳重复了几十次。我想,如果您在运行代码的机器上保证消耗超过一毫秒的其余部分,则该评论是正确的。无论如何,大多数时间戳字符串都没有改变,因此对唯一性的贡献非常小。我在节点上运行它,在前60次迭代中就发生了冲突。运行了600次,得到了堆栈溢出(尝试证明递归停止!)。 - Ron Burk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接