mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +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:
@@ -641,6 +641,49 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 到期时间 -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>到期时间 (可选)</label
|
||||
>
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateAccountExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateAccountCustomExpireAt"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-calendar-alt mr-1" />
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
<p v-else class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-infinity mr-1" />
|
||||
账户永不过期
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 分组选择器 -->
|
||||
<div v-if="form.accountType === 'group'">
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -2069,6 +2112,49 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 到期时间 -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>到期时间 (可选)</label
|
||||
>
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateAccountExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateAccountCustomExpireAt"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-calendar-alt mr-1" />
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
<p v-else class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-infinity mr-1" />
|
||||
账户永不过期
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 分组选择器 -->
|
||||
<div v-if="form.accountType === 'group'">
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -3352,7 +3438,11 @@ const form = ref({
|
||||
// Azure OpenAI 特定字段
|
||||
azureEndpoint: props.account?.azureEndpoint || '',
|
||||
apiVersion: props.account?.apiVersion || '',
|
||||
deploymentName: props.account?.deploymentName || ''
|
||||
deploymentName: props.account?.deploymentName || '',
|
||||
// 到期时间字段
|
||||
expireDuration: '',
|
||||
customExpireDate: '',
|
||||
expiresAt: props.account?.expiresAt || null
|
||||
})
|
||||
|
||||
// 模型限制配置
|
||||
@@ -3778,6 +3868,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: proxyPayload
|
||||
}
|
||||
|
||||
@@ -4069,6 +4160,7 @@ const createAccount = async () => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: proxyPayload
|
||||
}
|
||||
|
||||
@@ -4328,6 +4420,7 @@ const updateAccount = async () => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: proxyPayload
|
||||
}
|
||||
|
||||
@@ -5171,6 +5264,61 @@ const handleUnifiedClientIdChange = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 到期时间相关方法
|
||||
// 计算最小日期时间
|
||||
const minDateTime = computed(() => {
|
||||
const now = new Date()
|
||||
now.setMinutes(now.getMinutes() + 1)
|
||||
return now.toISOString().slice(0, 16)
|
||||
})
|
||||
|
||||
// 更新账户过期时间
|
||||
const updateAccountExpireAt = () => {
|
||||
if (!form.value.expireDuration) {
|
||||
form.value.expiresAt = null
|
||||
return
|
||||
}
|
||||
|
||||
if (form.value.expireDuration === 'custom') {
|
||||
return
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const duration = form.value.expireDuration
|
||||
const match = duration.match(/(\d+)([d])/)
|
||||
|
||||
if (match) {
|
||||
const [, value, unit] = match
|
||||
const num = parseInt(value)
|
||||
|
||||
if (unit === 'd') {
|
||||
now.setDate(now.getDate() + num)
|
||||
}
|
||||
|
||||
form.value.expiresAt = now.toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义过期时间
|
||||
const updateAccountCustomExpireAt = () => {
|
||||
if (form.value.customExpireDate) {
|
||||
form.value.expiresAt = new Date(form.value.customExpireDate).toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化过期日期
|
||||
const formatExpireDate = (dateString) => {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时获取统一 User-Agent 信息
|
||||
onMounted(() => {
|
||||
// 初始化平台分组
|
||||
|
||||
Reference in New Issue
Block a user