From 9b9d73fed9a407ca4e6d0fe779dd92f2e3d478f7 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 2 May 2026 04:38:10 +0300 Subject: [PATCH] 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 --- .../internal/id/snowflake.go | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 apps/carrier-connector/internal/id/snowflake.go diff --git a/apps/carrier-connector/internal/id/snowflake.go b/apps/carrier-connector/internal/id/snowflake.go new file mode 100644 index 0000000..3764ad3 --- /dev/null +++ b/apps/carrier-connector/internal/id/snowflake.go @@ -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()) +}