將輸入字串(如長網址)經 SHA256 雜湊,再以自定義 64 字元字典編碼,並引入隨機步長切分雜湊值,產生 8-16 字元的短代碼。特點:
<?php
class ShortCodeGenerator {
// 64 字元字典:URL 安全、無視覺混淆字元
private const DICTIONARY = '1qaz2wsx3edc4rfv5t-gb6yhn7ujm8ik9ol0p_';
private const DICT_LEN = 64; // strlen(self::DICTIONARY)
/**
* 產生短代碼
* @param string $input 輸入字串(長網址、ID 等)
* @param int $minLen 最小長度
* @param int $maxLen 最大長度
* @return string 短代碼
*/
public static function generate(string $input, int $minLen = 8, int $maxLen = 16): string {
// 1. SHA256 雜湊(64 hex 字元 = 256 bits)
$hash = hash('sha256', $input);
// 2. 隨機步長:決定如何切分雜湊
$step = random_int($minLen, $maxLen);
$count = (int)ceil(strlen($hash) / $step);
$code = '';
for ($i = 0; $i < $count; $i++) {
$start = $i * $step;
$chunk = substr($hash, $start, $step);
// hex 轉整數,取低 30 位元
$num = 0x3fffffff & (1 * '0x' . $chunk);
// 模 64 映射到字典
$index = $num % self::DICT_LEN;
$code .= self::DICTIONARY[$index];
}
return $code;
}
/**
* 批次產生(避免衝突重試)
*/
public static function generateUnique(string $input, callable $existsCheck, int $maxRetries = 5): string {
for ($i = 0; $i < $maxRetries; $i++) {
$code = self::generate($input . $i); // 加入鹽值避免衝突
if (!$existsCheck($code)) return $code;
}
throw new RuntimeException('無法產生唯一短代碼,請增加長度或字典');
}
}
// 使用範例
$urls = [
'https://example.com/very/long/url/with/many/parameters?foo=bar&baz=qux',
'https://shop.example.com/product/12345?ref=affiliate&utm_source=newsletter',
'https://api.example.com/v1/users/987654321/posts?limit=50&offset=100',
];
foreach ($urls as $url) {
$code = ShortCodeGenerator::generate($url);
echo "短代碼:$code (長度:" . strlen($code) . ")\n";
echo "短網址:https://short.ly/$code\n\n";
}
?>
| 原則 | 說明 | 本實作字典 |
|---|---|---|
| 長度 64 | 2^6,每字元承載 6 bits 資訊量 | ✅ 64 字元 |
| URL 安全 | 無保留字元、無需編碼 | ✅ 無 + / = ? & # |
| 視覺可辨 | 避免 0/O, 1/l/I, 2/Z, 5/S, 8/B 等混淆 | ⚠️ 含 0/o, 1/l 等,建議移除 |
| 大小寫混合 | 增加資訊熵、美觀 | ❌ 僅小寫+數字+符號 |
// 移除視覺混淆字元:0 O o, 1 I l, 2 Z, 5 S, 8 B, - _ 等 private const DICTIONARY_V2 = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz'; // 共 64 字元:數字 2-9 (8) + 大寫除 I,O (24) + 小寫除 l,o (24) = 56... 需補齊 // 標準 Base64URL 字典即為最佳選擇 private const DICTIONARY_BASE64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
| 方案 | 字元數 | 資訊熵/字元 | 標準化 | 適用場景 |
|---|---|---|---|---|
| Base62 (0-9a-zA-Z) | 62 | 5.95 bits | 事實標準 | 短網址、ID 混淆 |
| Base64URL | 64 | 6 bits | RFC 4648 | JWT、Token、短網址 |
| 本文自定義 | 64 | 6 bits | 無 | 教學、特定需求 |
短代碼生成核心是「雜湊 + 編碼 + 衝突處理」。生產環境建議:使用標準 Base64URL、資料庫 UNIQUE 索引保證唯一性、Redis 快取映射關係、考慮分散式系統下的雪花演算法 或 UUID 作為替代方案。