|
|
const redis = require('../models/redis') |
|
|
const logger = require('../utils/logger') |
|
|
const { v4: uuidv4 } = require('uuid') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TokenRefreshService { |
|
|
constructor() { |
|
|
this.lockTTL = 60 |
|
|
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 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
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) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async acquireRefreshLock(accountId, platform = 'claude') { |
|
|
const lockKey = `token_refresh_lock:${platform}:${accountId}` |
|
|
return await this.acquireLock(lockKey) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async releaseRefreshLock(accountId, platform = 'claude') { |
|
|
const lockKey = `token_refresh_lock:${platform}:${accountId}` |
|
|
await this.releaseLock(lockKey) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|