如何基于id生成不可思议的“小网址”?

时间:2010-08-06 21:36:06

标签: php url-shortener tinyurl base36

我有兴趣创建像链接一样的小网址。我的想法是只为每个发布的长网址存储一个递增的标识符,然后将此id转换为它的基本36变体,如下面的PHP中所示:

$tinyurl = base_convert($id, 10, 36)

这里的问题是结果是可猜测的,虽然必须很难猜出下一个网址是什么,但仍然很短(很小)。例如。如果我的最后一个小小的是a1,那么下一个将是a2。这对我来说是件坏事。

那么,我如何确保产生的小网址不是可猜测但仍然很短?

10 个答案:

答案 0 :(得分:9)

您要求的是在减少信息(数据库中的索引的URL)和人为增加信息(在序列中创建漏洞)之间取得平衡。

你必须决定两者对你有多重要。另一个问题是,您是否只是不希望连续URL是可猜测的,或者让它们足够随机以使猜测任何有效URL变得困难。

基本上,您希望从N个有效ID中声明n。选择N较小以使URL更短,并使n更小以生成难以猜测的URL。将n和N设置得更大,以便在拍摄较短的URL时生成更多的URL。

要分配ID,您可以使用任何类型的随机生成器或散列函数,并将其限制为目标范围N.如果检测到碰撞,请选择下一个随机值。如果已达到n个唯一ID的计数,则必须增加ID集的范围(n和N)。

答案 1 :(得分:5)

我只想crc32 url

$url = 'http://www.google.com';
$tinyurl = hash('crc32', $url ); // db85f073

缺点:常量8个字符的长标识符

答案 2 :(得分:4)

这真的很便宜,但是如果用户不知道它正在发生那么它不是可猜测的,而是用2或3个随机数字/字母作为前缀和后缀实际id。

如果我看到9d2a1me3,我不会猜测dm2a2dq2是系​​列中的下一个。

答案 3 :(得分:2)

尝试使用某些值对$ id进行Xor'ing,例如$id ^ 46418 - 并且要转换回原来的ID,您只需再次执行相同的Xor即$mungedId ^ 46418。将它与你的base_convert一起堆叠,也许在结果字符串中交换一些字符,猜测一个URL就变得非常棘手了。

答案 4 :(得分:2)

另一种方法是设置URL的最大字符数(假设它是n)。然后,您可以选择介于1和n!之间的随机数,这将是您的排列数。

在新的URL上,您将增加id并使用置换编号来关联将使用的实际ID。最后,您将基于32(或其他)编码您的URL。这将是完全随机的,完全可逆的。

答案 5 :(得分:1)

如果您想要一个内射函数,可以使用任何形式的加密。例如:

<?php
$key = "my secret";
$enc = mcrypt_ecb (MCRYPT_3DES, $key, "42", MCRYPT_ENCRYPT);
$f = unpack("H*", $enc);
$value = reset($f);
var_dump($value); //string(16) "1399e6a37a6e9870"

要反转:

$rf = pack("H*", $value);
$dec = rtrim(mcrypt_ecb (MCRYPT_3DES, $key, $rf, MCRYPT_DECRYPT), "\x00");
var_dump($dec); //string(2) "42"

这不会给你一个基数为32的数字;它将为您提供加密数据,每个字节转换为基数16(即转换是全局的)。如果你真的需要,你可以通过任何支持大整数的库将其简单地转换为基数10,然后转换为基数32。

答案 6 :(得分:0)

您可以预先预先定义4个字符的代码(所有可能的组合),然后随机化该列表并将其以随机顺序存储在数据表中。当您想要一个新值时,只需抓住顶部的第一个并从列表中删除它。它速度快,无需动态计算,并保证最终用户的伪随机性。

答案 7 :(得分:0)

Hashids是一个开源库,可以从一个或多个数字生成短,唯一,非连续,类似YouTube的ID 。您可以将其视为混淆数字的算法

它将像347这样的数字转换为像“yr8”这样的字符串,或者将像[27,986]这样的数组转换为“3kTMd”。您还可以解码这些ID。这可以将多个参数捆绑到一个参数中,或者简单地将它们用作短UID。

您不希望 向您的用户公开您的数据库 ID 时使用它。

它允许自定义字母和盐,因此ID仅对您有用。

增量输入被破坏以保持不可思议。

没有碰撞,因为该方法基于整数到十六进制的转换。

编写的目的是将创建的ID放在可见的位置,例如URL。因此,该算法避免产生最常见的英语诅咒词。

代码示例

$hashids = new Hashids();
$id = $hashids->encode(1, 2, 3); // o2fXhV
$numbers = $hashids->decode($id); // [1, 2, 3]

答案 8 :(得分:0)

您可以使用这种方法来生成可能不是随机的但看起来难以预测的字符串。

将此示例考虑为62个字符并生成长度为LEN = 6的URL字符串

  1. 创建一个字符串映射,将数字作为其索引映射到字符时说“ abc ... zABC ... Z012 ... 9” (此处)为62个字符。您可以将它们混洗以使该序列随机出现。

  2. 获取整数N,以使2 ^ N小于62 ^ LEN。对于这种情况,N = 35。

  3. 现在从1开始创建一个计数器。要生成新的URL,请将此计数器转换为二进制字符串,彼此交换一些位(应该对将要生成的所有二进制字符串进行相同的交换,可以简单地反转二进制字符串)。

  4. 将该二进制数字转换回整数。并将该整数转换为以62为基数的数字,将所有其余部分映射到第一步生成的字符图中的字符。

这是Java中的简单实现。我提到this是为了轻松填充0:

a

看看生成的URL。这段代码可以生成2 ^ 35个字符串,没有任何重复,一旦计数器启动,它就会显得非常随机。您也可以在字符图中随机排列字符。

private static final AtomicLong counter = new AtomicLong(0);
private static final String MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; // 62 characters

public static String generateNextURL(){

    long num = counter.incrementAndGet();
    String binary = Long.toBinaryString((1L<<35)|num).substring(1); // to binary with padding 0s
    binary = new StringBuilder(binary).reverse().toString();           // swapping ith with 35-1-ith character
    num =  Long.parseLong(binary,2);                             // back to integer

    StringBuilder url= new StringBuilder();
    for(int i=0;i<6;i++){
        long SZ = 62;
        url.append(MAP.charAt((int) (num%SZ)));
        num=num/SZ;
    }

    String newURL = url.reverse().toString();
    System.out.println(newURL);
    return newURL;
}

答案 9 :(得分:-1)

我最终创建了标识符的md5总和,使用它的前4个字母数字,如果这是重复,只需增加长度,直到不再重复。

function idToTinyurl($id) {
    $md5 = md5($id);
    for ($i = 4; $i < strlen($md5); $i++) {
        $possibleTinyurl = substr($md5, 0, $i);
        $res = mysql_query("SELECT id FROM tabke WHERE tinyurl='".$possibleTinyurl."' LIMIT 1");
        if (mysql_num_rows($res) == 0) return $possibleTinyurl;
    }
    return $md5;
}

接受了relet的答案,因为它引导我采用这种策略。