Base10到base64 url​​缩短

时间:2010-07-07 23:59:46

标签: php url-shortener

我正在编写一个url缩短函数,用于我正在学习php的项目,这里是代码(顺便说一下,我认为global这里不是一件好事:P):

$alphabet = array(1 => "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
                "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
                "0","1","2","3","4","5","6","7","8","9","_","-");

function shorten($id){
    global $alphabet;
    $shortenedId = "";
    while($id>0){
        $remainder = $id % 64;
        $id = $id / 64;     
        $shortenedId = $alphabet[$remainder].$shortenedId;
    }
    return $shortenedId;
}

代码取自this Wikipedia article并适用于php。我的问题是,当我将64的倍数传递给函数时,我得到了一个错误的(为了我的目的)结果,例如128返回b不正确,它应该是aaa,但对于3位数来说太长了号。

此外,我开始认为这段代码有问题,如果我传递1'000'000'000'000 $id我得到nItOq ...我觉得这是错误的,因为网址缩短像bit.ly这样的服务如果我使用它会返回一个6位数的id,我认为这个算法不比他们好。

所以,有两个问题:

  • 您是否发现上述代码中的任何错误?
  • 如何管理64位多个ID?我是否必须忽略它们并传递给下一个?

8 个答案:

答案 0 :(得分:11)

只需要几个小的调整,主要的两个是使字母表零索引而不是一个索引,并在分割之前从id中减去余数

function shorten($id)
{
    $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
    $shortenedId = '';
    while($id>0) {
        $remainder = $id % 64;
        $id = ($id-$remainder) / 64;     
        $shortenedId = $alphabet{$remainder} . $shortenedId;
    };
    return $shortenedId;
}

这是一个进一步修改过的版本......我很喜欢

