94 lines
1.8 KiB
Go
94 lines
1.8 KiB
Go
// k ordered id generator
|
|
package ids
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// 2024-07-03T00:00:00Z
|
|
customEpochMs int64 = 1719964800000
|
|
|
|
seqBits = 8
|
|
workerBits = 16
|
|
timeBits = 40
|
|
|
|
seqMask = (1 << seqBits) - 1 // 0xFF
|
|
workerMask = (1 << workerBits) - 1 // 0xFFFF
|
|
timeMask = (int64(1) << timeBits) - 1 // low 40 bits
|
|
|
|
workerShift = seqBits
|
|
timeShift = workerBits + seqBits
|
|
)
|
|
|
|
// Generator is safe for concurrent use.
|
|
type Generator struct {
|
|
workerID uint64
|
|
mu sync.Mutex
|
|
lastMs int64
|
|
sequence uint64
|
|
}
|
|
|
|
func NewGenerator(workerID uint32) (*Generator, error) {
|
|
if workerID > workerMask {
|
|
return nil, errors.New("workerID too large for 16 bits")
|
|
}
|
|
return &Generator{workerID: uint64(workerID)}, nil
|
|
}
|
|
|
|
func (g *Generator) Next() (uint64, error) {
|
|
nowMs := time.Now().UTC().UnixMilli()
|
|
delta := nowMs - customEpochMs
|
|
if delta < 0 {
|
|
return 0, errors.New("time is before custom epoch")
|
|
}
|
|
if delta > timeMask {
|
|
return 0, errors.New("timestamp overflow (40-bit ms range exceeded)")
|
|
}
|
|
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
if delta == g.lastMs {
|
|
g.sequence = (g.sequence + 1) & seqMask
|
|
if g.sequence == 0 {
|
|
// sequence wrapped; wait for next millisecond
|
|
for delta == g.lastMs {
|
|
nowMs = time.Now().UTC().UnixMilli()
|
|
delta = nowMs - customEpochMs
|
|
}
|
|
}
|
|
} else {
|
|
g.sequence = 0
|
|
g.lastMs = delta
|
|
}
|
|
|
|
id := (uint64(delta) << timeShift) |
|
|
((g.workerID & workerMask) << workerShift) |
|
|
(g.sequence & seqMask)
|
|
|
|
return id, nil
|
|
}
|
|
|
|
// ---- Base62 ----
|
|
|
|
const base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
|
|
func Base62Encode(x uint64) string {
|
|
if x == 0 {
|
|
return "0"
|
|
}
|
|
var buf [11]byte // 62^11 > 2^64, so max 11 chars
|
|
i := len(buf)
|
|
for x > 0 {
|
|
r := x % 62
|
|
x /= 62
|
|
i--
|
|
buf[i] = base62Alphabet[r]
|
|
}
|
|
return string(buf[i:])
|
|
}
|
|
|