如何在Ruby中生成一个随机且唯一的字符串?

13
在我正在开发的Ruby on Rails应用程序中,我允许用户上传文件,并希望为这些文件赋予一个短的、随机的字母数字名称。(例如“g7jf8”或“3bp76”)。最好的方法是什么?
我考虑从原始文件名和时间戳生成哈希/加密字符串。然后查询数据库以双重检查它不存在。如果存在,则生成另一个并重复此过程。
我认为这种方法的问题在于,如果有很高的重复字符串概率,它可能会增加相当多的数据库负载。

1
还有一种潜在(虽然不太可能的)竞争条件,即两个请求同时尝试添加相同的名称。该数据库应该对该列具有唯一约束,并且您应该准备捕获ActiveRecord :: RecordNotUnique异常。 - mpartel
请生成一个包含小写字母和数字的唯一随机字符串。详见:https://dev59.com/fG025IYBdhLWcg3wf2OE - sameera207
“random” 这个名称是否有安全目的?如果没有,您有更多选择。 - Neil Slater
不需要安全性,但可能会在URL中使用。感谢迄今为止所有的帮助。有许多想法可以尝试和实践。 - user1746971
8个回答

12

我使用这个 :)

def generate_token(column, length = 64)
  begin
    self[column] = SecureRandom.urlsafe_base64 length
  end while Model.exists?(column => self[column])
end

Model 替换为您的模型名称


10
SecureRandom.uuid

将为您提供一个全球唯一的字符串。http://en.m.wikipedia.org/wiki/Universally_unique_identifier

SecureRandom.hex 32

这个函数会返回一个随机字符串,但它的算法没有被优化用于确保唯一性。当然,在假定真正的随机情况下,32位数字之间的碰撞概率基本上是理论上的。即使你每秒钟可以生成10亿个字符串并且持续100年,也只有50%的机会发生碰撞。


6

3
对于这个问题,我的首选可能是SecureRandom.urlsafe_base64 - Neil Slater

1

这将始终生成新的唯一的40个大小的字母数字字符串,因为它还具有时间戳。

loop do
  random_token = Digest::SHA1.hexdigest([Time.now, rand(111..999)].join)
  break random_token unless Model.exists?(column_name: random_token)
end

注意:将 Model 替换为您的 model_name,将 column_name 替换为您的模型中任何现有列。

0

如果你最终生成了十六进制或数字摘要,你可以将这个数字表示为62进制,从而使代码更简短:

# This is a lightweight base62 encoding for Ruby integers.
B62CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a

def base62_string nbr
  b62 = ''
  while nbr > 0
    b62 << B62CHARS[nbr % 62]
    nbr /= 62
  end
  b62.reverse
end

如果您重视限制使用的字符集(例如文件名中不包含大写字母),那么只要您能找到一种合适的随机数输入方式,就可以轻松地调整此代码。
如果您的文件名应该是半安全的,则需要确保存储中有比实际名称更多的可能名称。

这并不会生成任何独特的东西,这正是 OP 所要求的。对于相同的 nbr,每次都会返回相同的字符串,并且您需要传递一个非常大的数字才能返回任何重要大小的字符串。例如:> base62_string 99999999999999999999 # => "1V973MbJYWoT" - Chris Bloom
@Chrisbloom7: 同意,这需要输入一个数字,我没有解释如何获取(但我在文本中提到了这是必需的)。在Ruby中生成一个适当大小的随机数非常简单:SecureRandom.random_number(2**128)。该方法也适用于序列、哈希等。需要大量输入来生成短字符串的事实实际上是OP所期望的,他们要求一个短字符串。 - Neil Slater

0

每次添加新文件时,您可以通过递增来分配一个唯一的ID,并使用OpenSSL::Cipher将该ID转换为加密字符串,使用您在某个地方保存的常量密钥。


0

看起来你需要一个唯一的文件名,对吧?为什么不放弃复杂的解决方案,直接使用Time#nsec呢?

t = Time.now        #=> 2007-11-17 15:18:03 +0900
"%10.9f" % t.to_f   #=> "1195280283.536151409"

如果应用程序很大且繁忙,并且有多个独立的服务器,最终会有两个同时处理文件并发生冲突。 - Chris Aitchison
将“nanosecs”与服务器名称连接起来,以确保完全正确。 - Aleksei Matiushkin
1
那时,我认为SecureRandom.uuid是一个更简单的解决方案。 - Chris Aitchison
在我的情况下,您可以直接获得可排序列表。但当然,这是一个品味问题。 - Aleksei Matiushkin

0

你可以使用毫秒时间,然后将其转换为36进制以缩短长度。 由于它依赖于时间,因此非常独特。

示例: Time.now.to_f.to_s.gsub('.', '').ljust(17, '0').to_i.to_s(36) # => "4j26lna7g62"

请参阅此答案: https://dev59.com/4VnUa4cB1Zd3GeqPWgJF#72738840


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