php中的短唯一ID

问题描述 投票:46回答:15

我想创建一个独特的id,但uniqid()正在给像'492607b0ee414'。我想要的是类似于tinyurl给出的东西:'64k8ra'。越短越好。唯一的要求是它不应该有明显的顺序,它应该看起来比看似随机的数字序列更漂亮。字母比数字更受欢迎,理想情况下不会是混合字母。由于参赛人数不会那么多(最多10000人左右),因此碰撞的风险并不是一个很大的因素。

任何建议赞赏。

php uniqueidentifier unique-index
15个回答
42
投票

创建一个小函数,返回给定长度的随机字母:

<?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')));

1
投票

你也可以这样做:

public static function generateCode($length = 6)
    {
        $az = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $azr = rand(0, 51);
        $azs = substr($az, $azr, 10);
        $stamp = hash('sha256', time());
        $mt = hash('sha256', mt_rand(5, 20));
        $alpha = hash('sha256', $azs);
        $hash = str_shuffle($stamp . $mt . $alpha);
        $code = ucfirst(substr($hash, $azr, $length));
        return $code;
    }

1
投票

你可以用干净,易于阅读的方式做到这一点,不需要循环,字符串连接或多次调用rand()等不干净/有价值的东西。此外,最好使用mt_rand()

function createRandomString($length)
{
    $random = mt_rand(0, (1 << ($length << 2)) - 1);
    return dechex($random);
}

如果在任何情况下都需要String具有精确的长度,只需用十进制填充十六进制数:

function createRandomString($length)
{
    $random = mt_rand(0, (1 << ($length << 2)) - 1);
    $number = dechex($random);
    return str_pad($number, $length, '0', STR_PAD_LEFT);
}

“理论上的反向”是,你仅限于PHP的功能 - 但在这种情况下这更像是一个哲学问题;)无论如何我们都要经历它:

  • PHP的限制在于它可以表示为十六进制数字。至少在32位系统上这将是$length <= 8,其中PHP的限制应该是4.294.967.295。
  • PHP随机数生成器也有最大值。对于至少在32位系统上的mt_rand(),它应该是2.147.483.647
  • 所以理论上你只限于2.147.483.647 ID。

回到主题 - 直观的do { (generate ID) } while { (id is not uniqe) } (insert id)有一个缺点和一个可能的缺陷,可能会让你直接到黑暗...

缺点:验证是悲观的。这样做总是需要检查数据库。拥有足够的密钥空间(例如,10k条目的长度为5)将不太可能经常发生冲突,因为仅仅尝试存储数据并且仅在UNIQUE KEY错误的情况下重试可能相对较少消耗资源。

缺陷:用户A检索已验证为尚未使用的ID。然后代码将尝试插入数据。但与此同时,用户B进入相同的循环并且不幸地检索到相同的随机数,因为用户A尚未存储且该ID仍然是免费的。现在系统存储用户B或用户A,并且当试图存储第二个用户时,同时已经存在另一个用户 - 具有相同的ID。

在任何情况下都需要处理该异常,并且需要使用新创建的ID重新尝试插入。在保持悲观检查循环(您需要重新输入)的同时添加它将导致非常难看且难以遵循的代码。幸运的是,这个问题的解决方案与缺点相同:只是首先考虑它并尝试存储数据。如果出现UNIQUE KEY错误,只需使用新ID重试。


1
投票

看看这篇文章

它解释了如何从你的bdd id中生成短的唯一ID,就像youtube一样。

实际上,文章中的函数与php function base_convert非常相关,function rand_str($len = 12, $type = '111', $add = null) { $rand = ($type[0] == '1' ? 'abcdefghijklmnpqrstuvwxyz' : '') . ($type[1] == '1' ? 'ABCDEFGHIJKLMNPQRSTUVWXYZ' : '') . ($type[2] == '1' ? '123456789' : '') . (strlen($add) > 0 ? $add : ''); if(empty($rand)) $rand = sha1( uniqid(mt_rand(), true) . uniqid( uniqid(mt_rand(), true), true) ); return substr(str_shuffle( str_repeat($rand, 2) ), 0, $len); } 将一个数字从一个数字转换为另一个数字(但只能达到36的基数)。



