Krótki skrót PHP, taki jak strony skracające adresy URL

83

Szukam funkcji PHP, która tworzy krótki hash z ciągu lub pliku, podobnie do tych witryn skracających adresy URL, takich jak tinyurl.com

Skrót nie powinien być dłuższy niż 8 znaków.

Zacisk
źródło
2
Wiem, że to stare pytanie, ale sprawdź: hashids.org . Działa z większością języków programowania
Greg
Sprawdź bibliotekę ShortCode . Robi dokładnie to, czego chcesz. Na podstawie konwersji podstawowej.
Anis
Inny niż przy użyciu Adler-32 lub CRC32, nie można skrócić nowoczesny (kolizji) odporne mieszań , że wiele (czyli aż do 8 znaków). Nie z SHA-2, nie z SHA-1, a nawet nie z MD5. Dzięki Alphabet::convert($hash, Alphabet::HEX, Alphabet::ALPHANUMERIC), możesz uzyskać MD5 do 22 (z 32) znaków. Zamiast tego chcesz zakodować identyfikatory całkowite plików (np. Z bazy danych) za pomocą (new Id())->encode($id).
krakanie

Odpowiedzi:

47

Usługi skracania adresów URL używają raczej automatycznie zwiększanej wartości całkowitej (takiej jak dodatkowy identyfikator bazy danych) i kodują ją za pomocą kodowania Base64 lub innego, aby uzyskać więcej informacji na znak (64 zamiast tylko 10 takich jak cyfry).

