// uniqueid // 的设计是用于生成全局唯一的 ID,避免重复。 // 此库不考虑分布式,仅通过数据库主键索引来实现。 // 当 id 重复时,由业务端抛出错误即可 package uniqueid import ( "context" "crypto/rand" "log/slog" "math/big" "time" "git.lnton.com/lnton/pkg/orm" "github.com/ixugo/goddd/pkg/hook" ) const ( // 删除 o 和 i 的字符集,避免视觉混淆 LetterBytes36NoOI = "abcdefghjklmnpqrstuvwxyz0123456789" LetterBytes72 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" LetterBytes36 = "abcdefghijklmnopqrstuvwxyz0123456789" // default LetterBytes36Upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) type IDManager struct { store UniqueIDStorer // TODO: 可以初始化时读取数据库内的数量,判断重复因子,从而减少尝试或更换策略 letterBytes string // 随机字符串字符集 } func NewIDManager(store UniqueIDStorer) *IDManager { return &IDManager{ store: store, letterBytes: LetterBytes36, } } // SetLetterBytes 设置随机字符串字符集 func (m *IDManager) SetLetterBytes(letterBytes string) { m.letterBytes = letterBytes } // UniqueID 获取唯一 id func (m *IDManager) UniqueID(prefix string, length int) string { cost := hook.UseTiming(time.Second) defer cost() // 如果在最低长度中,碰撞比较频繁,增加 1 位长度再试一次 for i := range 10 { // 生成自定义长度随机数,通过数据库主键来防止碰撞,碰撞后再次尝试 for range 36 { id := prefix + GenerateRandomString(m.letterBytes, length+i) if err := m.store.Add(context.Background(), &UniqueID{ID: id}); err != nil { slog.Error("UniqueID", "err", err) continue } return id } } slog.Error("UniqueID", "err", "超过最大循环次数,未获取到唯一 id") return "unknown" } // UndoUniqueID 删除唯一 id func (m *IDManager) UndoUniqueID(id string) error { var uni UniqueID return m.store.Del(context.Background(), &uni, orm.Where("id=?", id)) } // GenerateRandomString 生成随机字符串 func GenerateRandomString(letterBytes string, length int) string { lettersLength := big.NewInt(int64(len(letterBytes))) result := make([]byte, length) for i := 0; i < length; i++ { idx, _ := rand.Int(rand.Reader, lettersLength) result[i] = letterBytes[idx.Int64()] } return string(result) }