English 中文(简体)
PHP中的短唯一ID
原标题:
  • 时间:2008-11-21 01:05:57
  •  标签:

我想创建一个独特的ID,但是uniqid()给出的结果类似于 492607b0ee414 。我想要的是类似于tinyurl给出的 64k8ra 的东西。越短越好。唯一的要求是它不应该具有明显的顺序,看起来比一个看似随机的数字序列更漂亮。字母优于数字,理想情况下不应该含有大小写混合。由于记录数不会太多(最多10000个),碰撞的风险不是一个巨大的因素。

任何建议都受到欢迎。

最佳回答

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

<?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的需求。

EDIT: If you want a combinations of letters and numbers you can use the following code:

$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord( a ), ord( z )));
问题回答

由gord创造的@gen_uuid()

preg_replace got some nasty utf-8 problems, which causes the uid somtimes to contain "+" or "/". To get around this, you have to explicitly make the pattern 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);
}

我花了相当长时间才找到了那个,也许这能帮助其他人避免一些烦恼。

你可以用更少的代码完成这个。

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

结果(例子):

  • cjnp56brdy
  • 9d5uv84zfa
  • ih162lryez
  • ri4ocf6tkj
  • xj04s83egi

有两种方式可以获得一个可靠的唯一标识符:使其长度和变量太大,以至于发生碰撞的几率极小(与 GUID 一样),或者将所有生成的标识符存储在表中进行查找(在内存中、数据库中或文件中),以在生成时验证唯一性。

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

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

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

By default this returns 8 digits, hence a space of 64^8 or roughly 10^14, this is often enough to make collisions quite rare.

对于更长或更短的字符串,请根据需要将$len传入。长度没有限制,我会一直添加直到满足 [最高安全限制为128个字符,可删除]。

注意,在md5 [或sha1,如果您喜欢]内使用随机盐,以便它不能轻易地被逆向工程。

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

Use freely under BSD licence, enjoy,

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

非常简单的解决方案:

使用以下内容生成唯一的ID:

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

再次获得原始值:

intval($str,36);

不能因为这是从另一个堆栈溢出页面得来的而自夸,但我认为这个解决方案非常优雅和棒,值得复制到这个帖子中供人们参考。

如果您想进行转换,可以使用ID并将其转换为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都是唯一的,同时看起来是随机的。

It s python not php, but I uploaded the code here: https://github.com/adecker89/Tiny-Unique-Identifiers

Letters are pretty, digits are ugly. You want random strings, but don t want "ugly" random strings?

创建一个随机数并按字母样式 (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 );
}

你也可以像这样做:

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;
    }

你可以毫不费劲地做到这一点,而不需要像循环、字符串连接或多次调用rand()这样的脏乱昂贵的东西,以一种干净且易于阅读的方式进行。此外,最好使用mt_rand():

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

如果您需要字符串在任何情况下都具有相同的长度,请使用零填充十六进制数字:

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 is limited in what it can represent as a hex number doing it like this. This would be $length <= 8 at least on a 32bit system, where PHPs limitation for this should be 4.294.967.295 .
  • PHPs random number generator also has a maximum. For mt_rand() at least on a 32bit system, it should be 2.147.483.647
  • So you are theoretically limited to 2.147.483.647 IDs.

回到话题 - 直觉 do { (生成 ID) } while { (ID 不唯一) } (插入 ID) 有一个缺点和一个可能的缺陷,可能会将您带入黑暗之中...

缺点:验证是悲观的。像这样做始终需要在数据库中进行检查。拥有足够的密钥空间(例如,您的10k条目的长度为5)很不可能经常引起冲突,因为只需要尝试存储数据,并在出现唯一键错误的情况下重试可能会更加节省资源。

缺陷: 用户A 检索到一个尚未被使用的ID并将其验证。然后,代码将尝试插入数据。但与此同时,用户B也进入了相同的循环,并不幸地检索到了相同的随机数,因为用户A尚未存储,因此该ID仍然是空闲的。现在系统会存储用户B用户A中的一个。当尝试存储第二个用户时,可能已经有另一个用户在此期间存储了相同的ID。

无论如何,您都需要处理该异常,并需要使用新创建的ID重新尝试插入。在保持悲观检查循环(您需要重新输入)的情况下添加此操作将导致相当丑陋且难以跟踪的代码。幸运的是,解决此问题的方法与缺点相同:在第一次尝试存储数据时,尝试进行操作。如果发生唯一键错误,请使用新的ID重试。

看一下这篇文章

它解释了如何从您的BDD IDs生成类似YouTube ID的短唯一ID。

实际上,文章中的函数与PHP函数base_convert非常相关,它将一个数字从一个进制转换为另一个进制(但只能到36进制)。

十个字符:

substr(uniqid(),-10);

5个二进制字符:

hex2bin( substr(uniqid(),-10) );

8个Base64字符:

base64_encode( hex2bin( substr(uniqid(),-10) ) );
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);
}

If you do like a longer version of unique Id use this:
$uniqueid = sha1(md5(time()));

目前最佳答案:给定唯一数据库 ID,最小唯一“哈希类”字符串 - PHP 解决方案,无需第三方库。

这是代码:

<?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
";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1 maps to $base36value
";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1987645 maps to $base36value
";

// 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;

}

?>




相关问题
热门标签