如何创建URL缩短器?


667

我想创建一个URL缩短服务,您可以在其中将长URL写入输入字段,然后该服务将URL缩短为“ http://www.example.org/abcdef”。

除了“ abcdef”以外,还有其他任何包含六个字符的字符串a-z, A-Z and 0-9。这使得56〜570亿个可能的字符串。

我的方法:

我有一个包含三列的数据库表:

  1. id,整数,自动递增
  2. long,字符串,用户输入的长URL
  3. 简短的字符串,缩短的URL(或仅六个字符)

然后,我将长网址插入表中。然后,我将为“ id” 选择自动增量值,并为其构建一个哈希值。然后,此哈希应插入为“ short”。但是我应该建立什么样的哈希?像MD5这样的哈希算法创建的字符串太长。我认为我不使用这些算法。自建算法也将起作用。

我的点子:

对于“ http://www.google.de/”,我获得了自动增量id 239472。然后,我执行以下步骤:

short = '';
if divisible by 2, add "a"+the result to short
if divisible by 3, add "b"+the result to short
... until I have divisors for a-z and A-Z.

可以重复进行直到该数字不再可除。您认为这是个好方法吗?你有更好的主意吗?

由于对该主题的持续关注,我为GitHub发布了一种有效的解决方案,其中包含JavaScriptPHPPythonJava的实现。如果您愿意,请添加您的解决方案:)


5
@gudge这些函数的要点是它们具有反函数。这意味着您可以同时拥有encode()decode()功能。因此,步骤如下:(1)在数据库中保存URL(2)从数据库中获取该URL的唯一行ID(3)将整数ID转换为带有的短字符串encode(),例如273984,转换为f5a4(4 f4a4)在您的字符串中使用短字符串(例如)可共享的URL(5)收到短字符串的请求时(例如20a8),使用decode()(6)在数据库中查找给定ID的URL 将字符串解码为整数ID。为了转换,使用:github.com/delight-im/ShortURL
CAW

@Marco,将散列存储在数据库中有什么意义?
马克西姆六世。

3
@MaksimVi。如果您具有可逆函数,则没有任何函数。如果您具有单向哈希函数,那么将只有一个。
Caw

1
如果我们使用简单的CRC32算法来缩短URL,那会错吗?尽管不太可能发生冲突(CRC32输出通常为8个字符长,这给我们提供了超过3000万个可能性)如果生成的CRC32输出先前已被使用并在数据库中找到,我们可以使用随机数加长网址直到找到在我的数据库中唯一的CRC32输出。对于一个简单的解决方案,这将是多么的糟糕,不同或丑陋?
拉基布

Answers:


816

我将继续您的“将数字转换为字符串”方法。但是,您将意识到,如果您的ID为质数且大于52,则建议的算法将失败。

理论背景

您需要一个双射函数 f。这是必要的,以便为f(123)='abc'函数找到反函数g('abc')= 123。这表示:

  • 必须没有x1,x2(x1≠x2)会使f(x1)= f(x2)
  • 并且对于每个y,您都必须能够找到x,以便f(x)= y

如何将ID转换为缩短的URL

  1. 想一想我们要使用的字母。就您而言,那就是[a-zA-Z0-9]。它包含62个字母
  2. 以自动生成的唯一数字键id为例(例如,MySQL表的自动递增)。

    在此示例中,我将使用125 10(125以10为底)。

  3. 现在您必须将125 10转换为X 62(以62为基数)。

    125 10 = 2×62 1 + 1×62 0 =[2,1]

    这需要使用整数除法和取模。伪代码示例:

    digits = []
    
    while num > 0
      remainder = modulo(num, 62)
      digits.push(remainder)
      num = divide(num, 62)
    
    digits = digits.reverse
    

    现在将索引2和1映射到您的字母。这就是您的映射(例如带有数组)的样子:

    0  → a
    1  → b
    ...
    25 → z
    ...
    52 → 0
    61 → 9
    

    使用2→c和1→b,您将收到cb 62作为缩短的URL。

    http://shor.ty/cb
    

