mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 添加多模型支持和OpenAI兼容接口
- 新增 Gemini 模型支持和账户管理功能 - 实现 OpenAI 格式到 Claude/Gemini 的请求转换 - 添加自动 token 刷新服务,支持提前刷新策略 - 增强 Web 管理界面,支持 Gemini 账户管理 - 优化 token 显示,添加掩码功能 - 完善日志记录和错误处理 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
147
src/services/tokenRefreshService.js
Normal file
147
src/services/tokenRefreshService.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const redis = require('../models/redis');
|
||||
const logger = require('../utils/logger');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const {
|
||||
logRefreshSkipped
|
||||
} = require('../utils/tokenRefreshLogger');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user