在PHP中生成短唯一ID

60
我想创建一个独特的 ID,但是uniqid()返回的结果类似于'492607b0ee414'。我希望得到类似于TinyURL提供的'64k8ra'的字符串,越短越好。唯一的要求是它不应该有明显的顺序,并且应该比看起来像随机数字序列的字符更漂亮。字母比数字更受欢迎,理想情况下不会混合大小写字母。由于条目数量不会很多(最多10000个左右),碰撞的风险并不是很大。 欢迎任何建议。

6
由于uniqid是基于时间戳的,因此前6个字符会很长一段时间内保持相同 ;) 即使我取最后x个字符或以某种方式组合它们,我仍然认为有更简洁的方法。类似' x1f '这样的东西会比较好。 - Antti
1
你找到解决方案了吗?如果是的话,请分享或者采纳答案。 - Till
是的,我采用了lpfavreau建议的方法,不过稍作修改。由于项目列表相当小,我可以在内存中检查冲突。 - Antti
如果你想要随机的、短小的、无序的、只包含字母的小写字符串,你可以使用 Random::alphaLowercaseString(6) 来获取,或者根据你的需要选择长度为 8 或 10。 - caw
16个回答

53

编写一个小函数,根据给定的长度返回随机字母:

<?php
function generate_random_letters($length) {
    $random = '';
    for ($i = 0; $i < $length; $i++) {
        $random .= chr(rand(ord('a'), ord('z')));
    }
    return $random;
}

然后你需要调用它,直到它变得唯一,在伪代码中,取决于您在哪里存储该信息:

do {
    $unique = generate_random_letters(6);
} while (is_in_table($unique));
add_to_table($unique);

你可能还希望确保这些字母在任何字典中都不能组成一个单词,无论是整个英语词典还是仅包含脏话的字典,以避免顾客感到不适。

编辑:如果你想要使用此方法来处理大量项,可能会变得非常慢,因为会有更多的冲突(获取已经在表中的ID)。当然,你需要一个索引表,并调整ID中字母的数量以避免冲突。在这种情况下,使用6个字母,你将有26 ^ 6 = 308915776个可能的唯一ID(减去不良单词),这应该足够满足你10000个的需求。

编辑: 如果你想要字母和数字的组合,可以使用以下代码:

$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));

4
应将 ord('a')ord('z') 放在循环外,以避免在每次迭代时调用该函数。 - Scalpweb
对于字母和数字的组合,您将使用26个字母平等地加权10个数字。因此,您将比随机情况下拥有更多的数字。为了使每个数字和字母具有相同的概率,您可以执行$random .= rand(0,35) < 10 ? rand(0,9) : chr(rand(ord('a'), ord('z'))); - Buttle Butkus

33

@gen_uuid() 由gord编写。

preg_replace存在一些讨厌的UTF-8问题,导致UID有时会包含“+”或“/”。 为了解决这个问题,您必须明确地将模式设置为UTF-8。

function gen_uuid($len=8) {

    $hex = md5("yourSaltHere" . uniqid("", true));

    $pack = pack('H*', $hex);
    $tmp =  base64_encode($pack);

    $uid = preg_replace("#(*UTF8)[^A-Za-z0-9]#", "", $tmp);

    $len = max(4, min(128, $len));

    while (strlen($uid) < $len)
        $uid .= gen_uuid(22);

    return substr($uid, 0, $len);
}

花了我相当长的时间才找到这个,或许能帮助其他人避免头疼


31

你可以用更少的代码实现此目标:

function gen_uid($l=10){
    return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz"), 0, $l);
}

结果 (示例):

  • cjnp56brdy
  • 9d5uv84zfa
  • ih162lryez
  • ri4ocf6tkj
  • xj04s83egi