如何将缩短的URL解析为初始ID

反之则更容易。您只需要对字母进行反向查找。

  1. e9a 62将解析为“字母表中的第4、61 和0个字母”。

    e9a 62 = [4,61,0]= 4×62 2 + 61×62 1 + 0×62 0 = 19158 10

  2. 现在使用查找您的数据库记录,WHERE id = 19158并进行重定向。

示例实现(由评论者提供)


18
不要忘了清理恶意JavaScript代码的URL!请记住,JavaScript可以在URL中进行base64编码,因此仅搜索'javascript'还不够好。j–
Bjorn

3
函数必须是双射的(内射和外射)才能具有逆函数。
Gumbo 2010年

57
值得深思的是,在URL中添加两个字符的校验和可能很有用。这样可以防止系统中所有URL的直接迭代。简单的东西,例如f(checksum(id)%(62 ^ 2))+ f(id)= url_id
koblas 2010年

6
至于清理URL,您将要面对的问题之一是垃圾邮件发送者使用您的服务掩盖其URL以避免垃圾邮件过滤器。您需要将服务限制在已知好的参与者上,或者将垃圾邮件过滤应用于长网址。否则,您将被垃圾邮件发送者滥用。
爱德华·福克

74
Base62可能是一个错误的选择,因为它有可能生成f *个单词(例如,3792586=='F_ck'用u代替_)。我会排除一些字符,例如u / U,以将其最小化。
Paulo Scardine

56

为什么要使用哈希?

您只需将自动增量值转换为字母数字值即可。通过使用一些基本转换,您可以轻松地做到这一点。假设您的字符空间(AZ,az,0-9等)有40个字符,将id转换为以40为底的数字,然后将这些字符用作数字。


13
从AZ,az和0-9 = 62个字符而不是40个字符这一事实来看,您是对的。
Evan Teran

谢谢!那我应该使用62底的字母吗? en.wikipedia.org/wiki/Base_62 但是如何将ID转换为以62为底的数字?
caw


2
关于“为什么要使用哈希?”,基于自动增量的基本转换将创建顺序的URL,因此您需要对能够“浏览”其他人的缩短URL的人感到满意,对?
Andrew Coleson 09年

2
只要有足够的资源和时间,您就可以“浏览”任何URL缩短服务的所有URL。
shoosh

51
public class UrlShortener {
    private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int    BASE     = ALPHABET.length();

    public static String encode(int num) {
        StringBuilder sb = new StringBuilder();
        while ( num > 0 ) {
            sb.append( ALPHABET.charAt( num % BASE ) );
            num /= BASE;
        }
        return sb.reverse().toString();   
    }

    public static int decode(String str) {
        int num = 0;
        for ( int i = 0; i < str.length(); i++ )
            num = num * BASE + ALPHABET.indexOf(str.charAt(i));
        return num;
    }   
}

我真的很喜欢这个主意,我唯一的问题是我不断使解码函数中的num变量超出范围(即使很长时间),您是否知道如何使它起作用?还是仅仅是理论上的?
user1322801

@ user1322801:大概您正在尝试解码远大于编码功能实际可以处理的内容。如果将所有“ int”都转换为BigInteger,则可以从中得到更多收益,但是除非您拥有> 922337203685477575807索引,否则长的数据可能就足够了。
biggusjimmus

2
我可以知道换向的重要性是什么?即sb.reverse()。toString();
dotNet解码器

那62 ^ 62 = 1.7万亿?
诺亚·托尼

33

不能回答您的问题,但是我不会使用区分大小写的简短URL。它们很难记住,通常不可读(许多字体呈现1和l,0和O以及其他字符非常相似,以至于几乎无法分辨出它们之间的差异)并且容易产生彻头彻尾的错误。尝试仅使用小写或大写。

另外,请尝试使用以预定义形式混合数字和字符的格式。有研究表明,人们会比其他人更好地记住一种形式(想想电话号码,其中的电话以特定形式分组)。试试像num-char-char-num-char-char这样的东西。我知道这会降低组合,特别是如果您没有大写和小写字母时,它会更有用,因此很有用。


