在ruby / ActiveRecord中生成类似Instagram或Youtube的不可语句字符串ID

在创建给定的ActiveRecord模型对象的实例时,我需要生成一个短的(6-8个字符)唯一字符串,用作URL中的标识符,采用Instagram的照片URL样式(如http://instagram.com/ p / P541i4ErdL / ,我刚刚加入404)或Youtube的videourl(如http://www.youtube.com/watch?v=oHg5SJYRHA0 )。

这样做的最佳方法是什么? 最简单的方法就是重复创建一个随机字符串 ,直到它是唯一的? 有没有办法散列/随机播放整数id,以便用户不能通过更改一个字符来破解URL(就像我上面的404’d Instagram链接那样)并最终获得新记录?

你可以这样做:

random_attribute.rb

module RandomAttribute def generate_unique_random_base64(attribute, n) until random_is_unique?(attribute) self.send(:"#{attribute}=", random_base64(n)) end end def generate_unique_random_hex(attribute, n) until random_is_unique?(attribute) self.send(:"#{attribute}=", SecureRandom.hex(n/2)) end end private def random_is_unique?(attribute) val = self.send(:"#{attribute}") val && !self.class.send(:"find_by_#{attribute}", val) end def random_base64(n) val = base64_url val += base64_url while val.length < n val.slice(0..(n-1)) end def base64_url SecureRandom.base64(60).downcase.gsub(/\W/, '') end end Raw 

user.rb

 class Post < ActiveRecord::Base include RandomAttribute before_validation :generate_key, on: :create private def generate_key generate_unique_random_hex(:key, 32) end end 

这是一个很好的方法,没有在plpgsql中实现冲突。

第一步 :考虑PG wiki中的pseudo_encrypt函数。 此函数采用32位整数作为参数,并返回一个32位整数,该整数对人眼看起来是随机的,但唯一对应于其参数(因此加密,而不是散列)。 在函数内部,您可以更改公式: (((1366.0 * r1 + 150889) % 714025) / 714025.0)使用另一个只有您知道的函数,在[0..1]范围内产生结果(只需调整常量)可能会很好,见下面我尝试这样做)。 有关更多理论解释,请参阅Feistel cypher上的维基百科文章。

第二步 :使用您选择的字母表编码输出编号。 这是一个函数,它在base 62中使用所有字母数字字符。

 CREATE OR REPLACE FUNCTION stringify_bigint(n bigint) RETURNS text LANGUAGE plpgsql IMMUTABLE STRICT AS $$ DECLARE alphabet text:='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; base int:=length(alphabet); _n bigint:=abs(n); output text:=''; BEGIN LOOP output := output || substr(alphabet, 1+(_n%base)::int, 1); _n := _n / base; EXIT WHEN _n=0; END LOOP; RETURN output; END $$ 

现在,我们将获得与单调序列对应的前10个URL:

 select stringify_bigint(pseudo_encrypt(i)) from generate_series(1,10) as i; 
  stringify_bigint 
 ------------------
  tWJbwb
  eDUHNb
  0k3W4b
  w9dtmc
  wWoCi
  2hVQz
  PyOoR
  cjzW8
  bIGoqb
  A5tDHb

结果看起来是随机的,并且保证在整个输出空间中是唯一的(如果您使用带有负整数的整个输入空间,则为2 ^ 32或大约40亿个值)。 如果40亿个值不够宽,您可以仔细地将两个32位结果组合成64位,同时不会丢失输出中的单一性。 棘手的部分正确处理符号位并避免溢出。

关于修改函数以生成自己独特的结果:让我们在函数体中将常量从1366.0更改为1367.0,然后重试上面的测试。 看看结果如何完全不同:

  NprBxb
  sY38Ob
  urrF6b
  OjKVnc
  vdS7j
  uEfEB
  3zuaT
  0fjsab
  j7OYrb
  PYiwJb

更新:对于那些可以编译C扩展的人来说, pseudo_encrypt()一个很好的替代是pseudo_encrypt() permuteseq extension range_encrypt_element() ,它具有以下优点:

  • 适用于最高64位的任何输出空间,并且它不必是2的幂。

  • 使用秘密的64位密钥进行不可知的序列。

  • 如果这很重要,速度要快得多。

你可以散列id:

 Digest::MD5.hexdigest('1')[0..9] => "c4ca4238a0" Digest::MD5.hexdigest('2')[0..9] => "c81e728d9d" 

但有人仍然可以猜测你正在做什么,并以这种方式迭代。 对内容进行哈希处理可能更好