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() (string, error) { now := time.Now().Unix() // 脚本实现原子操作 script := redis.NewScript(` local current = tonumber(redis.call('GET', KEYS[1])) or 0 if current >= tonumber(ARGV[1]) then return redis.error_reply('sequence overflow') end local sequence = current + 1 redis.call('SET', KEYS[1], sequence, 'EX', ARGV[2]) return sequence `) sequence, err := script.Run(context.Background(), g.Redis, []string{idSerialKey(now)}, maxSequence, redisTTL).Int64() if err != nil { return "", err } // 组装最终ID id := uint64((now << timestampShift) | sequence) return strconv.FormatUint(id, 10), 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("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