2
谢谢,很好的主意。我还没想过 显然,是否有意义取决于使用的类型。
caw

19
如果人们严格复制并粘贴短网址,这将不是问题。
爱德华·福克

2
简短网址的目的不是令人难忘或难以说出来。仅单击或复制/粘贴。
Hugo Nogueira

是的,我认为短URL仅用于人们列出或通过电子邮件发送,因此它很短,不会像某些URL那样占用200个字符,因此大小写不是问题
nonopolarity

29

我的方法:获取数据库ID,然后对Base36进行编码。我不会同时使用大写和小写字母,因为这会使通过电话传输这些URL成为噩梦,但是您当然可以轻松地将该功能扩展为以62为基础的en / decoder。


谢谢,你是对的。无论您有2,176,782,336的可能性还是56,800,235,584的可能性都是一样的:两者都足够。所以我将使用base 36编码。
caw

可能显而易见,但这是Wikipedia中引用的一些PHP代码,用于在php中进行base64编码tonymarston.net/php-mysql/converter.html
Ryan White

8

这是我的PHP 5类。

<?php
class Bijective
{
    public $dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    public function __construct()
    {
        $this->dictionary = str_split($this->dictionary);
    }

    public function encode($i)
    {
        if ($i == 0)
        return $this->dictionary[0];

        $result = '';
        $base = count($this->dictionary);

        while ($i > 0)
        {
            $result[] = $this->dictionary[($i % $base)];
            $i = floor($i / $base);
        }

        $result = array_reverse($result);

        return join("", $result);
    }

    public function decode($input)
    {
        $i = 0;
        $base = count($this->dictionary);

        $input = str_split($input);

        foreach($input as $char)
        {
            $pos = array_search($char, $this->dictionary);

            $i = $i * $base + $pos;
        }

        return $i;
    }
}

6

Node.js和MongoDB解决方案

由于我们知道MongoDB用于创建具有12个字节的新ObjectId的格式。

  • 一个4字节的值,表示自Unix时代以来的秒数,
  • 一个3字节的机器标识符,
  • 一个2字节的进程ID
  • 一个3字节的计数器(在您的计算机中),以随机值开头。

示例(我选择一个随机序列) a1b2c3d4e5f6g7h8i9j1k2l3

  • a1b2c3d4代表自Unix时代以来的秒数,
  • 4e5f6g7代表机器标识符,
  • h8i9表示进程ID
  • j1k2l3代表从随机值开始的计数器。

由于如果我们将数据存储在同一台计算机上,计数器将是唯一的,因此毫无疑问它可以重复获得。

因此,短网址将是计数器,而这是一个假定您的服务器正常运行的代码段。

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Create a schema
const shortUrl = new Schema({
    long_url: { type: String, required: true },
    short_url: { type: String, required: true, unique: true },
  });
const ShortUrl = mongoose.model('ShortUrl', shortUrl);

// The user can request to get a short URL by providing a long URL using a form

app.post('/shorten', function(req ,res){
    // Create a new shortUrl */
    // The submit form has an input with longURL as its name attribute.
    const longUrl = req.body["longURL"];
    const newUrl = ShortUrl({
        long_url : longUrl,
        short_url : "",
    });
    const shortUrl = newUrl._id.toString().slice(-6);
    newUrl.short_url = shortUrl;
    console.log(newUrl);
    newUrl.save(function(err){
        console.log("the new URL is added");
    })
});

1
RDBMS会比无SQL /键值存储更好吗?
kjs3

@ kjs3是的,您是对的,因为与其他表没有关系,所以不需要RDBMS,并且键值存储将更快。
Firas Omrane

4

C#版本:

public class UrlShortener 
{
    private static String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static int    BASE     = 62;

    public static String encode(int num)
    {
        StringBuilder sb = new StringBuilder();

        while ( num > 0 )
        {
            sb.Append( ALPHABET[( num % BASE )] );
            num /= BASE;
        }

        StringBuilder builder = new StringBuilder();
        for (int i = sb.Length - 1; i >= 0; i--)
        {
            builder.Append(sb[i]);
        }
        return builder.ToString(); 
    }

