gemini_session.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package service
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "encoding/json"
  6. "strconv"
  7. "strings"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
  9. "github.com/cespare/xxhash/v2"
  10. )
  11. // shortHash 使用 XXHash64 + Base36 生成短 hash(16 字符)
  12. // XXHash64 比 SHA256 快约 10 倍,Base36 比 Hex 短约 20%
  13. func shortHash(data []byte) string {
  14. h := xxhash.Sum64(data)
  15. return strconv.FormatUint(h, 36)
  16. }
  17. // BuildGeminiDigestChain 根据 Gemini 请求生成摘要链
  18. // 格式: s:<hash>-u:<hash>-m:<hash>-u:<hash>-...
  19. // s = systemInstruction, u = user, m = model
  20. func BuildGeminiDigestChain(req *antigravity.GeminiRequest) string {
  21. if req == nil {
  22. return ""
  23. }
  24. var parts []string
  25. // 1. system instruction
  26. if req.SystemInstruction != nil && len(req.SystemInstruction.Parts) > 0 {
  27. partsData, _ := json.Marshal(req.SystemInstruction.Parts)
  28. parts = append(parts, "s:"+shortHash(partsData))
  29. }
  30. // 2. contents
  31. for _, c := range req.Contents {
  32. prefix := "u" // user
  33. if c.Role == "model" {
  34. prefix = "m"
  35. }
  36. partsData, _ := json.Marshal(c.Parts)
  37. parts = append(parts, prefix+":"+shortHash(partsData))
  38. }
  39. return strings.Join(parts, "-")
  40. }
  41. // GenerateGeminiPrefixHash 生成前缀 hash(用于分区隔离)
  42. // 组合: userID + apiKeyID + ip + userAgent + platform + model
  43. // 返回 16 字符的 Base64 编码的 SHA256 前缀
  44. func GenerateGeminiPrefixHash(userID, apiKeyID int64, ip, userAgent, platform, model string) string {
  45. // 组合所有标识符
  46. normalizedUserAgent := NormalizeSessionUserAgent(userAgent)
  47. combined := strconv.FormatInt(userID, 10) + ":" +
  48. strconv.FormatInt(apiKeyID, 10) + ":" +
  49. ip + ":" +
  50. normalizedUserAgent + ":" +
  51. platform + ":" +
  52. model
  53. hash := sha256.Sum256([]byte(combined))
  54. // 取前 12 字节,Base64 编码后正好 16 字符
  55. return base64.RawURLEncoding.EncodeToString(hash[:12])
  56. }
  57. // ParseGeminiSessionValue 解析 Gemini 会话缓存值
  58. // 格式: {uuid}:{accountID}
  59. func ParseGeminiSessionValue(value string) (uuid string, accountID int64, ok bool) {
  60. if value == "" {
  61. return "", 0, false
  62. }
  63. // 找到最后一个 ":" 的位置(因为 uuid 可能包含 ":")
  64. i := strings.LastIndex(value, ":")
  65. if i <= 0 || i >= len(value)-1 {
  66. return "", 0, false
  67. }
  68. uuid = value[:i]
  69. accountID, err := strconv.ParseInt(value[i+1:], 10, 64)
  70. if err != nil {
  71. return "", 0, false
  72. }
  73. return uuid, accountID, true
  74. }
  75. // FormatGeminiSessionValue 格式化 Gemini 会话缓存值
  76. // 格式: {uuid}:{accountID}
  77. func FormatGeminiSessionValue(uuid string, accountID int64) string {
  78. return uuid + ":" + strconv.FormatInt(accountID, 10)
  79. }
  80. // geminiDigestSessionKeyPrefix Gemini 摘要 fallback 会话 key 前缀
  81. const geminiDigestSessionKeyPrefix = "gemini:digest:"
  82. // GenerateGeminiDigestSessionKey 生成 Gemini 摘要 fallback 的 sessionKey
  83. // 组合 prefixHash 前 8 位 + uuid 前 8 位,确保不同会话产生不同的 sessionKey
  84. // 用于在 SelectAccountWithLoadAwareness 中保持粘性会话
  85. func GenerateGeminiDigestSessionKey(prefixHash, uuid string) string {
  86. prefix := prefixHash
  87. if len(prefixHash) >= 8 {
  88. prefix = prefixHash[:8]
  89. }
  90. uuidPart := uuid
  91. if len(uuid) >= 8 {
  92. uuidPart = uuid[:8]
  93. }
  94. return geminiDigestSessionKeyPrefix + prefix + ":" + uuidPart
  95. }