From 5de6610626ea3b2a8eafb3837be32a6254a12857 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Fri, 1 May 2026 20:42:15 +0800 Subject: [PATCH] feat(id): refactor to ID naming, define Epoch, and optimize defaultIncr --- .gitignore | 1 + CHANGELOG.md | 6 ++++++ TEST.md | 14 +++++++------- go.sum | 4 ---- id.go | 45 +++++++++++++++++++++++++-------------------- id_test.go | 16 ++++++++-------- 6 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 .gitignore delete mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08cb523 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +go.sum diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed64c3..5ee6974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog: @go/id +## [v1.0.1] - 2026-05-01 + +### Changed +- 重构:将 IdMaker 重命名为 IDMaker,符合 Go 命名规范。 +- 优化:使用 Epoch 常量替代硬编码时间戳,优化 defaultIncr 控制流。 + ## [v1.0.0] - 2026-04-22 ### Added diff --git a/TEST.md b/TEST.md index bb95f18..ad93d77 100644 --- a/TEST.md +++ b/TEST.md @@ -1,27 +1,27 @@ # Test Report: @go/id ## 📋 测试概览 -- **测试时间**: 2026-04-22 +- **测试时间**: 2026-05-01 - **测试环境**: darwin/amd64 (Intel i9-9980HK) - **Go 版本**: 1.25.0 ## ✅ 功能测试 (Functional Tests) | 场景 | 状态 | 描述 | | :--- | :--- | :--- | -| `TestMakeId` | PASS | 生成长度符合预期的通用 ID。 | +| `TestMakeID` | PASS | 生成长度符合预期的通用 ID。 | | `TestGetForMysql` | PASS | 生成 MySQL 友好主键,包含右旋散列逻辑。 | | `TestGetForPostgreSQL`| PASS | 生成 PostgreSQL 友好主键,无右旋散列。 | ## ⚡ 性能基准 (Benchmarks) -| 函数 | 平均耗时 | 内存分配 | -| :--- | :--- | :--- | -| `MakeId-10` | **1523 ns/op** | 912 B/op (9 allocs/op) | -| `GetForMysql-10` | **1505 ns/op** | 896 B/op (9 allocs/op) | +| 函数 | 平均耗时 | +| :--- | :--- | +| `MakeID-10` | **1572 ns/op** | +| `GetForMysql-10` | **1552 ns/op** | * 集成 `@go/rand` 的 `FastInt` 优化,在高并发下彻底规避了锁竞争。 * ID 生成性能稳定,满足高性能主键生成场景。 ## 🛡️ 鲁棒性防御 (Robustness) -- **并发安全**:核心计数器使用 `sync.Mutex` 保护,生成器随机数部分使用高性能无锁池。 +- **并发安全**:核心计数器使用 `sync.Mutex` 保护。 - **碰撞防御**:秒级重置机制配合随机偏移初始化,极大地降低碰撞概率。 - **扩展性**:支持自定义钩子,轻松对接 Redis 等分布式协调服务。 diff --git a/go.sum b/go.sum deleted file mode 100644 index 6744073..0000000 --- a/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -apigo.cc/go/encoding v1.0.0 h1:NFb658uGqyh8hKKK9EYqQ6ybmcIOslV57Tdqvd0+z6Y= -apigo.cc/go/encoding v1.0.0/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI= -apigo.cc/go/rand v1.0.2 h1:dJsm607EynJOAoukTvarrUyvLtBF7pi27A99vw2+i78= -apigo.cc/go/rand v1.0.2/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk= diff --git a/id.go b/id.go index 33745e3..8391be5 100644 --- a/id.go +++ b/id.go @@ -8,45 +8,50 @@ import ( "apigo.cc/go/encoding" ) -type IdMaker struct { +const Epoch = 946656000 + +type IDMaker struct { secCurrent uint64 secIndexNext uint64 secIndexLock sync.Mutex Incr func(sec uint64) uint64 } -func NewIdMaker(incr func(sec uint64) uint64) *IdMaker { - return &IdMaker{Incr: incr, secIndexNext: 1} +func NewIDMaker(incr func(sec uint64) uint64) *IDMaker { + return &IDMaker{Incr: incr, secIndexNext: 1} } -var DefaultIdMaker = NewIdMaker(nil) +var DefaultIDMaker = NewIDMaker(nil) -func MakeId(size int) string { - return DefaultIdMaker.Get(size) +func MakeID(size int) string { + return DefaultIDMaker.Get(size) } -func (im *IdMaker) defaultIncr(sec uint64) uint64 { +func (im *IDMaker) defaultIncr(sec uint64) uint64 { im.secIndexLock.Lock() defer im.secIndexLock.Unlock() + if im.secCurrent == sec { im.secIndexNext++ - } else { - if im.secCurrent == 0 { - im.secIndexNext = rand.FastInt(uint64(1000), uint64(1999)) - } else { - im.secIndexNext = 1 - } - im.secCurrent = sec + return im.secIndexNext } + + if im.secCurrent == 0 { + im.secIndexNext = uint64(rand.FastInt(1000, 1999)) + } else { + im.secIndexNext = 1 + } + + im.secCurrent = sec return im.secIndexNext } -func (im *IdMaker) get(size int, ordered bool, hashToHead bool) string { +func (im *IDMaker) get(size int, ordered bool, hashToHead bool) string { tm := time.Now() - nowSec := uint64(tm.Unix() - 946656000) + nowSec := uint64(tm.Unix() - Epoch) var n, sec uint64 - secCapacity := uint64(901356495) + const secCapacity = 901356495 if nowSec < 11*secCapacity { n = nowSec / secCapacity sec = (nowSec % secCapacity) + 14776336 @@ -94,14 +99,14 @@ func (im *IdMaker) get(size int, ordered bool, hashToHead bool) string { return string(uid) } -func (im *IdMaker) Get(size int) string { +func (im *IDMaker) Get(size int) string { return im.get(size, false, false) } -func (im *IdMaker) GetForMysql(size int) string { +func (im *IDMaker) GetForMysql(size int) string { return im.get(size, true, true) } -func (im *IdMaker) GetForPostgreSQL(size int) string { +func (im *IDMaker) GetForPostgreSQL(size int) string { return im.get(size, true, false) } diff --git a/id_test.go b/id_test.go index 68b916e..b18e81e 100644 --- a/id_test.go +++ b/id_test.go @@ -5,36 +5,36 @@ import ( "apigo.cc/go/id" ) -func TestMakeId(t *testing.T) { - uid := id.MakeId(10) +func TestMakeID(t *testing.T) { + uid := id.MakeID(10) if len(uid) != 10 { t.Errorf("expected length 10, got %d", len(uid)) } } func TestGetForMysql(t *testing.T) { - uid := id.DefaultIdMaker.GetForMysql(10) + uid := id.DefaultIDMaker.GetForMysql(10) if len(uid) != 10 { t.Errorf("expected length 10, got %d", len(uid)) } } func TestGetForPostgreSQL(t *testing.T) { - uid := id.DefaultIdMaker.GetForPostgreSQL(10) + uid := id.DefaultIDMaker.GetForPostgreSQL(10) if len(uid) != 10 { t.Errorf("expected length 10, got %d", len(uid)) } } -func BenchmarkIdMaker(b *testing.B) { - b.Run("MakeId-10", func(b *testing.B) { +func BenchmarkIDMaker(b *testing.B) { + b.Run("MakeID-10", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = id.MakeId(10) + _ = id.MakeID(10) } }) b.Run("GetForMysql-10", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = id.DefaultIdMaker.GetForMysql(10) + _ = id.DefaultIDMaker.GetForMysql(10) } }) }