Loading

php版 雪花ID转换12位base36工具

为了获取 更短&高可辨识度的 唯一字符串ID, 设计了一个 雪花ID转换12位base36工具 ,也可以将 base36 转换回 雪花ID

雪花算法格式图解:

image


<?php
namespace App\Utils;

use Exception;

/**
 * 雪花ID转换工具
 * 雪花ID为12个字符的base36字符串
 */
class SnowBase36
{
    private static $charset           = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    // 字符集长度
    private static $base              = 36;   
    //选填0,1 如果填0转换出的base36字符串首位可能为0打头,所以这里固定为1      
    private static $first_bin         = '1';
    // 数据中心ID配置 (5位) 用于转换回雪花ID时使用 ,根据实际配置修改
    private static $datacenter_id_bin = '00000';

    /**
     * 雪花ID 转 12个字符的base36字符串
     * @param string $snowflakeId
     * @return string
     */
    public static function id2base36($snowflakeId): string
    {
        $snowflakeIdBin = self::decimal2binary($snowflakeId);
        $binary         = str_pad($snowflakeIdBin, 64, '0', STR_PAD_LEFT);
        $timestamp      = substr($binary, 1, 41); // 42位时间截(毫秒级)
        $worker_id      = substr($binary, 42, 5); // 5位workerId
                                                  // $datacenter_id = substr($binary, 47, 5);  // 5位datacenterId   // 默认 "00000"
        $sequence_id = substr($binary, 52, 12);   //  12位序列号

        // var_dump(self::$first_bin . $timestamp . $worker_id . $sequence_id);
        return self::base36encode(self::$first_bin . $timestamp . $worker_id . $sequence_id);
    }

    /**
     * 12个字符的base36字符串 转换回 雪花ID
     * @param string $base36Str
     * @return string
     */
    public static function base362id($base36Str): string
    {
        $binary        = self::base36decode($base36Str); // 59位二进制字符串
        $timestamp     = substr($binary, 1, 41);         // 42位时间截(毫秒级)
        $worker_id     = substr($binary, 42, 5);         // 5位workerId
        $datacenter_id = self::$datacenter_id_bin;       // 默认 "00000"
        $sequence_id   = substr($binary, 47, 12);        // 12位序列号
                                                         // 转换回二进制时,雪花算法第一位是固定值是 0
        $resBin = '0' . $timestamp . $worker_id . $datacenter_id . $sequence_id;
        return self::binary2decimal($resBin);
    }

    /**
     * 10进制字符串转二进制字符串
     * @param string $decimal
     * @throws \Exception
     * @return string
     */
    public static function decimal2binary($decimal)
    {
        // 使用BCMath处理大数
        if (! function_exists('bcmul')) {
            throw new Exception("需启用BCMath扩展");
        }
        $binary = '';
        $num    = $decimal;
        while (bccomp($num, '0') > 0) {
            $remainder = bcmod($num, '2');
            $binary    = $remainder . $binary;
            $num       = bcdiv($num, '2', 0);
        }
        return $binary;
    }

    /**
     * 二进制转10进制(字符串形式)
     * @param string $binary
     * @return string
     */
    public static function binary2decimal($binary): string
    {
        $decimal = '0';
        for ($i = 0; $i < strlen($binary); $i++) {
            $decimal = bcmul($decimal, '2', 0); // 十进制数 × 2
            if ($binary[$i] === '1') {
                $decimal = bcadd($decimal, '1', 0); // 加当前位值
            }
        }
        return $decimal;
    }

    /**
     * 十进制转36进制(12位)
     * @param mixed $decimal
     * @return int|string
     */
    private static function decimal2base36($decimal): string
    {
        if ($decimal === '0') {
            return '0';
        }

        $result = '';
        while (bccomp($decimal, '0', 0) > 0) {
            $remainder = bcmod($decimal, self::$base);
            $result    = self::$charset[$remainder] . $result;
            $decimal   = bcdiv($decimal, self::$base, 0);
        }

        // 确保12位长度(高位补零)
        return str_pad($result, 12, self::$charset[0], STR_PAD_LEFT);
    }

    /**
     * 二进制转36进制(12位)
     * @param string $binary 59位二进制字符串
     * @return string 12位大写字符串
     * @throws Exception
     */
    public static function base36encode($binary): string
    {
        // 验证输入格式
        if (strlen($binary) !== 59 || ! preg_match('/^[01]+$/', $binary)) {
            throw new Exception("无效的二进制输入:必须为59位0/1字符串");
        }
        // 二进制转十进制大数(字符串形式)
        $decimal = self::binary2decimal($binary);
        // 十进制大数转36进制
        return self::decimal2base36($decimal);
    }

    /**
     * 36进制转二进制(59位)
     * @param mixed $base36
     * @throws \Exception
     * @return string
     */
    public static function base36decode($base36): string
    {
        // 验证输入格式
        if (strlen($base36) !== 12 || ! preg_match('/^[0-9A-Za-z]+$/', $base36)) {
            throw new Exception("无效的Base36输入:必须为12位0-9/A-Z字符");
        }

        // 字符到数值的映射表
        $reverseCharset = array_flip(str_split(self::$charset));

        // 将Base36转为十进制大数
        $decimal = '0';
        for ($i = 0; $i < 12; $i++) {
            $char = strtoupper($base36[$i]);
            if (! isset($reverseCharset[$char])) {
                throw new Exception("非法字符 '$char' 在Base36字符串中");
            }
            $digit = $reverseCharset[$char];

            // 大数运算:decimal = decimal * base + digit
            $decimal = bcmul($decimal, self::$base, 0);
            $decimal = bcadd($decimal, $digit, 0);
        }

        // 十进制大数转二进制字符串
        $binary = self::decimal2binary($decimal);

        // 补齐59位(高位补零)
        return str_pad($binary, 59, '0', STR_PAD_LEFT);
    }
}

posted @ 2025-09-04 09:29  ZJH_BLOGS  阅读(13)  评论(0)    收藏  举报