function shorten($id, $alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')
{
    $base = strlen($alphabet);
    $short = '';
    while($id) {
        $id = ($id-($r=$id%$base))/$base;     
        $short = $alphabet{$r} . $short;
    };
    return $short;
}

编辑:排序的连接与OP相同

答案 1 :(得分:5)

如果你正在寻找相反的功能来获取base64号码并转换为base10,这里有一些PHP基于这个答案中的JavaScript:How to convert base64 to base10 in PHP?

function lengthen($id) {
    $alphabet='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';

    $number=0;
    foreach(str_split($id) as $letter) {
        $number=($number*64) + strpos($alphabet,$letter);
    }
    return $number;
}

答案 2 :(得分:1)

顺便说一下,看看base_convert()函数(http://php.net/manual/en/function.base-convert.php):

echo base_convert(1000000000, 10, 36);
但是,36是它可以转换成的最长基数。但在评论部分我发现了这个:

function dec2any( $num, $base, $index=false ) {
    if (! $base ) {
        $base = strlen( $index );
    } else if (! $index ) {
        $index = substr( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ,0 ,$base );
    }
    $out = "";
    for ( $t = floor( log10( $num ) / log10( $base ) ); $t >= 0; $t-- ) {
        $a = floor( $num / pow( $base, $t ) );
        $out = $out . substr( $index, $a, 1 );
        $num = $num - ( $a * pow( $base, $t ) );
    }
    return $out;
}

echo dec2any(1000000000, 64, "_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");

也许会有所帮助?

答案 3 :(得分:1)

Paul Greg创建了一些从Base-10转换为另一个基础的PHP代码。这可以在这里测试并下载代码:

http://www.pgregg.com/projects/php/base_conversion/base_conversion.php

我使用这种方法将数据库行ID转换为Base-64。缩短这些数字后,可以在网址中使用这些数字。 [details]

答案 4 :(得分:1)

这个怎么样:

function shorten_int($id){
    $hex = base_convert(id, 10, 16);
    $base64 = base64_encode(pack('H*', $hex));
    //$base64 = str_replace("/", "_", $base64); // remove unsafe url chars
    //$base64 = str_replace("+", "-", $base64);
    //$base64 = rtrim($base64, '='); // Remove the padding "=="
    $replacePairs = array('/' => '_',
                          '+' => '-',
                          '=' => '');
    $base64 = strtr($base64, $replacePairs); // optimisation
    return $base64;
}

答案 5 :(得分:0)

这两个功能非常方便,这要感谢@malhal:

function shorten_int($id)
{
    $id=dechex($id);
    $id=strlen($id)%2===0?hex2bin($id):hex2bin('0'.$id);
    $id=base64_encode($id);
    $id=strtr($id, array('/'=>'_', '+'=>'-', '='=>''));
    return $id;
}

function unshorten_int($id)
{
    $id=strtr($id, array('-'=>'+', '_'=>'/'));
    $id=base64_decode($id);
    $id=bin2hex($id);
    return base_convert($id, 16, 10);
}

echo shorten_int(43121111)."\n";
echo unshorten_int(shorten_int(43121111))."\n";

答案 6 :(得分:0)

这是Nathans代码的一种变体,用于处理大于PHP_INT_MAX的大整数。

这使用应该在Windows服务器上内置的BC Maths Functions,但是需要在Unix服务器上将其作为可选扩展启用。此解决方案还需要几个自定义BC函数来处理我从post by Alix Axel复制来的地板和圆形函数。

function shorten($value, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-') {
    $base = strlen($alphabet);
    $result = '';
    while ($value) {
        $mod = bcmod($value, $base);
        $value = bcfloor(bcdiv($value, $base));
        $result = $alphabet[$mod] . $result;
    }
    return $result;
  }

function lengthen($value, $alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-') {
    $base= strlen($alphabet);
    $result = '';
    for($i = 0, $limit = strlen($value); $i < $limit; $i++) {
        $result = bcadd(bcmul($base, $result), strpos($alphabet, $value[$i]));
    }
    return $result;
}

function bcceil($number) {
    if (strpos($number, '.') !== false) {
        if (preg_match("~\.[0]+$~", $number)) return bcround($number, 0);
        if ($number[0] != '-') return bcadd($number, 1, 0);
        return bcsub($number, 0, 0);
    }
    return $number;
}

function bcfloor($number) {
    if (strpos($number, '.') !== false) {
        if (preg_match("~\.[0]+$~", $number)) return bcround($number, 0);
        if ($number[0] != '-') return bcadd($number, 0, 0);
        return bcsub($number, 1, 0);
    }
    return $number;
}

function bcround($number, $precision = 0) {
    if (strpos($number, '.') !== false) {
        if ($number[0] != '-') return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
        return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
    }
    return $number;
}

在Windows(32位)上运行PHP 5.6的示例

foreach ([0, 1, 9, 10, 115617, bcsub(PHP_INT_MAX, 1), PHP_INT_MAX, bcadd(PHP_INT_MAX, 1234567890)] as $value) {
    $short = shorten($value);
    $reversed = lengthen($short);
    print shorten($value) . " ($value)<br>";
    if ("$value" !== $reversed) {
        print 'ERROR REVERSING VALUE<br>';
    }
}

输出

0 (0)
1 (1)
9 (9)
a (10)
sex (115617)
1----_ (2147483646)
1----- (2147483647)
39Bwbh (3382051537)

如果ID是公开的,请避免在字符串中使用元音(例如,将115617缩短为性)。这将是应该提供安全字词的基础54版本。

$alphabet = '0123456789bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ_-';

答案 7 :(得分:-1)

您可以使用pack

$int = 1129717211140920362;

$byte = pack('J*', $int);    
echo base64_encode($byte); //= D62P0WqzFCo=

它将导致D62P0WqzFCo=,它是正确的,因为$int是一个int64并使用64位。 Base64为每个字符使用6位,因此它们需要~11个字符。

解码使用:

$base64 = 'D62P0WqzFCo=';

$byte = base64_decode($base64);
echo unpack('J*',  $byte)[1]; //= 1129717211140920362

它将返回1129717211140920362。 ;)

它基于Stackoverflow in Portuguese

的答案