package common import ( "errors" "strconv" "sync" "time" ) /** 分布式/多实例运行(重要) 如果你有多个 Pod 或服务器,必须在程序启动时给它们分配不同的 ID,否则还是会冲突 func main() { // 获取当前机器的编号,比如从配置文件或环境变量读取 // 假设这是第 2 号机器 myMachineID := int64(2) // 初始化 err := common.InitGenerator(myMachineID) if err != nil { panic(err) } // 之后随处调用 println(common.GenerateStringID()) } */ // 定义常量 (标准雪花算法配置) const ( epoch = int64(1704067200000) // 起始时间戳 2024-01-01 workerBits = uint(10) // 机器ID位数 sequenceBits = uint(12) // 序列号位数 maxWorker = -1 ^ (-1 << workerBits) maxSequence = -1 ^ (-1 << sequenceBits) workerShift = sequenceBits timestampShift = sequenceBits + workerBits ) type IDGenerator struct { mu sync.Mutex lastTime int64 workerID int64 sequence int64 } var ( defaultGenerator *IDGenerator once sync.Once ) // InitGenerator 初始化单例生成器 // 修复:校验逻辑移到 once.Do 外部,防止校验失败消耗掉 once 的执行机会 func InitGenerator(workerID int64) error { // 1. 先校验,如果失败直接返回,不要触碰 once if workerID < 0 || workerID > int64(maxWorker) { return errors.New("worker ID excess of limit (0-1023)") } // 2. 执行初始化 once.Do(func() { defaultGenerator = &IDGenerator{ workerID: workerID, lastTime: 0, sequence: 0, } }) return nil } // getInstance 获取单例 func getInstance() *IDGenerator { // 双重检查,虽然 once.Do 是线程安全的,但如果 InitGenerator 没被调用过, // 我们需要确保这里能兜底初始化 if defaultGenerator == nil { once.Do(func() { defaultGenerator = &IDGenerator{ workerID: 1, // 默认机器ID,防止未初始化导致 panic lastTime: 0, sequence: 0, } }) } // 【关键修复】如果经过上面的逻辑 defaultGenerator 还是 nil // (这种情况极少见,除非 InitGenerator 曾经被错误调用且没有赋值) // 强制创建一个临时的或抛出 panic,避免空指针崩溃 if defaultGenerator == nil { // 最后的兜底,防止崩溃 return &IDGenerator{workerID: 1} } return defaultGenerator } // GenerateLongID 全局辅助函数 func GenerateLongID() int64 { return getInstance().NextID() } // GenerateStringID 全局辅助函数 func GenerateStringID() string { return strconv.FormatInt(getInstance().NextID(), 10) } // NextID 生成下一个 ID func (g *IDGenerator) NextID() int64 { // 防御性编程:防止 g 为 nil if g == nil { // 如果实例是 nil,尝试获取默认实例 if defaultGenerator != nil { g = defaultGenerator } else { // 极端情况,创建一个临时对象(虽然锁不住全局,但能防崩) g = &IDGenerator{workerID: 1} } } g.mu.Lock() defer g.mu.Unlock() now := time.Now().UnixMilli() if now < g.lastTime { now = g.lastTime } if now == g.lastTime { g.sequence = (g.sequence + 1) & int64(maxSequence) if g.sequence == 0 { for now <= g.lastTime { now = time.Now().UnixMilli() } } } else { g.sequence = 0 } g.lastTime = now return ((now - epoch) << timestampShift) | (g.workerID << workerShift) | g.sequence }