mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 改进 Gemini token 刷新机制和错误处理
- 修复 token 刷新时的错误处理逻辑 - 添加详细的错误日志记录 - 新增 test-gemini-refresh.js 测试脚本 - 更新 CLAUDE.md 文档,添加 Gemini token 刷新故障排除指南 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
15
CLAUDE.md
15
CLAUDE.md
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
Claude Relay Service 是一个功能完整的 Claude API 中转服务,支持多账户管理、API Key 认证、代理配置和现代化 Web 管理界面。该服务作为客户端(如 SillyTavern)与 Anthropic API 之间的中间件,提供认证、限流、监控等功能。
|
Claude Relay Service 是一个功能完整的 AI API 中转服务,支持 Claude 和 Gemini 双平台。提供多账户管理、API Key 认证、代理配置和现代化 Web 管理界面。该服务作为客户端(如 SillyTavern、Claude Code、Gemini CLI)与 AI API 之间的中间件,提供认证、限流、监控等功能。
|
||||||
|
|
||||||
## 核心架构
|
## 核心架构
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ Claude Relay Service 是一个功能完整的 Claude API 中转服务,支持
|
|||||||
### 主要服务组件
|
### 主要服务组件
|
||||||
- **claudeRelayService.js**: 核心代理服务,处理请求转发和流式响应
|
- **claudeRelayService.js**: 核心代理服务,处理请求转发和流式响应
|
||||||
- **claudeAccountService.js**: Claude账户管理,OAuth token刷新和账户选择
|
- **claudeAccountService.js**: Claude账户管理,OAuth token刷新和账户选择
|
||||||
|
- **geminiAccountService.js**: Gemini账户管理,Google OAuth token刷新和账户选择
|
||||||
- **apiKeyService.js**: API Key管理,验证、限流和使用统计
|
- **apiKeyService.js**: API Key管理,验证、限流和使用统计
|
||||||
- **oauthHelper.js**: OAuth工具,PKCE流程实现和代理支持
|
- **oauthHelper.js**: OAuth工具,PKCE流程实现和代理支持
|
||||||
|
|
||||||
@@ -124,6 +125,11 @@ npm run setup # 自动生成密钥并创建管理员账户
|
|||||||
2. **授权码无效**: 确保复制了完整的Authorization Code,没有遗漏字符
|
2. **授权码无效**: 确保复制了完整的Authorization Code,没有遗漏字符
|
||||||
3. **Token刷新失败**: 检查refreshToken有效性和代理配置
|
3. **Token刷新失败**: 检查refreshToken有效性和代理配置
|
||||||
|
|
||||||
|
### Gemini Token刷新问题
|
||||||
|
1. **刷新失败**: 确保 refresh_token 有效且未过期
|
||||||
|
2. **错误日志**: 查看 `logs/token-refresh-error.log` 获取详细错误信息
|
||||||
|
3. **测试脚本**: 运行 `node scripts/test-gemini-refresh.js` 测试 token 刷新
|
||||||
|
|
||||||
### 常见开发问题
|
### 常见开发问题
|
||||||
1. **Redis连接失败**: 确认Redis服务运行,检查连接配置
|
1. **Redis连接失败**: 确认Redis服务运行,检查连接配置
|
||||||
2. **管理员登录失败**: 检查init.json同步到Redis,运行npm run setup
|
2. **管理员登录失败**: 检查init.json同步到Redis,运行npm run setup
|
||||||
@@ -217,4 +223,9 @@ npm run cli accounts refresh <accountId>
|
|||||||
# 管理员操作
|
# 管理员操作
|
||||||
npm run cli admin create -- --username admin2
|
npm run cli admin create -- --username admin2
|
||||||
npm run cli admin reset-password -- --username admin
|
npm run cli admin reset-password -- --username admin
|
||||||
```
|
```
|
||||||
|
# important-instruction-reminders
|
||||||
|
Do what has been asked; nothing more, nothing less.
|
||||||
|
NEVER create files unless they're absolutely necessary for achieving your goal.
|
||||||
|
ALWAYS prefer editing an existing file to creating a new one.
|
||||||
|
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
||||||
91
scripts/test-gemini-refresh.js
Normal file
91
scripts/test-gemini-refresh.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 Gemini token 刷新功能
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
||||||
|
|
||||||
|
const redis = require('../src/models/redis');
|
||||||
|
const geminiAccountService = require('../src/services/geminiAccountService');
|
||||||
|
const logger = require('../src/utils/logger');
|
||||||
|
|
||||||
|
async function testGeminiTokenRefresh() {
|
||||||
|
try {
|
||||||
|
console.log('🚀 开始测试 Gemini token 刷新功能...\n');
|
||||||
|
|
||||||
|
// 1. 连接 Redis
|
||||||
|
console.log('📡 连接 Redis...');
|
||||||
|
await redis.connect();
|
||||||
|
console.log('✅ Redis 连接成功\n');
|
||||||
|
|
||||||
|
// 2. 获取所有 Gemini 账户
|
||||||
|
console.log('🔍 获取 Gemini 账户列表...');
|
||||||
|
const accounts = await geminiAccountService.getAllAccounts();
|
||||||
|
const geminiAccounts = accounts.filter(acc => acc.platform === 'gemini');
|
||||||
|
|
||||||
|
if (geminiAccounts.length === 0) {
|
||||||
|
console.log('❌ 没有找到 Gemini 账户');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ 找到 ${geminiAccounts.length} 个 Gemini 账户\n`);
|
||||||
|
|
||||||
|
// 3. 测试每个账户的 token 刷新
|
||||||
|
for (const account of geminiAccounts) {
|
||||||
|
console.log(`\n📋 测试账户: ${account.name} (${account.id})`);
|
||||||
|
console.log(` 状态: ${account.status}`);
|
||||||
|
console.log(` 是否有 refresh token: ${account.refreshToken ? '是' : '否'}`);
|
||||||
|
|
||||||
|
if (!account.refreshToken || account.refreshToken === '[ENCRYPTED]') {
|
||||||
|
console.log(' ⚠️ 跳过:无 refresh token\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取完整账户信息(包括解密的 token)
|
||||||
|
const fullAccount = await geminiAccountService.getAccount(account.id);
|
||||||
|
|
||||||
|
if (!fullAccount.refreshToken) {
|
||||||
|
console.log(' ⚠️ 跳过:无法获取 refresh token\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(' 🔄 开始刷新 token...');
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 执行 token 刷新
|
||||||
|
const newTokens = await geminiAccountService.refreshAccountToken(account.id);
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
console.log(` ✅ Token 刷新成功!耗时: ${duration}ms`);
|
||||||
|
console.log(` 📅 新的过期时间: ${new Date(newTokens.expiry_date).toLocaleString()}`);
|
||||||
|
console.log(` 🔑 Access Token: ${newTokens.access_token.substring(0, 20)}...`);
|
||||||
|
|
||||||
|
// 验证账户状态已更新
|
||||||
|
const updatedAccount = await geminiAccountService.getAccount(account.id);
|
||||||
|
console.log(` 📊 账户状态: ${updatedAccount.status}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` ❌ Token 刷新失败: ${error.message}`);
|
||||||
|
console.log(` 🔍 错误详情:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ 测试完成!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 测试失败:', error);
|
||||||
|
} finally {
|
||||||
|
// 断开 Redis 连接
|
||||||
|
await redis.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testGeminiTokenRefresh();
|
||||||
@@ -168,22 +168,36 @@ async function refreshAccessToken(refreshToken) {
|
|||||||
const oAuth2Client = createOAuth2Client();
|
const oAuth2Client = createOAuth2Client();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 设置 refresh_token
|
||||||
oAuth2Client.setCredentials({
|
oAuth2Client.setCredentials({
|
||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
});
|
});
|
||||||
|
|
||||||
const { credentials } = await oAuth2Client.refreshAccessToken();
|
// 调用 refreshAccessToken 获取新的 tokens
|
||||||
|
const response = await oAuth2Client.refreshAccessToken();
|
||||||
|
const credentials = response.credentials;
|
||||||
|
|
||||||
|
// 检查是否成功获取了新的 access_token
|
||||||
|
if (!credentials || !credentials.access_token) {
|
||||||
|
throw new Error('No access token returned from refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`🔄 Successfully refreshed Gemini token. New expiry: ${new Date(credentials.expiry_date).toISOString()}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access_token: credentials.access_token,
|
access_token: credentials.access_token,
|
||||||
refresh_token: credentials.refresh_token || refreshToken,
|
refresh_token: credentials.refresh_token || refreshToken, // 保留原 refresh_token 如果没有返回新的
|
||||||
scope: credentials.scope || OAUTH_SCOPES.join(' '),
|
scope: credentials.scope || OAUTH_SCOPES.join(' '),
|
||||||
token_type: credentials.token_type || 'Bearer',
|
token_type: credentials.token_type || 'Bearer',
|
||||||
expiry_date: credentials.expiry_date
|
expiry_date: credentials.expiry_date || Date.now() + 3600000 // 默认1小时过期
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error refreshing access token:', error);
|
logger.error('Error refreshing access token:', {
|
||||||
throw new Error('Failed to refresh access token');
|
message: error.message,
|
||||||
|
code: error.code,
|
||||||
|
response: error.response?.data
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to refresh access token: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,11 +639,17 @@ async function refreshAccountToken(accountId) {
|
|||||||
|
|
||||||
logger.error(`Failed to refresh token for account ${accountId}:`, error);
|
logger.error(`Failed to refresh token for account ${accountId}:`, error);
|
||||||
|
|
||||||
// 标记账户为错误状态
|
// 标记账户为错误状态(只有在账户存在时)
|
||||||
await updateAccount(accountId, {
|
if (account) {
|
||||||
status: 'error',
|
try {
|
||||||
errorMessage: error.message
|
await updateAccount(accountId, {
|
||||||
});
|
status: 'error',
|
||||||
|
errorMessage: error.message
|
||||||
|
});
|
||||||
|
} catch (updateError) {
|
||||||
|
logger.error(`Failed to update account status after refresh error:`, updateError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user