0
投票

如果您确实喜欢更长版本的唯一ID,请使用以下命令: $ uniqueid = sha1(md5(time()));


0
投票

最佳答案:<?php /* THE FOLLOWING CODE WILL PRINT: A database_id value of 200 maps to 5K A database_id value of 1 maps to 1 A database_id value of 1987645 maps to 16LOD */ $database_id = 200; $base36value = dec2string($database_id, 36); echo "A database_id value of 200 maps to $base36value\n"; $database_id = 1; $base36value = dec2string($database_id, 36); echo "A database_id value of 1 maps to $base36value\n"; $database_id = 1987645; $base36value = dec2string($database_id, 36); echo "A database_id value of 1987645 maps to $base36value\n"; // HERE'S THE FUNCTION THAT DOES THE HEAVY LIFTING... function dec2string ($decimal, $base) // convert a decimal number into a string using $base { //DebugBreak(); global $error; $string = null; $base = (int)$base; if ($base < 2 | $base > 36 | $base == 10) { echo 'BASE must be in the range 2-9 or 11-36'; exit; } // if // maximum character string is 36 characters $charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // strip off excess characters (anything beyond $base) $charset = substr($charset, 0, $base); if (!ereg('(^[0-9]{1,50}$)', trim($decimal))) { $error['dec_input'] = 'Value must be a positive integer with < 50 digits'; return false; } // if do { // get remainder after dividing by BASE $remainder = bcmod($decimal, $base); $char = substr($charset, $remainder, 1); // get CHAR from array $string = "$char$string"; // prepend to output //$decimal = ($decimal - $remainder) / $base; $decimal = bcdiv(bcsub($decimal, $remainder), $base); } while ($decimal > 0); return $string; } ?>

这是代码:

qazxswpoi

27
投票

@gen_uuid()来自gord。

preg_replace遇到了一些讨厌的utf-8问题,导致uid somtimes包含“+”或“/”。要解决这个问题,你必须明确地将模式设为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);
}

我花了很长时间才发现这一点,也许这让别人感到头疼


24
投票

您可以用更少的代码实现这一目标:

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

结果(例子):

  • cjnp56brdy
  • 9英尺4兹瓦
  • ih162lryez
  • ri4ocf6tkj
  • xj04s83egi

17
投票

有两种方法可以获得可靠的唯一ID:使它变得如此长且可变,以至于碰撞的可能性非常小(与GUID一样)或将所有生成的ID存储在表中以供查找(在内存或数据库中)或文件)以验证生成时的唯一性。

如果您真的问如何生成这样一个短密钥并保证其独特性而不需要进行某种重复检查,答案是,您不能。


11
投票

这是我用于任意长度的随机base62s的例程......

调用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);
}

11
投票

非常简单的解决方案:

使用以下内容创建唯一ID:

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

再次获取原始值:

intval($str,36);

不能因为它来自另一个堆栈溢出页面而受到赞誉,但我认为该解决方案非常优雅且非常棒,以至于值得复制到此线程以供引用此功能的人使用。


4
投票

您可以使用Id并将其转换为base-36数字,如果您想要来回转换它。可用于任何具有整数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

聪明的人可能会用足够的id示例来解决它。不要让这种默默无闻取代安全。


4
投票

我想出了一个非常酷的解决方案,没有进行唯一性检查。我想我会分享给未来的访客。

计数器是保证唯一性的一种非常简单的方法,或者如果您使用数据库,主键也可以保证唯一性。问题是它看起来很糟糕,而且可能很脆弱。所以我接受了序列并用密码混淆了它。由于密码可以反转,我知道每个id都是唯一的,同时仍然是随机出现的。

这是python而不是php,但我在这里上传了代码:https://github.com/adecker89/Tiny-Unique-Identifiers


3
投票

字母很漂亮,数字很难看。你想要随机字符串,但不想要“丑陋”的随机字符串?

创建一个随机数并以alpha风格(base-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');
}
© www.soinside.com 2019 - 2024. All rights reserved.