ROR - 为数据库ID生成一个字母数字字符串

4
在我们的数据库中,每个Person都有一个ID,这是由数据库生成的自增整数。现在,我们想要生成一个更用户友好的字母数字ID,可以公开展示,就像护照号码一样。我们显然不想向用户公开DB ID。对于这个问题,我将称我们需要生成的为UID。
注意:UID并不意味着要替换掉DB ID。您可以把UID看作是DB ID的漂亮版本,我们可以向用户提供它。
我在想,这个UID是否可以作为DB ID的函数。也就是说,我们应该能够为给定的DB ID重新生成相同的UID。
显然,该函数将除了DB ID之外还需要一个“盐”或密钥。
UID不应该是顺序的。也就是说,两个相邻的DB ID应该生成外观不同的UID。
虽然没有必要使UID不可逆。也就是说,如果有人研究了UID几天,并能够进行逆向工程并找到DB ID,那么这是可以接受的。我不认为这会对我们造成任何伤害。
UID应只包含A-Z(仅大写)和0-9。不能包含其他字符,这些字符可能与其他字母或数字混淆,如0和O,l和1等。我想Crockford的Base32编码可以解决这个问题。
UID应具有固定长度(10个字符),而与DB ID的大小无关。我们可以用一些常量字符串填充UID,以使其达到所需的固定长度。DB ID可以增长到任何大小。因此,该算法不应具有任何此类输入限制。
我认为解决这个问题的方法是:
步骤1:哈希。
我了解以下哈希函数:
SHA-1
MD5
Jenkin's
哈希返回一个长字符串。我在这里读到了有关称为XOR折叠的东西,可以将字符串缩短到较短的长度。但是我找不到太多关于它的信息。
步骤2:编码。
我了解以下编码方法:
Crockford Base 32编码
Z-Base32
Base36
我猜测编码的输出将是我要寻找的UID字符串。
步骤3:处理碰撞。
  • 为了避免冲突,我想知道在UID生成时能否生成一个随机密钥并在该函数中使用此随机密钥。
  • 我可以将此随机密钥存储在一列中,以便我们知道用于生成特定UID的密钥。
  • 在将新生成的UID插入表之前,我将检查唯一性,如果检查失败,我可以生成一个新的随机密钥并使用它来生成新的UID。对于特定的DB ID,可以重复此步骤直到找到唯一的UID。

我希望得到一些专家意见,了解我是否走在正确的轨道上,以及如何实际实现这一点。

我将在Ruby On Rails应用程序中实现这个,所以请考虑这一点在你的建议中。

谢谢。

更新

评论和答案让我重新考虑并质疑我所要求的其中一个需求:我们需要在分配一次后重新生成用户的UID。我想我只是想保险起见,在我们丢失用户的UID时,如果它是用户现有属性的功能,我们将能够重新获得它。但是我们可以通过备份解决这个问题,我想。

因此,如果我删除该要求,则UID基本上成为完全随机的10个字符的字母数字字符串。我正在添加一个包含我提出的实施计划的答案。如果其他人提出更好的计划,我将把它标记为答案。


根据年、月、日(甚至需要时间)创建一个基于数字的编号,并跟随 YYYYMMDD/NN 的格式?其中 NN 可以是字母或数字。 - Roger
你是在建议我只使用时间戳+字母数字常量作为UID吗?那对我来说有点太明显了。我不希望用户能够轻易地猜测ID背后的逻辑。 - Anjan
不清楚那里面有什么信息(假设是一个URL)。SO是怎么做的呢?一个编号+名称的组合(623581/anjan),这个组合很难猜到(但不是不可能)。 - Roger
正如我在问题中提到的那样,这个UID类似于护照号码。我们在数据库中有成千上万的用户,我显然不想在UID中使用名称。每个用户都应该被分配这个UID,并且这就是他们将要使用的来识别自己的标识符。这些用户实际上是学生,由学校在我们的数据库中注册,因此学生不能像传统的注册系统那样选择唯一的“用户名”。我们必须自动分配一个唯一的ID。我开始倾向于预先生成所有可能的10个字母数字字符的组合,并随机选择它们。 - Anjan
我确实看了你的问题;-) 但有时候更多地从不同角度思考会让问题变得清晰明了。 - Roger
@Rogier - 谢谢。你让我重新思考和质疑我的一些需求。我已经相应地更新了问题。 - Anjan
2个回答

2

正如我在问题更新中提到的,我认为我们要做的是:

  • 预先生成足够数量的随机且唯一的十个字符的字母数字字符串。不进行哈希或编码。
  • 将它们以随机顺序存储在一个表中。
  • 在创建用户时,选择第一个字符串并将其分配给用户。
  • 在将选定的ID分配给用户后,从ID池中删除此ID。
  • 当池减少到较低数量时,使用新的字符串补充池,并进行唯一性检查。这可以通过由观察者启动的延迟作业来完成。
  • 预先生成的原因是我们将所有昂贵的唯一性检查都转移到了一次性的预生成操作中。
  • 从池中选择ID为新用户保证了唯一性。因此,创建用户(非常频繁)的操作变得更快。

0

你觉得 db_id.chr 可以用吗?它可以将整数转换为字符字符串。然后,你可以在其后面添加他们的名字缩写或姓氏等内容。例如:

user = {:id => 123456, :f_name => "Scott", :l_name => "Shea"}
(user.id.to_s.split(//).map {|x| (x.to_i + 64).chr}).join.downcase + user.l_name.downcase

#result = "abcdefshea"

1
谢谢你的想法。我不想使用名称,因为它可以被用户更改。此外:(123456.to_s.split(//).map {|x| (x.to_i + 64).chr}).join.downcase(123457.to_s.split(//).map {|x| (x.to_i + 64).chr}).join.downcase 的结果是 abcdefabcdeg。这太相似了;连续的。正如我在问题中所说的:两个相邻的DB ID应该生成外观不同的UID。 - Anjan

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