11
很好的解决方案,但它只能返回每个字母的1次出现,这限制了可能性。我稍微修改了它:function gen_uid($l=10){ $str = ""; for ($x=0;$x<$l;$x++) $str .= substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz"), 0, 1); return $str; } - LobsterMan
@LobsterMan,你的解决方案更加随机,这很好。但是对于你的解决方案,每次都洗牌字符串是完全不必要的。无论你的$character_list是什么(alnum等),你只需要取一个随机的单字母子串:$str .= substr($character_list,rand(0,61),1);(我用[0-9A-Za-z]测试了一下,共62个字符)。如果速度很重要,我运行了这个版本100万次,时间为4.57-4.93秒,而你的版本则需要9.96-10.34秒。 - Buttle Butkus

20

获得可靠唯一ID的方法有两种:使其变得非常长和多变,以使冲突的机会极小(例如GUID),或将所有生成的ID存储在表中进行查找(无论是在内存中还是在DB或文件中)以验证其唯一性。

如果您真的想知道如何生成这样一个短键并保证其唯一性而不进行任何重复检查,那么答案是,您不能。


13

这是我用于生成任意长度随机base62字符串的例程...

调用gen_uuid()将返回像WJX0u0jV,E9EMaZ3P之类的字符串。

默认情况下,此函数返回8个数字,因此可以提供64^8或大约10^14个空间,这通常足以使冲突非常少。

对于更大或更小的字符串,请传递所需的$len。长度没有限制,我一直添加到满意为止[最高安全限制为128个字符,可以删除]。

请注意,在md5 [或如果您喜欢,则为sha1]中使用随机盐值,以便它不能轻松地进行反向工程。

我在网上没有找到可靠的base62转换方法,因此采用了从base64结果中去除字符的方法。

在BSD许可证下免费使用,祝你使用愉快!

gord

function gen_uuid($len=8)
{
    $hex = md5("your_random_salt_here_31415" . uniqid("", true));

    $pack = pack('H*', $hex);

    $uid = base64_encode($pack);        // max 22 chars

    $uid = ereg_replace("[^A-Za-z0-9]", "", $uid);    // mixed case
    //$uid = ereg_replace("[^A-Z0-9]", "", strtoupper($uid));    // uppercase only

    if ($len<4)
        $len=4;
    if ($len>128)
        $len=128;                       // prevent silliness, can remove

    while (strlen($uid)<$len)
        $uid = $uid . gen_uuid(22);     // append until length achieved

    return substr($uid, 0, $len);
}

$uid = ereg_replace("["A-Z0-9]","",strtoupper($uid)); 请将 $uid 中的大写字母和数字替换为空,并将剩余字符串中的小写字母转换为大写字母。 - gord
如果盐是一个变量,那么有没有办法反向查找这个盐变量呢? - Jon

12

非常简单的解决方案:

使用以下方法生成唯一ID:

$id = 100;
base_convert($id, 10, 36);

再次获取原始值:

intval($str,36);

我不能为此内容负责,因为它来自于另一个stackoverflow页面。但是,我认为这个解决方案非常优雅和棒,值得将其复制到本主题供其他人参考。


1
这完全失败了,"唯一的要求是它不应该有明显的顺序"。 - Kaktus

6
您可以使用ID并将其转换为36进制数,以实现ID的相互转换。这适用于任何带有整数ID的表格。
function toUId($baseId, $multiplier = 1) {
    return base_convert($baseId * $multiplier, 10, 36);
}
function fromUId($uid, $multiplier = 1) {
    return (int) base_convert($uid, 36, 10) / $multiplier;
}

echo toUId(10000, 11111);
1u5h0w
echo fromUId('1u5h0w', 11111);
10000

聪明的人可能可以通过足够的示例来理解它。不要让这种晦涩替代安全。

有没有办法在使用base_convert()函数时包括大写字母、小写字母和0-9?base_convert($uid,62,10)是否可行? - JoshFinnie
JoshFinnie:如果要处理高于36进制的情况,你需要自己编写区分大小写的函数。 - OIS

4

我想到了一个非常酷的解决方案,可以在不进行唯一性检查的情况下完成此操作。为了给未来的访问者提供帮助,我想分享一下我的方法。

计数器是保证唯一性的一种非常简单的方法,或者如果您正在使用数据库,则主键也可以保证唯一性。问题在于它看起来很丑陋,而且可能存在漏洞。所以我将序列与密码混合在一起。由于密码可以反转,因此我知道每个id都是唯一的,同时看起来是随机的。

这是Python而不是PHP,但我上传了代码: https://github.com/adecker89/Tiny-Unique-Identifiers


3

字母很漂亮,数字很丑。

您想要随机字符串,但不想要"丑陋"的随机字符串?

创建一个随机数并以字母样式26进制)打印出来,就像航空公司给出的预订“编号”。

据我所知,PHP中没有通用的基本转换函数,因此您需要自己编写那一部分代码。

另一个选择:使用uniqid()并摆脱数字。

function strip_digits_from_string($string) {
    return preg_replace('/[0-9]/', '', $string);
}

或将它们替换为字母:
function replace_digits_with_letters($string) {
    return strtr($string, '0123456789', 'abcdefghij');
}

这非常接近我想要的。航空公司机票ID也是一个很好的例子。基本上我的目的是找到一种好的方式来创建这个随机的 ~3-5 字符/数字代码,然后将其转换为字符串。Uniqid 除此之外还不错,只是太长了。 - Antti

1

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