File size: 4,146 Bytes
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
const redis = require('../models/redis')
const logger = require('../utils/logger')
const { v4: uuidv4 } = require('uuid')

/**
 * Token 刷新锁服务
 * 提供分布式锁机制,避免并发刷新问题
 */
class TokenRefreshService {
  constructor() {
    this.lockTTL = 60 // 锁的TTL: 60秒(token刷新通常在30秒内完成)
    this.lockValue = new Map() // 存储每个锁的唯一值
  }

  /**
   * 获取分布式锁
   * 使用唯一标识符作为值,避免误释放其他进程的锁
   */
  async acquireLock(lockKey) {
    try {
      const client = redis.getClientSafe()
      const lockId = uuidv4()
      const result = await client.set(lockKey, lockId, 'NX', 'EX', this.lockTTL)

      if (result === 'OK') {
        this.lockValue.set(lockKey, lockId)
        logger.debug(`🔒 Acquired lock ${lockKey} with ID ${lockId}, TTL: ${this.lockTTL}s`)
        return true
      }
      return false
    } catch (error) {
      logger.error(`Failed to acquire lock ${lockKey}:`, error)
      return false
    }
  }

  /**
   * 释放分布式锁
   * 使用 Lua 脚本确保只释放自己持有的锁
   */
  async releaseLock(lockKey) {
    try {
      const client = redis.getClientSafe()
      const lockId = this.lockValue.get(lockKey)

      if (!lockId) {
        logger.warn(`⚠️ No lock ID found for ${lockKey}, skipping release`)
        return
      }

      // Lua 脚本:只有当值匹配时才删除
      const luaScript = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
          return redis.call("del", KEYS[1])
        else
          return 0
        end
      `

      const result = await client.eval(luaScript, 1, lockKey, lockId)

      if (result === 1) {
        this.lockValue.delete(lockKey)
        logger.debug(`🔓 Released lock ${lockKey} with ID ${lockId}`)
      } else {
        logger.warn(`⚠️ Lock ${lockKey} was not released - value mismatch or already expired`)
      }
    } catch (error) {
      logger.error(`Failed to release lock ${lockKey}:`, error)
    }
  }

  /**
   * 获取刷新锁
   * @param {string} accountId - 账户ID
   * @param {string} platform - 平台类型 (claude/gemini)
   * @returns {Promise<boolean>} 是否成功获取锁
   */
  async acquireRefreshLock(accountId, platform = 'claude') {
    const lockKey = `token_refresh_lock:${platform}:${accountId}`
    return await this.acquireLock(lockKey)
  }

  /**
   * 释放刷新锁
   * @param {string} accountId - 账户ID
   * @param {string} platform - 平台类型 (claude/gemini)
   */
  async releaseRefreshLock(accountId, platform = 'claude') {
    const lockKey = `token_refresh_lock:${platform}:${accountId}`
    await this.releaseLock(lockKey)
  }

  /**
   * 检查刷新锁状态
   * @param {string} accountId - 账户ID
   * @param {string} platform - 平台类型 (claude/gemini)
   * @returns {Promise<boolean>} 锁是否存在
   */
  async isRefreshLocked(accountId, platform = 'claude') {
    const lockKey = `token_refresh_lock:${platform}:${accountId}`
    try {
      const client = redis.getClientSafe()
      const exists = await client.exists(lockKey)
      return exists === 1
    } catch (error) {
      logger.error(`Failed to check lock status ${lockKey}:`, error)
      return false
    }
  }

  /**
   * 获取锁的剩余TTL
   * @param {string} accountId - 账户ID
   * @param {string} platform - 平台类型 (claude/gemini)
   * @returns {Promise<number>} 剩余秒数,-1表示锁不存在
   */
  async getLockTTL(accountId, platform = 'claude') {
    const lockKey = `token_refresh_lock:${platform}:${accountId}`
    try {
      const client = redis.getClientSafe()
      const ttl = await client.ttl(lockKey)
      return ttl
    } catch (error) {
      logger.error(`Failed to get lock TTL ${lockKey}:`, error)
      return -1
    }
  }

  /**
   * 清理本地锁记录
   * 在进程退出时调用,避免内存泄漏
   */
  cleanup() {
    this.lockValue.clear()
    logger.info('🧹 Cleaned up local lock records')
  }
}

// 创建单例实例
const tokenRefreshService = new TokenRefreshService()

module.exports = tokenRefreshService