package services import ( "context" "errors" "fmt" g "platform/web/globals" "strconv" "strings" "time" "github.com/google/uuid" "github.com/jxskiss/base62" "github.com/redis/go-redis/v9" ) var ID = IdService{} type IdService struct { } // region SerialID const ( // 保留位,确保最高位为0,防止产生负值 reservedBits = 1 // 时间戳位数 timestampBits = 41 // 序列号位数 sequenceBits = 22 // 最大序列号掩码:2^22 - 1 maxSequence = (1 << sequenceBits) - 1 // 位移计算常量 timestampShift = sequenceBits // Redis 缓存过期时间(秒) redisTTL = 5 ) var ( ErrSequenceOverflow = errors.New("sequence overflow") ) func (s *IdService) GenSerial(ctx context.Context) (string, error) { // 构造Redis键 now := time.Now().Unix() key := idSerialKey(now) // 使用Redis事务确保原子操作 var sequence int64 err := g.Redis.Watch(ctx, func(tx *redis.Tx) error { // 获取当前序列号 currentVal, err := tx.Get(ctx, key).Int64() if err != nil && !errors.Is(err, redis.Nil) { return err } if errors.Is(err, redis.Nil) { currentVal = 0 } sequence = currentVal + 1 // 检查序列号是否溢出 if sequence > maxSequence { return ErrSequenceOverflow } // 将更新后的序列号保存回Redis,设置5秒过期时间 pipe := tx.Pipeline() pipe.Set(ctx, key, sequence, redisTTL*time.Second) _, err = pipe.Exec(ctx) return err }, key) if err != nil { return "", err } // 组装最终ID id := uint64((now << timestampShift) | sequence) idStr := strconv.FormatUint(id, 10) return idStr, nil } // ParseSerial 解析ID,返回其组成部分 func (s *IdService) ParseSerial(id uint64) (timestamp int64, sequence int64) { // 通过位运算和掩码提取各部分 timestamp = int64(id >> timestampShift) sequence = int64(id & maxSequence) return } // idSerialKey 根据时间戳生成Redis键 func idSerialKey(timestamp int64) string { return fmt.Sprintf("global:id:serial:%d", timestamp) } // endregion // region ReadableID // GenReadable 根据给定的标签生成易读的全局唯一标识符 // tag 参数用于标识 ID 的用途,如 "usr" 表示用户ID,"ord" 表示订单ID等 // 生成的 ID 格式为:_,例如:usr_7NLmVLeHwqS73enFZ1i8tB func (s *IdService) GenReadable(tag string) string { // 生成 UUID id := uuid.New() // 将 UUID 编码为 Base62 字符串(更短,更易读) encoded := base62.EncodeToString(id[:]) // 如标签为空,则直接返回编码后的字符串 if tag == "" { return encoded } // 标准化标签:转换为小写并移除特殊字符 tag = normalizeTag(tag) // 组合最终 ID return fmt.Sprintf("%s_%s", tag, encoded) } // ParseReadableID 解析易读ID,返回其标签和编码部分 func (s *IdService) ParseReadableID(id string) (tag string, encoded string) { parts := strings.SplitN(id, "_", 2) if len(parts) != 2 { return "", id } return parts[0], parts[1] } // TryDecodeID 尝试将编码部分解码回 UUID // 如果解码失败,返回错误 func (s *IdService) TryDecodeID(encoded string) (uuid.UUID, error) { // 尝试解码 Base62 编码 bytes, err := base62.DecodeString(encoded) if err != nil { return uuid.UUID{}, err } // 确保长度正确 if len(bytes) != 16 { return uuid.UUID{}, fmt.Errorf("invalid UUID length after decoding: %d", len(bytes)) } // 转换为 UUID var result uuid.UUID copy(result[:], bytes) return result, nil } // normalizeTag 标准化标签 // 转换为小写,移除特殊字符,最多保留 5 个字符 func normalizeTag(tag string) string { // 转换为小写 tag = strings.ToLower(tag) // 移除特殊字符 var sb strings.Builder for _, c := range tag { if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { sb.WriteRune(c) } } // 截取最多 5 个字符 result := sb.String() if len(result) > 5 { result = result[:5] } return result } // endregion