Gumbo
źródło
1
Co to oznacza (więcej informacji na postać) jest po prostu ciekawe!
ravisoni
2
@ravisoni Jeśli używasz cyfr dziesiętnych 0- 9do reprezentowania liczby, masz 10 możliwych wartości na zakodowany znak (ld (10) ≈ 3,32 bitów / znak). Jeśli jednak przedstawisz tę samą liczbę za pomocą znaków Base64, masz 64 możliwych wartości na zakodowany znak (ld (64) = 6 bitów / znak). Tak więc w przypadku Base64 jest więcej informacji przechowywanych w każdym zakodowanym znaku, tj. 6 bitów informacji zamiast 3,32 bitów.
Gumbo
3
Jeśli używasz base64, nic nie stoi na przeszkodzie, aby skrypt powiedział for ($ i = 0; $ i <999999; $ i ++) {$ pageContent = fread (fopen (' yoururl.com/'.base64_encode($i) );} a teraz mam dostęp do każdego adresu URL w Twojej bazie danych
161

TinyURL nic nie haszuje, używa liczb całkowitych o podstawie 36 (lub nawet podstawy 62, używając małych i wielkich liter), aby wskazać, który rekord odwiedzić.

Podstawa 36 na liczbę całkowitą:

intval($str, 36);

Liczba całkowita do podstawy 36:

base_convert($val, 10, 36);

Więc zamiast przekierowywać do trasy, /url/1234jaką się staje /url/ax. Daje to dużo więcej zastosowań niż funkcja skrótu, ponieważ nie będzie kolizji. Dzięki temu możesz łatwo sprawdzić, czy adres URL istnieje i zwrócić właściwy, istniejący identyfikator w bazie 36 bez wiedzy użytkownika, że ​​jest już w bazie danych.

Nie haszuj, używaj innych baz do tego typu rzeczy. (Jest szybszy i może być odporny na kolizje).

Robert K.
źródło
Cześć @RobertK, jak wyglądałby PHP w przypadku konwersji 6-cyfrowych ciągów zawierających zarówno cyfry, jak i litery?
tim Peterson
@timpeterson, po prostu wywołaj intval i przekaż podaną bazę (zobacz mój pierwszy blok kodu).
Robert K
@RobertK, ale intval()zamienia wszystko w liczbę. Myślę, że być może jestem zdezorientowany, w jaki sposób intval()łączy się z innymi krokami wymaganymi do wykonania przekierowania, takimi jak rola bazy danych.
tim Peterson
@timpeterson, ponieważ ciąg reprezentuje identyfikator rekordu bazy danych. Więc wybierasz rekord na podstawie przekazanego identyfikatora.
Robert K,
@RobertK, jedyny problem, z którym widzę, intval()to to, czy masz w nim $strukośniki (/) lub myślniki (-). Zdałem sobie sprawę, że on/stuff, on-stuffi onwszyscy wrócili numer 887. Czy masz na myśli rozwiązanie umożliwiające pracę z adresami URL zawierającymi ukośniki i myślniki?
tim Peterson
83

Napisałem małą bibliotekę do generowania zaciemnionych skrótów z liczb całkowitych.

http://web.archive.org/web/20130727034425/http://blog.kevburnsjr.com/php-unique-hash

$ids = range(1,10);
foreach($ids as $id) {
  echo PseudoCrypt::unhash($id) . "\n";
}
m8z2p
8hy5e
uqx83
gzwas
38vdh
phug6
bqtiv
xzslk
k8ro9
6hqqy

14.07.2015: Dodanie rzeczywistego kodu poniżej, ponieważ znalezienie go stało się trudne:

<?php
/**
 * PseudoCrypt by KevBurns (http://blog.kevburnsjr.com/php-unique-hash)
 * Reference/source: http://stackoverflow.com/a/1464155/933782
 * 
 * I want a short alphanumeric hash that’s unique and who’s sequence is difficult to deduce. 
 * I could run it out to md5 and trim the first n chars but that’s not going to be very unique. 
 * Storing a truncated checksum in a unique field means that the frequency of collisions will increase 
 * geometrically as the number of unique keys for a base 62 encoded integer approaches 62^n. 
 * I’d rather do it right than code myself a timebomb. So I came up with this.
 * 
 * Sample Code:
 * 
 * echo "<pre>";
 * foreach(range(1, 10) as $n) {
 *     echo $n." - ";
 *     $hash = PseudoCrypt::hash($n, 6);
 *     echo $hash." - ";
 *     echo PseudoCrypt::unhash($hash)."<br/>";
 * }
 * 
 * Sample Results:
 * 1 - cJinsP - 1
 * 2 - EdRbko - 2
 * 3 - qxAPdD - 3
 * 4 - TGtDVc - 4
 * 5 - 5ac1O1 - 5
 * 6 - huKpGQ - 6
 * 7 - KE3d8p - 7
 * 8 - wXmR1E - 8
 * 9 - YrVEtd - 9
 * 10 - BBE2m2 - 10
 */

class PseudoCrypt {

    /* Key: Next prime greater than 62 ^ n / 1.618033988749894848 */
    /* Value: modular multiplicative inverse */
    private static $golden_primes = array(
        '1'                  => '1',
        '41'                 => '59',
        '2377'               => '1677',
        '147299'             => '187507',
        '9132313'            => '5952585',
        '566201239'          => '643566407',
        '35104476161'        => '22071637057',
        '2176477521929'      => '294289236153',
        '134941606358731'    => '88879354792675',
        '8366379594239857'   => '7275288500431249',
        '518715534842869223' => '280042546585394647'
    );

    /* Ascii :                    0  9,         A  Z,         a  z     */
    /* $chars = array_merge(range(48,57), range(65,90), range(97,122)) */
    private static $chars62 = array(
        0=>48,1=>49,2=>50,3=>51,4=>52,5=>53,6=>54,7=>55,8=>56,9=>57,10=>65,
        11=>66,12=>67,13=>68,14=>69,15=>70,16=>71,17=>72,18=>73,19=>74,20=>75,
        21=>76,22=>77,23=>78,24=>79,25=>80,26=>81,27=>82,28=>83,29=>84,30=>85,
        31=>86,32=>87,33=>88,34=>89,35=>90,36=>97,37=>98,38=>99,39=>100,40=>101,
        41=>102,42=>103,43=>104,44=>105,45=>106,46=>107,47=>108,48=>109,49=>110,
        50=>111,51=>112,52=>113,53=>114,54=>115,55=>116,56=>117,57=>118,58=>119,
        59=>120,60=>121,61=>122
    );

    public static function base62($int) {
        $key = "";
        while(bccomp($int, 0) > 0) {
            $mod = bcmod($int, 62);
            $key .= chr(self::$chars62[$mod]);
            $int = bcdiv($int, 62);
        }
        return strrev($key);
    }

    public static function hash($num, $len = 5) {
        $ceil = bcpow(62, $len);
        $primes = array_keys(self::$golden_primes);
        $prime = $primes[$len];
        $dec = bcmod(bcmul($num, $prime), $ceil);
        $hash = self::base62($dec);
        return str_pad($hash, $len, "0", STR_PAD_LEFT);
    }

    public static function unbase62($key) {
        $int = 0;
        foreach(str_split(strrev($key)) as $i => $char) {
            $dec = array_search(ord($char), self::$chars62);
            $int = bcadd(bcmul($dec, bcpow(62, $i)), $int);
        }
        return $int;
    }

    public static function unhash($hash) {
        $len = strlen($hash);
        $ceil = bcpow(62, $len);
        $mmiprimes = array_values(self::$golden_primes);
        $mmi = $mmiprimes[$len];
        $num = self::unbase62($hash);
        $dec = bcmod(bcmul($num, $mmi), $ceil);
        return $dec;
    }

}
KevBurnsJr
źródło
12
to ma bardzo inteligentny projekt = D złote liczby pierwsze = world.rock ()
sova
3
Wiem, że komentuję starszy post. Pomyślałem, że wspomnę, że kod KevBurnsJr działa dobrze. Jednak ostatnio właśnie przerzuciłem się z 32-bitowego serwera Windows 2003 na serwer Windows 2008 R2 x64 i stwierdziłem, że powielam unikalne skróty. Muszę teraz znaleźć alternatywną metodę tworzenia kodów potwierdzających.
DanielJay
2
Post został zaktualizowany tak, aby korzystał z bcmath z pomocą niektórych komentatorów, więc teraz powinien być solidny. Ktoś też znalazł sposób na odwrócenie tego, co jest totalnie zajebiste.
KevBurnsJr
2
web.archive.org/web/20130727034425/http://blog.kevburnsjr.com/ ... wygląda na to, że strona jest niedostępna , więc tutaj jest kopia tego linku;)
Harinder
4
Powinieneś przywrócić kopię zapasową swojej witryny lub opublikować wersję php tego pod github.com/KevBurnsJr/pseudocrypt - co za wspaniała mała biblioteka! Nie chciałem używać gigantycznego "systemu" takiego jak YOURLS czy PHURL, po prostu fajnej biblioteki do tworzenia krótkich linków i to wszystko. Dzięki
inorganik
22

Najkrótszy hash ma długość 32 znaków, jednak możesz użyć pierwszych 8 znaków skrótu md5

echo substr(md5('http://www.google.com'), 0, 8);

Aktualizacja : oto kolejna znaleziona tutaj klasa napisana przez Travella Perkinsa, która bierze rekordowy numer i tworzy dla niej krótki hash. 14-cyfrowy numer daje 8-cyfrowy ciąg. Do daty osiągnięcia tej liczby stajesz się bardziej popularny niż tinyurl;)

