self::MAX_WORKER_ID || $workerId < 0) { throw new \Exception(sprintf( "Worker ID 必须在 0 到 %d 之间,当前值:%d", self::MAX_WORKER_ID, $workerId )); } // 验证 datacenterId if ($datacenterId > self::MAX_DATACENTER_ID || $datacenterId < 0) { throw new \Exception(sprintf( "Datacenter ID 必须在 0 到 %d 之间,当前值:%d", self::MAX_DATACENTER_ID, $datacenterId )); } $this->workerId = $workerId; $this->datacenterId = $datacenterId; // 记录日志 \think\Log::info(sprintf( "Snowflake 实例初始化完成,WorkerId: %d, DatacenterId: %d", $workerId, $datacenterId )); } /** * 生成下一个ID * @return int * @throws \Exception */ public function nextId() { $timestamp = $this->timeGen(); // 检查时钟回拨 if ($timestamp < $this->lastTimestamp) { $diff = $this->lastTimestamp - $timestamp; // 轻微时钟回拨,等待时钟追上 if ($diff < 100) { // 100毫秒内的回拨 usleep($diff * 1000); // 微秒休眠 $timestamp = $this->timeGen(); } else { throw new \Exception(sprintf( "时钟回拨过大,拒绝生成ID。回拨时间:%d 毫秒", $diff )); } } // 同一毫秒内生成多个ID if ($this->lastTimestamp == $timestamp) { $this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE; // 序列号溢出,等待下一毫秒 if ($this->sequence == 0) { $timestamp = $this->tilNextMillis($this->lastTimestamp); } } else { // 新的毫秒,重置序列号,使用随机初始值避免重复 $this->sequence = mt_rand(0, 10) & self::MAX_SEQUENCE; } $this->lastTimestamp = $timestamp; // 计算并返回ID $id = ((($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT) | ($this->datacenterId << self::DATACENTER_ID_SHIFT) | ($this->workerId << self::WORKER_ID_SHIFT) | $this->sequence); // 记录生成时间(用于调试) $this->lastGenerateTime = microtime(true); return $id; } /** * 等待到下一毫秒 * @param int $lastTimestamp 最后时间戳 * @return int */ protected function tilNextMillis($lastTimestamp) { $timestamp = $this->timeGen(); $waitCount = 0; while ($timestamp <= $lastTimestamp) { // 等待时间过长记录警告 if ($waitCount++ > 100) { \think\Log::warning("Snowflake 等待下一毫秒时间过长,已等待 {$waitCount} 次循环"); } // 微秒级休眠,避免CPU占用过高 usleep(100); // 休眠100微秒 $timestamp = $this->timeGen(); } return $timestamp; } /** * 获取当前时间戳(毫秒) * @return int */ protected function timeGen() { // 使用微秒时间,提高精度 return (int)(microtime(true) * 1000); } /** * 解析雪花ID为各个组成部分 * @param int $id 雪花ID * @return array */ public static function parseId($id) { // 转换为64位二进制字符串 $binary = decbin($id); $binary = str_pad($binary, 64, '0', STR_PAD_LEFT); // 提取各组成部分 $timestampBin = substr($binary, 0, 42); $datacenterIdBin = substr($binary, 42, 5); $workerIdBin = substr($binary, 47, 5); $sequenceBin = substr($binary, 52, 12); // 转换为十进制 $timestamp = bindec($timestampBin) + self::EPOCH; $datacenterId = bindec($datacenterIdBin); $workerId = bindec($workerIdBin); $sequence = bindec($sequenceBin); // 转换为可读时间 $dateTime = date('Y-m-d H:i:s', (int)($timestamp / 1000)); $millisecond = $timestamp % 1000; return [ 'id' => $id, 'timestamp' => $timestamp, 'datetime' => $dateTime, 'millisecond' => $millisecond, 'datacenter_id' => $datacenterId, 'worker_id' => $workerId, 'sequence' => $sequence, 'binary' => $binary, 'parts' => [ 'timestamp' => $timestampBin, 'datacenter_id' => $datacenterIdBin, 'worker_id' => $workerIdBin, 'sequence' => $sequenceBin ] ]; } /** * 生成ID(主调用方法) * @param int|null $workerId 工作机器ID * @param int|null $datacenterId 数据中心ID * @return int * @throws \Exception */ public static function generate($workerId = null, $datacenterId = null) { // 获取配置 if ($workerId === null) { $workerId = self::getWorkerId(); } if ($datacenterId === null) { $datacenterId = self::getDatacenterId(); } // 创建实例键 $instanceKey = "{$workerId}_{$datacenterId}"; // 检查是否已有实例 if (!isset(self::$instances[$instanceKey])) { self::$instances[$instanceKey] = new self($workerId, $datacenterId); } // 生成ID return self::$instances[$instanceKey]->nextId(); } /** * 获取工作机器ID * @return int */ protected static function getWorkerId() { // 1. 从配置文件获取(FastAdmin 使用 application/extra/ 目录) $config = config('snowflake'); if ($config && isset($config['worker_id'])) { $workerId = (int)$config['worker_id']; if ($workerId >= 0 && $workerId <= self::MAX_WORKER_ID) { return $workerId; } } // 2. 从环境变量获取(FastAdmin 使用 .env 文件) if (function_exists('env')) { $envWorkerId = env('SNOWFLAKE_WORKER_ID'); if ($envWorkerId !== null && $envWorkerId !== false) { $workerId = (int)$envWorkerId; if ($workerId >= 0 && $workerId <= self::MAX_WORKER_ID) { return $workerId; } } } // 3. 自动生成(基于服务器信息和进程ID) // 使用服务器IP的最后一段 + 进程ID $serverIp = self::getServerIp(); $ipParts = explode('.', $serverIp); $ipLastPart = end($ipParts); $processId = getmypid() ?: 1; // 计算唯一的工作ID $workerId = (($ipLastPart * 1000) + $processId) % (self::MAX_WORKER_ID + 1); // 确保在有效范围内 $workerId = max(0, min($workerId, self::MAX_WORKER_ID)); return (int)$workerId; } /** * 获取数据中心ID * @return int */ protected static function getDatacenterId() { // 1. 从配置文件获取 $config = config('snowflake'); if ($config && isset($config['datacenter_id'])) { $datacenterId = (int)$config['datacenter_id']; if ($datacenterId >= 0 && $datacenterId <= self::MAX_DATACENTER_ID) { return $datacenterId; } } // 2. 从环境变量获取 if (function_exists('env')) { $envDatacenterId = env('SNOWFLAKE_DATACENTER_ID'); if ($envDatacenterId !== null && $envDatacenterId !== false) { $datacenterId = (int)$envDatacenterId; if ($datacenterId >= 0 && $datacenterId <= self::MAX_DATACENTER_ID) { return $datacenterId; } } } // 3. 自动生成(基于服务器IP) $serverIp = self::getServerIp(); $ipParts = explode('.', $serverIp); // 使用IP前两段计算数据中心ID $part1 = isset($ipParts[0]) ? (int)$ipParts[0] : 0; $part2 = isset($ipParts[1]) ? (int)$ipParts[1] : 0; $datacenterId = (($part1 * 10) + $part2) % (self::MAX_DATACENTER_ID + 1); // 确保在有效范围内 $datacenterId = max(0, min($datacenterId, self::MAX_DATACENTER_ID)); return (int)$datacenterId; } /** * 获取服务器IP * @return string */ protected static function getServerIp() { // 多种方式获取服务器IP if (isset($_SERVER['SERVER_ADDR'])) { return $_SERVER['SERVER_ADDR']; } if (function_exists('gethostname')) { $hostname = gethostname(); if ($hostname) { $ip = gethostbyname($hostname); if ($ip !== $hostname) { return $ip; } } } // 尝试获取本机IP if (function_exists('shell_exec') && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { $ip = shell_exec("hostname -I | awk '{print $1}'"); if ($ip) { return trim($ip); } } // 默认返回127.0.0.1 return '127.0.0.1'; } /** * 批量生成ID * @param int $count 生成数量 * @param int|null $workerId 工作机器ID * @param int|null $datacenterId 数据中心ID * @return array * @throws \Exception */ public static function batchGenerate($count = 10, $workerId = null, $datacenterId = null) { if ($count <= 0 || $count > 10000) { throw new \Exception("生成数量必须在 1-10000 之间"); } $ids = []; for ($i = 0; $i < $count; $i++) { $ids[] = self::generate($workerId, $datacenterId); } return $ids; } /** * 验证ID是否有效 * @param int $id 雪花ID * @return bool */ public static function validate($id) { if (!is_numeric($id) || $id <= 0) { return false; } try { $parsed = self::parseId($id); // 检查时间戳是否合理(不能超过当前时间+10年) $currentTime = (int)(microtime(true) * 1000); $maxTime = $currentTime + (10 * 365 * 24 * 60 * 60 * 1000); // 10年后 if ($parsed['timestamp'] > $maxTime || $parsed['timestamp'] < self::EPOCH) { return false; } // 检查ID组成部分是否在有效范围内 if ($parsed['datacenter_id'] > self::MAX_DATACENTER_ID || $parsed['worker_id'] > self::MAX_WORKER_ID || $parsed['sequence'] > self::MAX_SEQUENCE) { return false; } return true; } catch (\Exception $e) { return false; } } /** * 获取ID生成统计信息 * @return array */ public function getStats() { return [ 'worker_id' => $this->workerId, 'datacenter_id' => $this->datacenterId, 'last_timestamp' => $this->lastTimestamp, 'last_generate_time' => $this->lastGenerateTime, 'max_worker_id' => self::MAX_WORKER_ID, 'max_datacenter_id' => self::MAX_DATACENTER_ID, 'max_sequence' => self::MAX_SEQUENCE, 'epoch' => self::EPOCH, 'epoch_datetime' => date('Y-m-d H:i:s', self::EPOCH / 1000) ]; } /** * 获取ID的详细信息(调试用) * @param int $id * @return array */ public static function getDetail($id) { if (!self::validate($id)) { return ['error' => '无效的雪花ID']; } $parsed = self::parseId($id); // 添加生成时间估算 $generateTime = date('Y-m-d H:i:s', $parsed['timestamp'] / 1000); $now = time(); $generateTimestamp = $parsed['timestamp'] / 1000; $timeDiff = $now - $generateTimestamp; if ($timeDiff < 60) { $timeAgo = "刚刚"; } elseif ($timeDiff < 3600) { $timeAgo = floor($timeDiff / 60) . "分钟前"; } elseif ($timeDiff < 86400) { $timeAgo = floor($timeDiff / 3600) . "小时前"; } else { $timeAgo = floor($timeDiff / 86400) . "天前"; } return [ 'id' => $parsed['id'], 'timestamp' => $parsed['timestamp'], 'datetime' => $parsed['datetime'], 'datetime_full' => $generateTime . '.' . str_pad($parsed['millisecond'], 3, '0', STR_PAD_LEFT), 'time_ago' => $timeAgo, 'datacenter_id' => $parsed['datacenter_id'], 'worker_id' => $parsed['worker_id'], 'sequence' => $parsed['sequence'], 'binary' => chunk_split($parsed['binary'], 8, ' '), 'binary_parts' => [ 'timestamp' => $parsed['parts']['timestamp'], 'datacenter' => $parsed['parts']['datacenter_id'], 'worker' => $parsed['parts']['worker_id'], 'sequence' => $parsed['parts']['sequence'] ] ]; } }