mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 为所有账户服务添加订阅过期检查功能
完成账户订阅到期时间功能的核心调度逻辑实现。 ## 实现范围 ✅ 已添加订阅过期检查的服务(5个): - Gemini 服务:添加 isSubscriptionExpired() 函数及调度过滤 - OpenAI 服务:添加 isSubscriptionExpired() 函数及调度过滤 - Droid 服务:添加 _isSubscriptionExpired() 方法及调度过滤 - Bedrock 服务:添加 _isSubscriptionExpired() 方法及调度过滤 - Azure OpenAI 服务:添加 isSubscriptionExpired() 函数及调度过滤 ## 核心功能 - 账户调度时自动检查 subscriptionExpiresAt 字段 - 过期账户将不再被系统调度使用 - 未设置过期时间的账户视为永不过期(向后兼容) - 使用 <= 比较判断过期(精确到过期时刻) - 跳过过期账户时记录 debug 日志便于排查 ## 技术实现 - 统一的实现模式:过期检查函数 + 账户选择逻辑集成 - 不影响现有功能,完全向后兼容 - 业务字段 subscriptionExpiresAt 与技术字段 expiresAt(OAuth token过期)独立管理 ## 相关文档 参考 account_expire_bugfix.md 了解问题背景和实现细节 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -735,7 +735,11 @@ class DroidAccountService {
|
||||
description,
|
||||
refreshToken: this._encryptSensitiveData(normalizedRefreshToken),
|
||||
accessToken: this._encryptSensitiveData(normalizedAccessToken),
|
||||
expiresAt: normalizedExpiresAt || '',
|
||||
expiresAt: normalizedExpiresAt || '', // OAuth Token 过期时间(技术字段,自动刷新)
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
|
||||
proxy: proxy ? JSON.stringify(proxy) : '',
|
||||
isActive: isActive.toString(),
|
||||
accountType,
|
||||
@@ -821,6 +825,10 @@ class DroidAccountService {
|
||||
accessToken: account.accessToken
|
||||
? maskToken(this._decryptSensitiveData(account.accessToken))
|
||||
: '',
|
||||
|
||||
// ✅ 前端显示订阅过期时间(业务字段)
|
||||
expiresAt: account.subscriptionExpiresAt || null,
|
||||
|
||||
apiKeyCount: (() => {
|
||||
const parsedCount = this._parseApiKeyEntries(account.apiKeys).length
|
||||
if (account.apiKeyCount === undefined || account.apiKeyCount === null) {
|
||||
@@ -961,6 +969,12 @@ class DroidAccountService {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 如果通过路由映射更新了 subscriptionExpiresAt,直接保存
|
||||
// subscriptionExpiresAt 是业务字段,与 token 刷新独立
|
||||
if (sanitizedUpdates.subscriptionExpiresAt !== undefined) {
|
||||
// 直接保存,不做任何调整
|
||||
}
|
||||
|
||||
if (sanitizedUpdates.proxy === undefined) {
|
||||
sanitizedUpdates.proxy = account.proxy || ''
|
||||
}
|
||||
@@ -1257,6 +1271,19 @@ class DroidAccountService {
|
||||
return hoursSinceRefresh >= this.refreshIntervalHours
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查账户订阅是否过期
|
||||
* @param {Object} account - 账户对象
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
_isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
return expiryDate <= new Date()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的 access token(自动刷新)
|
||||
*/
|
||||
@@ -1302,6 +1329,14 @@ class DroidAccountService {
|
||||
const isSchedulable = this._isTruthy(account.schedulable)
|
||||
const status = typeof account.status === 'string' ? account.status.toLowerCase() : ''
|
||||
|
||||
// ✅ 检查账户订阅是否过期
|
||||
if (this._isSubscriptionExpired(account)) {
|
||||
logger.debug(
|
||||
`⏰ Skipping expired Droid account: ${account.name}, expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isActive || !isSchedulable || status !== 'active') {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user