class BaseIntEncoder {

    //const $codeset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    //readable character set excluded (0,O,1,l)
    const codeset = "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ";

    static function encode($n){
        $base = strlen(self::codeset);
        $converted = '';

        while ($n > 0) {
            $converted = substr(self::codeset, bcmod($n,$base), 1) . $converted;
            $n = self::bcFloor(bcdiv($n, $base));
        }

        return $converted ;
    }

    static function decode($code){
        $base = strlen(self::codeset);
        $c = '0';
        for ($i = strlen($code); $i; $i--) {
            $c = bcadd($c,bcmul(strpos(self::codeset, substr($code, (-1 * ( $i - strlen($code) )),1))
                    ,bcpow($base,$i-1)));
        }

        return bcmul($c, 1, 0);
    }

    static private function bcFloor($x)
    {
        return bcmul($x, '1', 0);
    }

    static private function bcCeil($x)
    {
        $floor = bcFloor($x);
        return bcadd($floor, ceil(bcsub($x, $floor)));
    }

    static private function bcRound($x)
    {
        $floor = bcFloor($x);
        return bcadd($floor, round(bcsub($x, $floor)));
    }
}

oto przykład, jak go używać:

BaseIntEncoder::encode('1122344523');//result:3IcjVE
BaseIntEncoder::decode('3IcjVE');//result:1122344523
Nazariy
źródło
32
Używając pierwszych 8 znaków md5, prawdopodobnie istnieje rozsądna szansa, że ​​dwa adresy URL mają ten sam skrót
Tom Haigh
2
Tak, taka kolizja może się zdarzyć, ale szansa na losowy ciąg jest bardzo mała, to około 1 do 4 miliardów, ale jeśli chcesz mieć w 100% unikalny hash, którego możesz użyć jako odniesienia do użycia rekordu bazy danych zawiera klasę.
Nazariy
2
chcę wspomnieć, że const codesetmoże to być w dowolnej kolejności, tylko po to, aby zaciemnić ++
Luis Siquot
4

