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:
litongtongxue
2025-10-13 00:04:41 +08:00
committed by mrlitong
parent 268f041588
commit 1e7465e533
11 changed files with 1192 additions and 594 deletions

View File

@@ -3724,47 +3724,54 @@ const closeAccountExpiryEdit = () => {
editingExpiryAccount.value = null
}
// 根据账户平台解析更新端点
const resolveAccountUpdateEndpoint = (account) => {
switch (account.platform) {
case 'claude':
return `/admin/claude-accounts/${account.id}`
case 'claude-console':
return `/admin/claude-console-accounts/${account.id}`
case 'bedrock':
return `/admin/bedrock-accounts/${account.id}`
case 'openai':
return `/admin/openai-accounts/${account.id}`
case 'azure_openai':
return `/admin/azure-openai-accounts/${account.id}`
case 'openai-responses':
return `/admin/openai-responses-accounts/${account.id}`
case 'ccr':
return `/admin/ccr-accounts/${account.id}`
case 'gemini':
return `/admin/gemini-accounts/${account.id}`
case 'droid':
return `/admin/droid-accounts/${account.id}`
default:
throw new Error(`Unsupported platform: ${account.platform}`)
}
}
// 保存账户过期时间
const handleSaveAccountExpiry = async ({ accountId, expiresAt }) => {
try {
// 找到对应的账户以获取平台信息
// 根据账号平台选择正确的 API 端点
const account = accounts.value.find((acc) => acc.id === accountId)
if (!account) {
showToast('账户不存在', 'error')
if (expiryEditModalRef.value) {
expiryEditModalRef.value.resetSaving()
}
showToast('未找到账户', 'error')
return
}
// 根据平台动态选择端点
const endpoint = resolveAccountUpdateEndpoint(account)
// 定义每个平台的端点和参数名
// 注意:部分平台使用 :accountId部分使用 :id
let endpoint = ''
switch (account.platform) {
case 'claude':
case 'claude-oauth':
endpoint = `/admin/claude-accounts/${accountId}`
break
case 'gemini':
endpoint = `/admin/gemini-accounts/${accountId}`
break
case 'claude-console':
endpoint = `/admin/claude-console-accounts/${accountId}`
break
case 'bedrock':
endpoint = `/admin/bedrock-accounts/${accountId}`
break
case 'ccr':
endpoint = `/admin/ccr-accounts/${accountId}`
break
case 'openai':
endpoint = `/admin/openai-accounts/${accountId}` // 使用 :id
break
case 'droid':
endpoint = `/admin/droid-accounts/${accountId}` // 使用 :id
break
case 'azure_openai':
endpoint = `/admin/azure-openai-accounts/${accountId}` // 使用 :id
break
case 'openai-responses':
endpoint = `/admin/openai-responses-accounts/${accountId}` // 使用 :id
break
default:
showToast(`不支持的平台类型: ${account.platform}`, 'error')
return
}
const data = await apiClient.put(endpoint, {
expiresAt: expiresAt || null
})
@@ -3782,7 +3789,8 @@ const handleSaveAccountExpiry = async ({ accountId, expiresAt }) => {
}
}
} catch (error) {
showToast(error.message || '更新失败', 'error')
console.error('更新账户过期时间失败:', error)
showToast('更新失败', 'error')
// 重置保存状态
if (expiryEditModalRef.value) {
expiryEditModalRef.value.resetSaving()
@@ -3798,6 +3806,7 @@ onMounted(() => {
<style scoped>
.table-container {
overflow-x: auto;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
@@ -3831,6 +3840,12 @@ onMounted(() => {
min-height: calc(100vh - 300px);
}
.table-container {
overflow-x: auto;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.table-row {
transition: all 0.2s ease;
}