Files
yusheng-php/application/common/library/Snowflake.php
2025-12-26 18:46:06 +08:00

169 lines
4.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\library;
/**
* 雪花算法ID生成器
* 不依赖数据库通过配置或自动生成workerId
* 64位ID结构0(1位符号位) + 41位时间戳 + 10位工作机器ID + 12位序列号
*/
class Snowflake
{
// 起始时间戳2023-01-01 00:00:00可使用到2089年
const EPOCH = 1672531200000;
// 工作机器ID位数
const WORKER_ID_BITS = 10;
// 序列号位数
const SEQUENCE_BITS = 12;
// 最大工作机器ID
const MAX_WORKER_ID = (1 << self::WORKER_ID_BITS) - 1;
// 序列号掩码
const SEQUENCE_MASK = (1 << self::SEQUENCE_BITS) - 1;
// 工作机器ID左移位数
const WORKER_ID_SHIFT = self::SEQUENCE_BITS;
// 时间戳左移位数
const TIMESTAMP_LEFT_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS;
// 工作机器ID通过服务器IP生成
protected $workerId;
// 序列号
protected $sequence = 0;
// 上次生成ID的时间戳
protected $lastTimestamp = -1;
// 单例实例
protected static $instance = null;
/**
* 构造函数私有化,单例模式
*/
private function __construct()
{
$this->workerId = $this->generateWorkerId();
}
/**
* 获取单例实例
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 生成工作机器ID基于服务器IP
*/
protected function generateWorkerId()
{
// 方法1从配置文件中读取
if (config('snowflake.worker_id')) {
return config('snowflake.worker_id') & self::MAX_WORKER_ID;
}
// 方法2基于服务器IP生成推荐
$serverIp = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';
$ipParts = explode('.', $serverIp);
// 使用IP后两段生成workerId
if (count($ipParts) >= 4) {
$workerId = ($ipParts[2] << 8) | $ipParts[3];
} else {
// 如果是IPv6或特殊情况使用随机数
$workerId = mt_rand(0, self::MAX_WORKER_ID);
}
return $workerId & self::MAX_WORKER_ID;
}
/**
* 生成下一个ID
*/
public function nextId()
{
$timestamp = $this->getCurrentTimestamp();
// 时钟回拨处理
if ($timestamp < $this->lastTimestamp) {
$diff = $this->lastTimestamp - $timestamp;
throw new \Exception("时钟回拨了 {$diff} 毫秒");
}
// 同一毫秒内生成多个ID
if ($this->lastTimestamp == $timestamp) {
$this->sequence = ($this->sequence + 1) & self::SEQUENCE_MASK;
if ($this->sequence == 0) {
// 序列号用尽,等待下一毫秒
$timestamp = $this->waitNextMillis($this->lastTimestamp);
}
} else {
$this->sequence = 0;
}
$this->lastTimestamp = $timestamp;
// 组合生成ID
$id = (($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT)
| ($this->workerId << self::WORKER_ID_SHIFT)
| $this->sequence;
return (string)$id;
}
/**
* 批量生成ID
*/
public function nextIds($count)
{
$ids = [];
for ($i = 0; $i < $count; $i++) {
$ids[] = $this->nextId();
}
return $ids;
}
/**
* 获取当前毫秒时间戳
*/
protected function getCurrentTimestamp()
{
return (int)(microtime(true) * 1000);
}
/**
* 等待到下一毫秒
*/
protected function waitNextMillis($lastTimestamp)
{
$timestamp = $this->getCurrentTimestamp();
while ($timestamp <= $lastTimestamp) {
usleep(100); // 休眠100微秒
$timestamp = $this->getCurrentTimestamp();
}
return $timestamp;
}
/**
* 解析雪花ID
*/
public static function parse($id)
{
$id = intval($id);
return [
'timestamp' => ($id >> self::TIMESTAMP_LEFT_SHIFT) + self::EPOCH,
'worker_id' => ($id >> self::WORKER_ID_SHIFT) & self::MAX_WORKER_ID,
'sequence' => $id & self::SEQUENCE_MASK,
'generated_at' => date('Y-m-d H:i:s.v', (($id >> self::TIMESTAMP_LEFT_SHIFT) + self::EPOCH) / 1000)
];
}
}