W przypadku krótkiego skrótu , przyjaznego dla adresu URL , w celu uniemożliwienia powielania treści, możemy użyć, hash()a zwłaszcza skrótu CRC , ponieważ jest on stworzony dokładnie do tego:

Cykliczna kontrola nadmiarowa

Cykliczna kontrola nadmiarowa (CRC) to kod wykrywający błędy powszechnie używany w sieciach cyfrowych i urządzeniach pamięci masowej do wykrywania przypadkowych zmian w surowych danych. Bloki danych wprowadzane do tych systemów otrzymują dołączoną krótką wartość kontrolną, opartą na pozostałej części wielomianowego podziału ich zawartości. Przy pobieraniu obliczenia są powtarzane, a jeśli wartości kontrolne nie są zgodne, można podjąć działania naprawcze

https://en.wikipedia.org/wiki/Cyclic_redundancy_check

echo hash("crc32", "Content of article...");
// Output fd3e7c6e
NVRM
źródło
2

Najlepsza jak dotąd odpowiedź: Najmniejszy unikalny ciąg znaków „Hash Like” z podanym unikalnym identyfikatorem bazy danych - rozwiązanie PHP, żadne biblioteki zewnętrzne nie są wymagane.

Oto kod:

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

}

?>
John Erck
źródło
1

Właściwie najlepszym rozwiązaniem by mieć "losowy" hash jest wygenerowanie listy losowych hashów, umieszczenie jej na MySQL z unikalnym INDEKSEM (możesz napisać prosty UDF, aby wstawić 100 000 wierszy w 1 sekundę).

Myślę, że struktura taka jak ta ID | HASH | STATUS | URL | VIEWS | ......

Gdzie status wskazuje, czy ten hash jest wolny, czy nie.

Thomas Decaux
źródło
0

Prosty sposób na zduplikowane sprawdzanie w bazie danych:

$unique = false;

// While will be repeated until we get unique hash
while($unique == false) {

    // Getting full hash based on random numbers
    $full_hash = base64_encode( rand(9999,999999) ); 

    // Taking only first 8 symbols
    $hash = substr($full_hash, 0, 8); 

    // Checking for duplicate in Database - Laravel SQL syntax
    $duplicate = \App\Item::where('url', $hash)->count(); 

    // If no Duplicate, setting Hash as unique
    if ($duplicate==0) {

        // For stoping while
        $unique=true;

        // New Hash is confirmed as unique
        $input['url']=$hash; 
    }
}
Gediminas
źródło
0

Skracałem adres URL. W moim przypadku użyłem „id” bazy danych do stworzenia za każdym razem unikalnego, krótkiego adresu URL.

Najpierw zrobiłem -

Wstaw dane, takie jak „Oryginalny adres URL” i „data utworzenia” w bazie danych, pozostawiając „krótki adres URL” pusty w bazie danych. Następnie pobierz stamtąd "id" i przekaż poniższą funkcję.

<?php
    function genUniqueCode($id){
    $id = $id + 100000000000;
    return base_convert($id, 10, 36);
}

//Get Unique Code using ID
/*
id Below is retrived from Database after Inserting Original URL.
*/



$data['id'] =10;
$uniqueCode = genUniqueCode($data['id']);

   // Generating the URL
$protocol = strtolower(substr($_SERVER["SERVER_PROTOCOL"],0,5))=='https'?'https':'http';
echo "<a href='{$protocol}://{$_SERVER['HTTP_HOST']}/{$uniqueCode}'>{$protocol}://{$_SERVER['HTTP_HOST']}/{$uniqueCode}</a>";

?>

A następnie UPDATE wartość krótkiego kodu URL w bazie danych.

Tutaj używam "id" do tworzenia krótkiego kodu. Ponieważ identyfikator nie może być taki sam dla wielu wpisów. Jest unikalny, dlatego unikalny kod lub adres URL będzie niepowtarzalny.

lazycipher
źródło