    public static int decode(String str)
    {
        int num = 0;

        for ( int i = 0, len = str.Length; i < len; i++ )
        {
            num = num * BASE + ALPHABET.IndexOf( str[(i)] ); 
        }

        return num;
    }   
}


4

我不断增加数据库中每个域的整数序列,并使用Hashids将整数编码为URL路径。

static hashids = Hashids(salt = "my app rocks", minSize = 6)

我运行了一个脚本来查看直到耗尽字符长度需要多长时间。对于六个字符,它可以164,916,224链接,然后最多七个字符。位使用七个字符。五个字符以下对我来说很奇怪。

Hashids可以将URL路径解码回整数,但是更简单的解决方案是将整个短链接sho.rt/ka8ds3用作主键。

这是完整的概念:

function addDomain(domain) {
    table("domains").insert("domain", domain, "seq", 0)
}

function addURL(domain, longURL) {
    seq = table("domains").where("domain = ?", domain).increment("seq")
    shortURL = domain + "/" + hashids.encode(seq)
    table("links").insert("short", shortURL, "long", longURL)
    return shortURL
}

// GET /:hashcode
function handleRequest(req, res) {
    shortURL = req.host + "/" + req.param("hashcode")
    longURL = table("links").where("short = ?", shortURL).get("long")
    res.redirect(301, longURL)
}


3
// simple approach

$original_id = 56789;

$shortened_id = base_convert($original_id, 10, 36);

$un_shortened_id = base_convert($shortened_id, 36, 10);

2
alphabet = map(chr, range(97,123)+range(65,91)) + map(str,range(0,10))

def lookup(k, a=alphabet):
    if type(k) == int:
        return a[k]
    elif type(k) == str:
        return a.index(k)


def encode(i, a=alphabet):
    '''Takes an integer and returns it in the given base with mappings for upper/lower case letters and numbers 0-9.'''
    try:
        i = int(i)
    except Exception:
        raise TypeError("Input must be an integer.")

    def incode(i=i, p=1, a=a):
        # Here to protect p.                                                                                                                                                                                                                
        if i <= 61:
            return lookup(i)

        else:
            pval = pow(62,p)
            nval = i/pval
            remainder = i % pval
            if nval <= 61:
                return lookup(nval) + incode(i % pval)
            else:
                return incode(i, p+1)

    return incode()



def decode(s, a=alphabet):
    '''Takes a base 62 string in our alphabet and returns it in base10.'''
    try:
        s = str(s)
    except Exception:
        raise TypeError("Input must be a string.")

    return sum([lookup(i) * pow(62,p) for p,i in enumerate(list(reversed(s)))])a

这是我需要的版本。


1

为什么不将您的ID转换为字符串?您只需要一个将0到61之间的数字映射到单个字母(大写/小写)或数字的函数。然后应用此代码创建4个字母的代码,您将覆盖1470万个URL。


+1为简单化思维。真的就是这么简单。我刚刚发布了一个答案,正是这个答案。我有一些查询数据库的生产代码,以确保没有重复的字符串,并且所有内容都是唯一的。
安德鲁·里斯

1

这是PHP的不错的URL编码功能...

// From http://snipplr.com/view/22246/base62-encode--decode/
private function base_encode($val, $base=62, $chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
    $str = '';
    do {
        $i = fmod($val, $base);
        $str = $chars[$i] . $str;
        $val = ($val - $i) / $base;
    } while($val > 0);
    return $str;
}

1

不知道是否有人会发现此功能有用-它更像是一种“ hack n slash”方法,但很简单,并且如果您只想要特定的字符,效果很好。

$dictionary = "abcdfghjklmnpqrstvwxyz23456789";
$dictionary = str_split($dictionary);

// Encode
$str_id = '';
$base = count($dictionary);

while($id > 0) {
    $rem = $id % $base;
    $id = ($id - $rem) / $base;
    $str_id .= $dictionary[$rem];
}


// Decode
$id_ar = str_split($str_id);
$id = 0;

