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