mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
Merge PR #541: 添加账户订阅到期时间管理功能 + 修复核心过期检查逻辑
## 原PR功能 - ✅ 后端添加subscriptionExpiresAt字段支持 - ✅ 前端提供到期时间设置界面(快捷选项 + 自定义日期) - ✅ 账户列表显示到期状态(已过期🔴/即将过期🟠/永不过期⚪) - ✅ 新增AccountExpiryEditModal.vue编辑弹窗组件 - ✅ 支持创建和更新账户时设置到期时间 - ✅ 完整支持暗黑模式 ## 🔧 关键修复(本次提交) 原PR缺少核心过期检查逻辑,过期账户仍会被调度使用。本次合并时添加了: 1. **新增isAccountNotExpired()方法**: - 检查账户subscriptionExpiresAt字段 - 未设置过期时间视为永不过期 - 添加debug日志记录过期账户 2. **在selectAvailableAccount()中添加过期检查**: - 过滤逻辑中集成this.isAccountNotExpired(account) - 确保过期账户不被选择 3. **在selectAccountForApiKey()中添加过期检查**: - 绑定账户检查中添加过期验证 - 共享池过滤中添加过期验证 ## 🗑️ 清理工作 - 移除了不应提交的account_expire_feature.md评审文档(756行) ## 技术细节 - API层使用expiresAt,存储层使用subscriptionExpiresAt - 存储格式:ISO 8601 (UTC) - 空值表示:null表示永不过期 - 时区处理:后端UTC,前端自动转换本地时区 作者: mrlitong (原PR) + Claude Code (修复) PR: https://github.com/Wei-Shaw/claude-relay-service/pull/541
This commit is contained in:
@@ -2266,7 +2266,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
autoStopOnWarning,
|
||||
useUnifiedUserAgent,
|
||||
useUnifiedClientId,
|
||||
unifiedClientId
|
||||
unifiedClientId,
|
||||
expiresAt
|
||||
} = req.body
|
||||
|
||||
if (!name) {
|
||||
@@ -2309,7 +2310,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
autoStopOnWarning: autoStopOnWarning === true, // 默认为false
|
||||
useUnifiedUserAgent: useUnifiedUserAgent === true, // 默认为false
|
||||
useUnifiedClientId: useUnifiedClientId === true, // 默认为false
|
||||
unifiedClientId: unifiedClientId || '' // 统一的客户端标识
|
||||
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
|
||||
expiresAt: expiresAt || null // 账户订阅到期时间
|
||||
})
|
||||
|
||||
// 如果是分组类型,将账户添加到分组
|
||||
@@ -2396,7 +2398,14 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
}
|
||||
}
|
||||
|
||||
await claudeAccountService.updateAccount(accountId, updates)
|
||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
}
|
||||
|
||||
await claudeAccountService.updateAccount(accountId, mappedUpdates)
|
||||
|
||||
logger.success(`📝 Admin updated Claude account: ${accountId}`)
|
||||
return res.json({ success: true, message: 'Claude account updated successfully' })
|
||||
|
||||
@@ -73,7 +73,8 @@ class ClaudeAccountService {
|
||||
autoStopOnWarning = false, // 5小时使用量接近限制时自动停止调度
|
||||
useUnifiedUserAgent = false, // 是否使用统一Claude Code版本的User-Agent
|
||||
useUnifiedClientId = false, // 是否使用统一的客户端标识
|
||||
unifiedClientId = '' // 统一的客户端标识
|
||||
unifiedClientId = '', // 统一的客户端标识
|
||||
expiresAt = null // 账户订阅到期时间
|
||||
} = options
|
||||
|
||||
const accountId = uuidv4()
|
||||
@@ -113,7 +114,9 @@ class ClaudeAccountService {
|
||||
? JSON.stringify(subscriptionInfo)
|
||||
: claudeAiOauth.subscriptionInfo
|
||||
? JSON.stringify(claudeAiOauth.subscriptionInfo)
|
||||
: ''
|
||||
: '',
|
||||
// 账户订阅到期时间
|
||||
subscriptionExpiresAt: expiresAt || ''
|
||||
}
|
||||
} else {
|
||||
// 兼容旧格式
|
||||
@@ -141,7 +144,9 @@ class ClaudeAccountService {
|
||||
autoStopOnWarning: autoStopOnWarning.toString(), // 5小时使用量接近限制时自动停止调度
|
||||
useUnifiedUserAgent: useUnifiedUserAgent.toString(), // 是否使用统一Claude Code版本的User-Agent
|
||||
// 手动设置的订阅信息
|
||||
subscriptionInfo: subscriptionInfo ? JSON.stringify(subscriptionInfo) : ''
|
||||
subscriptionInfo: subscriptionInfo ? JSON.stringify(subscriptionInfo) : '',
|
||||
// 账户订阅到期时间
|
||||
subscriptionExpiresAt: expiresAt || ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +491,7 @@ class ClaudeAccountService {
|
||||
createdAt: account.createdAt,
|
||||
lastUsedAt: account.lastUsedAt,
|
||||
lastRefreshAt: account.lastRefreshAt,
|
||||
expiresAt: account.expiresAt,
|
||||
expiresAt: account.subscriptionExpiresAt || null, // 账户订阅到期时间
|
||||
// 添加 scopes 字段用于判断认证方式
|
||||
// 处理空字符串的情况,避免返回 ['']
|
||||
scopes: account.scopes && account.scopes.trim() ? account.scopes.split(' ') : [],
|
||||
@@ -618,7 +623,8 @@ class ClaudeAccountService {
|
||||
'autoStopOnWarning',
|
||||
'useUnifiedUserAgent',
|
||||
'useUnifiedClientId',
|
||||
'unifiedClientId'
|
||||
'unifiedClientId',
|
||||
'subscriptionExpiresAt'
|
||||
]
|
||||
const updatedData = { ...accountData }
|
||||
let shouldClearAutoStopFields = false
|
||||
@@ -637,6 +643,9 @@ class ClaudeAccountService {
|
||||
} else if (field === 'subscriptionInfo') {
|
||||
// 处理订阅信息更新
|
||||
updatedData[field] = typeof value === 'string' ? value : JSON.stringify(value)
|
||||
} else if (field === 'subscriptionExpiresAt') {
|
||||
// 处理订阅到期时间,允许 null 值(永不过期)
|
||||
updatedData[field] = value ? value.toString() : ''
|
||||
} else if (field === 'claudeAiOauth') {
|
||||
// 更新 Claude AI OAuth 数据
|
||||
if (value) {
|
||||
@@ -650,7 +659,7 @@ class ClaudeAccountService {
|
||||
updatedData.lastRefreshAt = new Date().toISOString()
|
||||
}
|
||||
} else {
|
||||
updatedData[field] = value.toString()
|
||||
updatedData[field] = value !== null && value !== undefined ? value.toString() : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user