for($i = count($id_ar); $i > 0; $i--) {
    $id += array_search($id_ar[$i-1], $dictionary) * pow($base, $i - 1);
} 

1

您是否故意忽略了O,0和i?

我刚刚基于Ryan的解决方案创建了一个PHP类。

<?php

    $shorty = new App_Shorty();

    echo 'ID: ' . 1000;
    echo '<br/> Short link: ' . $shorty->encode(1000);
    echo '<br/> Decoded Short Link: ' . $shorty->decode($shorty->encode(1000));


    /**
     * A nice shorting class based on Ryan Charmley's suggestion see the link on Stack Overflow below.
     * @author Svetoslav Marinov (Slavi) | http://WebWeb.ca
     * @see http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener/10386945#10386945
     */
    class App_Shorty {
        /**
         * Explicitly omitted: i, o, 1, 0 because they are confusing. Also use only lowercase ... as
         * dictating this over the phone might be tough.
         * @var string
         */
        private $dictionary = "abcdfghjklmnpqrstvwxyz23456789";
        private $dictionary_array = array();

        public function __construct() {
            $this->dictionary_array = str_split($this->dictionary);
        }

        /**
         * Gets ID and converts it into a string.
         * @param int $id
         */
        public function encode($id) {
            $str_id = '';
            $base = count($this->dictionary_array);

            while ($id > 0) {
                $rem = $id % $base;
                $id = ($id - $rem) / $base;
                $str_id .= $this->dictionary_array[$rem];
            }

            return $str_id;
        }

        /**
         * Converts /abc into an integer ID
         * @param string
         * @return int $id
         */
        public function decode($str_id) {
            $id = 0;
            $id_ar = str_split($str_id);
            $base = count($this->dictionary_array);

            for ($i = count($id_ar); $i > 0; $i--) {
                $id += array_search($id_ar[$i - 1], $this->dictionary_array) * pow($base, $i - 1);
            }
            return $id;
        }
    }
?>

是。您在类声明的下面看到了注释吗?
Svetoslav Marinov 2015年


0

这是我用的:

# Generate a [0-9a-zA-Z] string
ALPHABET = map(str,range(0, 10)) + map(chr, range(97, 123) + range(65, 91))

def encode_id(id_number, alphabet=ALPHABET):
    """Convert an integer to a string."""
    if id_number == 0:
        return alphabet[0]

    alphabet_len = len(alphabet) # Cache

    result = ''
    while id_number > 0:
        id_number, mod = divmod(id_number, alphabet_len)
        result = alphabet[mod] + result

    return result

def decode_id(id_string, alphabet=ALPHABET):
    """Convert a string to an integer."""
    alphabet_len = len(alphabet) # Cache
    return sum([alphabet.index(char) * pow(alphabet_len, power) for power, char in enumerate(reversed(id_string))])

它非常快,可以使用长整数。


0

对于类似的项目,要获取新的密钥,我围绕一个随机字符串生成器创建了一个包装器函数,该生成将调用该生成器,直到获得哈希表中尚未使用的字符串。一旦您的名称空间开始变满,此方法就会变慢,但是正如您已经说过的,即使只有6个字符,您也可以使用很多命名空间。


从长远来看,这种方法对您有用吗?
克里斯(Chris)

老实说,我不知道我在指的是哪个项目:-P
Joel Berger

0

我有一个问题的变体,因为我存储了许多不同作者的网页,并且需要防止通过猜测发现网页。因此,我的短网址在页码的Base-62字符串中添加了几个额外的数字。这些多余的数字是从页面记录本身的信息中生成的,它们确保3844个URL中只有1个有效(假设2个数字为Base-62)。您可以在http://mgscan.com/MBWL上看到概述说明。


0

很好的答案,我创建了bjf的Golang实现:

package bjf

import (
    "math"
    "strings"
    "strconv"
)

const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

