EasyVQD/domain/uniqueid/bz.go

84 lines
2.3 KiB
Go
Raw Permalink Normal View History

2026-01-15 19:32:33 +08:00
// 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)
}