Files
backend/internal/pkg/utils/snowflake.go
lan 4d8f2ec997 Initial backend repository commit.
Set up project files and add .gitignore to exclude local build/runtime artifacts.

Made-with: Cursor
2026-03-09 21:28:58 +08:00

262 lines
6.2 KiB
Go
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.
package utils
import (
"errors"
"os"
"sync"
"time"
)
// 雪花算法常量定义
const (
// 64位ID结构1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
// 机器ID占用的位数
nodeIDBits uint64 = 10
// 序列号占用的位数
sequenceBits uint64 = 12
// 机器ID的最大值 (0-1023)
maxNodeID int64 = -1 ^ (-1 << nodeIDBits)
// 序列号的最大值 (0-4095)
maxSequence int64 = -1 ^ (-1 << sequenceBits)
// 机器ID左移位数
nodeIDShift uint64 = sequenceBits
// 时间戳左移位数
timestampShift uint64 = sequenceBits + nodeIDBits
// 自定义纪元时间2024-01-01 00:00:00 UTC
// 使用自定义纪元可以延长ID有效期约24年从2024年开始
customEpoch int64 = 1704067200000 // 2024-01-01 00:00:00 UTC 的毫秒时间戳
)
// 错误定义
var (
// ErrInvalidNodeID 机器ID无效
ErrInvalidNodeID = errors.New("node ID must be between 0 and 1023")
// ErrClockBackwards 时钟回拨
ErrClockBackwards = errors.New("clock moved backwards, refusing to generate ID")
)
// IDInfo 解析后的ID信息
type IDInfo struct {
Timestamp int64 // 生成ID时的时间戳毫秒
NodeID int64 // 机器ID
Sequence int64 // 序列号
}
// Snowflake 雪花算法ID生成器
type Snowflake struct {
mu sync.Mutex // 互斥锁,保证线程安全
nodeID int64 // 机器ID (0-1023)
sequence int64 // 当前序列号 (0-4095)
lastTimestamp int64 // 上次生成ID的时间戳
}
// 全局雪花算法实例
var (
globalSnowflake *Snowflake
globalSnowflakeOnce sync.Once
globalSnowflakeErr error
)
// InitSnowflake 初始化全局雪花算法实例
// nodeID: 机器ID范围0-1023可以通过环境变量 NODE_ID 配置
func InitSnowflake(nodeID int64) error {
globalSnowflake, globalSnowflakeErr = NewSnowflake(nodeID)
return globalSnowflakeErr
}
// GetSnowflake 获取全局雪花算法实例
// 如果未初始化,会自动使用默认配置初始化
func GetSnowflake() *Snowflake {
globalSnowflakeOnce.Do(func() {
if globalSnowflake == nil {
globalSnowflake, globalSnowflakeErr = NewSnowflake(-1)
}
})
return globalSnowflake
}
// NewSnowflake 创建雪花算法ID生成器实例
// nodeID: 机器ID范围0-1023可以通过环境变量 NODE_ID 配置
// 如果nodeID为-1则尝试从环境变量 NODE_ID 读取
func NewSnowflake(nodeID int64) (*Snowflake, error) {
// 如果传入-1尝试从环境变量读取
if nodeID == -1 {
nodeIDStr := os.Getenv("NODE_ID")
if nodeIDStr != "" {
// 解析环境变量
parsedID, err := parseInt(nodeIDStr)
if err != nil {
return nil, ErrInvalidNodeID
}
nodeID = parsedID
} else {
// 默认使用0
nodeID = 0
}
}
// 验证机器ID范围
if nodeID < 0 || nodeID > maxNodeID {
return nil, ErrInvalidNodeID
}
return &Snowflake{
nodeID: nodeID,
sequence: 0,
lastTimestamp: 0,
}, nil
}
// parseInt 辅助函数:解析整数
func parseInt(s string) (int64, error) {
var result int64
var negative bool
if len(s) == 0 {
return 0, errors.New("empty string")
}
i := 0
if s[0] == '-' {
negative = true
i = 1
}
for ; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return 0, errors.New("invalid character")
}
result = result*10 + int64(s[i]-'0')
}
if negative {
result = -result
}
return result, nil
}
// GenerateID 生成唯一的雪花算法ID
// 返回值生成的ID以及可能的错误如时钟回拨
// 线程安全:使用互斥锁保证并发安全
func (s *Snowflake) GenerateID() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 获取当前时间戳(毫秒)
now := currentTimestamp()
// 处理时钟回拨
if now < s.lastTimestamp {
return 0, ErrClockBackwards
}
// 同一毫秒内
if now == s.lastTimestamp {
// 序列号递增
s.sequence = (s.sequence + 1) & maxSequence
// 序列号溢出,等待下一毫秒
if s.sequence == 0 {
now = s.waitNextMillis(now)
}
} else {
// 不同毫秒序列号重置为0
s.sequence = 0
}
// 更新上次生成时间
s.lastTimestamp = now
// 组装ID
// ID结构时间戳部分 | 机器ID部分 | 序列号部分
id := ((now - customEpoch) << timestampShift) |
(s.nodeID << nodeIDShift) |
s.sequence
return id, nil
}
// waitNextMillis 等待到下一毫秒
// 参数:当前时间戳
// 返回值:下一毫秒的时间戳
func (s *Snowflake) waitNextMillis(timestamp int64) int64 {
now := currentTimestamp()
for now <= timestamp {
now = currentTimestamp()
}
return now
}
// ParseID 解析雪花算法ID提取其中的信息
// id: 要解析的雪花算法ID
// 返回值包含时间戳、机器ID、序列号的结构体
func ParseID(id int64) *IDInfo {
// 提取序列号低12位
sequence := id & maxSequence
// 提取机器ID中间10位
nodeID := (id >> nodeIDShift) & maxNodeID
// 提取时间戳高41位
timestamp := (id >> timestampShift) + customEpoch
return &IDInfo{
Timestamp: timestamp,
NodeID: nodeID,
Sequence: sequence,
}
}
// currentTimestamp 获取当前时间戳(毫秒)
func currentTimestamp() int64 {
return time.Now().UnixNano() / 1e6
}
// GetNodeID 获取当前机器ID
func (s *Snowflake) GetNodeID() int64 {
return s.nodeID
}
// GetCustomEpoch 获取自定义纪元时间
func GetCustomEpoch() int64 {
return customEpoch
}
// IDToTime 将雪花算法ID转换为生成时间
// 这是一个便捷方法,等价于 ParseID(id).Timestamp
func IDToTime(id int64) time.Time {
info := ParseID(id)
return time.Unix(0, info.Timestamp*1e6) // 毫秒转纳秒
}
// ValidateID 验证ID是否为有效的雪花算法ID
// 检查时间戳是否在合理范围内
func ValidateID(id int64) bool {
if id <= 0 {
return false
}
info := ParseID(id)
// 检查时间戳是否在合理范围内
// 不能早于纪元时间不能晚于当前时间太多允许1分钟的时钟偏差
now := currentTimestamp()
if info.Timestamp < customEpoch || info.Timestamp > now+60000 {
return false
}
// 检查机器ID和序列号是否在有效范围内
if info.NodeID < 0 || info.NodeID > maxNodeID {
return false
}
if info.Sequence < 0 || info.Sequence > maxSequence {
return false
}
return true
}