func Encode(num string) string {
    n, _ := strconv.ParseUint(num, 10, 64)
    t := make([]byte, 0)

    /* Special case */
    if n == 0 {
        return string(alphabet[0])
    }

    /* Map */
    for n > 0 {
        r := n % uint64(len(alphabet))
        t = append(t, alphabet[r])
        n = n / uint64(len(alphabet))
    }

    /* Reverse */
    for i, j := 0, len(t) - 1; i < j; i, j = i + 1, j - 1 {
        t[i], t[j] = t[j], t[i]
    }

    return string(t)
}

func Decode(token string) int {
    r := int(0)
    p := float64(len(token)) - 1

    for i := 0; i < len(token); i++ {
        r += strings.Index(alphabet, string(token[i])) * int(math.Pow(float64(len(alphabet)), p))
        p--
    }

    return r
}

托管在github:https : //github.com/xor-gate/go-bjf


0
/**
 * <p>
 *     Integer to character and vice-versa
 * </p>
 *  
 */
public class TinyUrl {

    private final String characterMap = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private final int charBase = characterMap.length();

    public String covertToCharacter(int num){
        StringBuilder sb = new StringBuilder();

        while (num > 0){
            sb.append(characterMap.charAt(num % charBase));
            num /= charBase;
        }

        return sb.reverse().toString();
    }

    public int covertToInteger(String str){
        int num = 0;
        for(int i = 0 ; i< str.length(); i++)
            num += characterMap.indexOf(str.charAt(i)) * Math.pow(charBase , (str.length() - (i + 1)));

        return num;
    }
}

class TinyUrlTest{

    public static void main(String[] args) {
        TinyUrl tinyUrl = new TinyUrl();
        int num = 122312215;
        String url = tinyUrl.covertToCharacter(num);
        System.out.println("Tiny url:  " + url);
        System.out.println("Id: " + tinyUrl.covertToInteger(url));
    }
}

0

在Scala中实施:

class Encoder(alphabet: String) extends (Long => String) {

  val Base = alphabet.size

  override def apply(number: Long) = {
    def encode(current: Long): List[Int] = {
      if (current == 0) Nil
      else (current % Base).toInt :: encode(current / Base)
    }
    encode(number).reverse
      .map(current => alphabet.charAt(current)).mkString
  }
}

class Decoder(alphabet: String) extends (String => Long) {

  val Base = alphabet.size

  override def apply(string: String) = {
    def decode(current: Long, encodedPart: String): Long = {
      if (encodedPart.size == 0) current
      else decode(current * Base + alphabet.indexOf(encodedPart.head),encodedPart.tail)
    }
    decode(0,string)
  }
}

使用Scala测试的测试示例:

import org.scalatest.{FlatSpec, Matchers}

class DecoderAndEncoderTest extends FlatSpec with Matchers {

  val Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

  "A number with base 10" should "be correctly encoded into base 62 string" in {
    val encoder = new Encoder(Alphabet)
    encoder(127) should be ("cd")
    encoder(543513414) should be ("KWGPy")
  }

  "A base 62 string" should "be correctly decoded into a number with base 10" in {
    val decoder = new Decoder(Alphabet)
    decoder("cd") should be (127)
    decoder("KWGPy") should be (543513414)
  }

}

0

基于Xeoncross类的函数

function shortly($input){
$dictionary = ['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'];
if($input===0)
    return $dictionary[0];
$base = count($dictionary);
if(is_numeric($input)){
    $result = [];
    while($input > 0){
        $result[] = $dictionary[($input % $base)];
        $input = floor($input / $base);
    }
    return join("", array_reverse($result));
}
$i = 0;
$input = str_split($input);
foreach($input as $char){
    $pos = array_search($char, $dictionary);
    $i = $i * $base + $pos;
}
return $i;
}

0

这是一个可能是bit.ly的Node.js实现。生成一个高度随机的七个字符的字符串。

它使用Node.js加密算法生成高度随机的25个字符集,而不是随机选择七个字符。

var crypto = require("crypto");
exports.shortURL = new function () {
    this.getShortURL = function () {
        var sURL = '',
            _rand = crypto.randomBytes(25).toString('hex'),
            _base = _rand.length;
        for (var i = 0; i < 7; i++)
            sURL += _rand.charAt(Math.floor(Math.random() * _rand.length));
        return sURL;
    };
}

