feat: Add Snowflake ID generator with thread-safe generation, parsing utilities, and default generator

- Add Snowflake struct with mutex, timestamp, nodeID, sequence fields
- Add NewSnowflake constructor with nodeID validation (0 to MaxNodeID range)
- Add Generate method with sequence overflow handling and millisecond-based timestamp
- Add GenerateString method for string representation of generated IDs
- Add Parse, Time, NodeID, Sequence utility functions for ID decomposition
- Add defaultGenerator
This commit is contained in:
nutcas3
2026-05-02 04:38:10 +03:00
parent 96ca53e790
commit 9b9d73fed9
@@ -0,0 +1,109 @@
package id
import (
"fmt"
"sync"
"time"
)
const (
Epoch int64 = 1577836800000
NodeIDBits = 5
SequenceBits = 12
MaxNodeID = -1 ^ (-1 << NodeIDBits)
MaxSequence = -1 ^ (-1 << SequenceBits)
NodeShift = SequenceBits
TimeShift = SequenceBits + NodeIDBits
)
type Snowflake struct {
mu sync.Mutex
timestamp int64
nodeID int64
sequence int64
}
func NewSnowflake(nodeID int64) (*Snowflake, error) {
if nodeID < 0 || nodeID > MaxNodeID {
return nil, fmt.Errorf("node ID must be between 0 and %d", MaxNodeID)
}
return &Snowflake{
nodeID: nodeID,
}, nil
}
func (s *Snowflake) Generate() int64 {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now().UnixNano() / 1e6 // Convert to milliseconds
if s.timestamp == now {
s.sequence = (s.sequence + 1) & MaxSequence
if s.sequence == 0 {
// Sequence overflow, wait for next millisecond
for now <= s.timestamp {
now = time.Now().UnixNano() / 1e6
}
}
} else {
s.sequence = 0
}
s.timestamp = now
return ((now - Epoch) << TimeShift) |
(s.nodeID << NodeShift) |
s.sequence
}
func (s *Snowflake) GenerateString() string {
return fmt.Sprintf("%d", s.Generate())
}
func Parse(id int64) (timestamp, nodeID, sequence int64) {
timestamp = (id >> TimeShift) + Epoch
nodeID = (id >> NodeShift) & MaxNodeID
sequence = id & MaxSequence
return
}
func Time(id int64) time.Time {
timestamp, _, _ := Parse(id)
return time.Unix(timestamp/1000, (timestamp%1000)*1e6)
}
func NodeID(id int64) int64 {
_, nodeID, _ := Parse(id)
return nodeID
}
func Sequence(id int64) int64 {
_, _, sequence := Parse(id)
return sequence
}
var defaultGenerator *Snowflake
func init() {
var err error
defaultGenerator, err = NewSnowflake(1)
if err != nil {
panic(err)
}
}
func Generate() int64 {
return defaultGenerator.Generate()
}
func GenerateString() string {
return defaultGenerator.GenerateString()
}
func GeneratePrefixed(prefix string) string {
return fmt.Sprintf("%s_%s", prefix, GenerateString())
}