feat: 支持手动添加 Claude 账户 Access Token

- 添加 OAuth 和手动输入两种账户添加方式
- 支持直接输入 Access Token 和 Refresh Token
- 新增账户编辑功能,可更新 Token 和代理设置
- 优化 Token 刷新失败时的回退机制
- 改进用户体验,支持手动维护长期有效的 Token

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-15 19:28:14 +08:00
parent 8f7d3fcadf
commit fbf942a5fd
4 changed files with 468 additions and 21 deletions

View File

@@ -107,7 +107,7 @@ class ClaudeAccountService {
const refreshToken = this._decryptSensitiveData(accountData.refreshToken);
if (!refreshToken) {
throw new Error('No refresh token available');
throw new Error('No refresh token available - manual token update required');
}
// 创建代理agent
@@ -183,9 +183,20 @@ class ClaudeAccountService {
const now = Date.now();
if (!expiresAt || now >= (expiresAt - 10000)) { // 10秒提前刷新
logger.info(`🔄 Token expired/expiring for account ${accountId}, refreshing...`);
const refreshResult = await this.refreshAccountToken(accountId);
return refreshResult.accessToken;
logger.info(`🔄 Token expired/expiring for account ${accountId}, attempting refresh...`);
try {
const refreshResult = await this.refreshAccountToken(accountId);
return refreshResult.accessToken;
} catch (refreshError) {
logger.warn(`⚠️ Token refresh failed for account ${accountId}: ${refreshError.message}`);
// 如果刷新失败仍然尝试使用当前token可能是手动添加的长期有效token
const currentToken = this._decryptSensitiveData(accountData.accessToken);
if (currentToken) {
logger.info(`🔄 Using current token for account ${accountId} (refresh failed)`);
return currentToken;
}
throw refreshError;
}
}
const accessToken = this._decryptSensitiveData(accountData.accessToken);
@@ -240,7 +251,7 @@ class ClaudeAccountService {
throw new Error('Account not found');
}
const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive'];
const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive', 'claudeAiOauth'];
const updatedData = { ...accountData };
for (const [field, value] of Object.entries(updates)) {
@@ -249,6 +260,18 @@ class ClaudeAccountService {
updatedData[field] = this._encryptSensitiveData(value);
} else if (field === 'proxy') {
updatedData[field] = value ? JSON.stringify(value) : '';
} else if (field === 'claudeAiOauth') {
// 更新 Claude AI OAuth 数据
if (value) {
updatedData.claudeAiOauth = this._encryptSensitiveData(JSON.stringify(value));
updatedData.accessToken = this._encryptSensitiveData(value.accessToken);
updatedData.refreshToken = this._encryptSensitiveData(value.refreshToken);
updatedData.expiresAt = value.expiresAt.toString();
updatedData.scopes = value.scopes.join(' ');
updatedData.status = 'active';
updatedData.errorMessage = '';
updatedData.lastRefreshAt = new Date().toISOString();
}
} else {
updatedData[field] = value.toString();
}