所说“ bit.ly”是什么意思。?
彼得·莫滕森

0

我的Python 3版本

base_list = list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
base = len(base_list)

def encode(num: int):
    result = []
    if num == 0:
        result.append(base_list[0])

    while num > 0:
        result.append(base_list[num % base])
        num //= base

    print("".join(reversed(result)))

def decode(code: str):
    num = 0
    code_list = list(code)
    for index, code in enumerate(reversed(code_list)):
        num += base_list.index(code) * base ** index
    print(num)

if __name__ == '__main__':
    encode(341413134141)
    decode("60FoItT")

0

有关优质的Node.js / JavaScript解决方案,请参阅id-shortener模块,该模块经过了全面测试,已经在生产中使用了几个月。

它提供了一个有效的id / URL缩短器,由默认为Redis的可插入存储支持,您甚至可以自定义短id字符集以及缩短是否是幂等的。这是一个重要的区别,并非所有URL缩短器都考虑在内。

关于这里的其他答案,此模块实现了上面的Marcel Jackwerth公认的出色答案。

以下Redis Lua 片段提供了解决方案的核心:

local sequence = redis.call('incr', KEYS[1])

local chars = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz'
local remaining = sequence
local slug = ''

while (remaining > 0) do
  local d = (remaining % 60)
  local character = string.sub(chars, d + 1, d + 1)

  slug = character .. slug
  remaining = (remaining - d) / 60
end

redis.call('hset', KEYS[2], slug, ARGV[1])

return slug

0

为什么不只生成一个随机字符串并将其附加到基本URL?这是用C#进行的非常简化的版本。

static string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
static string baseUrl = "https://google.com/";

private static string RandomString(int length)
{
    char[] s = new char[length];
    Random rnd = new Random();
    for (int x = 0; x < length; x++)
    {
        s[x] = chars[rnd.Next(chars.Length)];
    }
    Thread.Sleep(10);

    return new String(s);
}

然后只需将随机字符串追加到baseURL:

string tinyURL = baseUrl + RandomString(5);

请记住,这是一个非常简化的版本,并且RandomString方法可能会创建重复的字符串。在生产中,您需要考虑重复的字符串,以确保您始终拥有唯一的URL。我有一些代码通过查询数据库共享表来考虑重复的字符串,如果有人感兴趣,我可以共享该数据库表。


0

这是我最初的想法,可以做更多的思考,或者可以进行一些模拟以查看它是否运行良好或需要任何改进:

我的答案是记住数据库中的长URL,并使用ID 09999999999999999(或需要很大的数字)。

但是ID 0 9999999999999999可能是一个问题,因为

  1. 如果我们使用十六进制,甚至使用base62或base64,它可以更短。(BASE64就像YouTube上使用A- Z a- z 0- 9 _-
  2. 如果从增加09999999999999999均匀,然后黑客就可以访问它们的顺序,知道什么网址,人们互相发送,因此它可以是一个隐私问题

我们做得到:

  1. 将一台服务器分配0999一台服务器A,因此现在服务器A具有1000个此类ID。因此,如果有20或200台服务器不断需要新的ID,则不必继续要求每个新ID,而只需一次询问1000个ID
  2. 例如,对于ID 1,反转位。所以 000...00000001成为10000...000,使得当转化为base64,这将是不均匀地每次增加的ID。
  3. 使用XOR翻转最终ID的位。例如,将XOR与0xD5AA96...2373(例如一个秘密密钥)进行比较,并且某些位将被翻转。(只要密钥的1位打开,它将翻转ID的位)。这将使ID更加难以猜测并且显得更加随机

按照此方案,分配ID的单个服务器可以形成ID,而请求分配ID的20或200个服务器也可以形成ID。分配服务器必须使用锁/信号量,以防止两个发出请求的服务器获得相同的批处理(或者如果它一次接受一个连接,则已经解决了问题)。因此,我们不想等待等待分配的行(队列)太长。这就是为什么一次分配1000或10000可以解决此问题的原因。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.