refactor: 优化账户过期检查逻辑和代码一致性

## 主要改进

### 1. 添加缺失的过期检查方法
- 在 `claudeConsoleAccountService` 中添加 `isSubscriptionExpired()` 方法
- 在 `ccrAccountService` 中添加 `isSubscriptionExpired()` 方法
- 与其他 7 个账户服务保持一致的实现方式

### 2. 统一过期检查逻辑
- 重构 `unifiedClaudeScheduler` 中的 5 处手动日期检查代码
- 统一调用服务层的 `isSubscriptionExpired()` 方法
- 消除重复代码,提升可维护性

### 3. 统一字段映射顺序
- 调整 Claude 账户更新端点的 `mapExpiryField()` 调用时机
- 与其他账户类型保持一致的处理顺序
- 提升代码可读性和一致性

## 技术细节

**修改文件**:
- `src/services/claudeConsoleAccountService.js`: 添加 `isSubscriptionExpired()`
- `src/services/ccrAccountService.js`: 添加 `isSubscriptionExpired()`
- `src/services/unifiedClaudeScheduler.js`: 5 处调用统一为服务方法
- `src/routes/admin.js`: 统一字段映射顺序

**改进效果**:
-  代码一致性提升:所有账户服务统一实现
-  可维护性提升:过期逻辑集中管理
-  减少重复代码:消除 4 处重复实现

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
mrlitong
2025-10-14 08:35:13 +00:00
parent cd5df4f76b
commit 1f61478fbc
4 changed files with 72 additions and 61 deletions

View File

@@ -2361,16 +2361,24 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
const { accountId } = req.params const { accountId } = req.params
const updates = req.body const updates = req.body
// ✅ 【修改】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt提前到参数验证之前
const mappedUpdates = mapExpiryField(updates, 'Claude', accountId)
// 验证priority的有效性 // 验证priority的有效性
if ( if (
updates.priority !== undefined && mappedUpdates.priority !== undefined &&
(typeof updates.priority !== 'number' || updates.priority < 1 || updates.priority > 100) (typeof mappedUpdates.priority !== 'number' ||
mappedUpdates.priority < 1 ||
mappedUpdates.priority > 100)
) { ) {
return res.status(400).json({ error: 'Priority must be a number between 1 and 100' }) return res.status(400).json({ error: 'Priority must be a number between 1 and 100' })
} }
// 验证accountType的有效性 // 验证accountType的有效性
if (updates.accountType && !['shared', 'dedicated', 'group'].includes(updates.accountType)) { if (
mappedUpdates.accountType &&
!['shared', 'dedicated', 'group'].includes(mappedUpdates.accountType)
) {
return res return res
.status(400) .status(400)
.json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' }) .json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' })
@@ -2378,9 +2386,9 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
// 如果更新为分组类型验证groupId或groupIds // 如果更新为分组类型验证groupId或groupIds
if ( if (
updates.accountType === 'group' && mappedUpdates.accountType === 'group' &&
!updates.groupId && !mappedUpdates.groupId &&
(!updates.groupIds || updates.groupIds.length === 0) (!mappedUpdates.groupIds || mappedUpdates.groupIds.length === 0)
) { ) {
return res return res
.status(400) .status(400)
@@ -2394,33 +2402,30 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
} }
// 处理分组的变更 // 处理分组的变更
if (updates.accountType !== undefined) { if (mappedUpdates.accountType !== undefined) {
// 如果之前是分组类型,需要从所有分组中移除 // 如果之前是分组类型,需要从所有分组中移除
if (currentAccount.accountType === 'group') { if (currentAccount.accountType === 'group') {
await accountGroupService.removeAccountFromAllGroups(accountId) await accountGroupService.removeAccountFromAllGroups(accountId)
} }
// 如果新类型是分组,添加到新分组 // 如果新类型是分组,添加到新分组
if (updates.accountType === 'group') { if (mappedUpdates.accountType === 'group') {
// 处理多分组/单分组的兼容性 // 处理多分组/单分组的兼容性
if (Object.prototype.hasOwnProperty.call(updates, 'groupIds')) { if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'groupIds')) {
if (updates.groupIds && updates.groupIds.length > 0) { if (mappedUpdates.groupIds && mappedUpdates.groupIds.length > 0) {
// 使用多分组设置 // 使用多分组设置
await accountGroupService.setAccountGroups(accountId, updates.groupIds, 'claude') await accountGroupService.setAccountGroups(accountId, mappedUpdates.groupIds, 'claude')
} else { } else {
// groupIds 为空数组,从所有分组中移除 // groupIds 为空数组,从所有分组中移除
await accountGroupService.removeAccountFromAllGroups(accountId) await accountGroupService.removeAccountFromAllGroups(accountId)
} }
} else if (updates.groupId) { } else if (mappedUpdates.groupId) {
// 兼容单分组模式 // 兼容单分组模式
await accountGroupService.addAccountToGroup(accountId, updates.groupId, 'claude') await accountGroupService.addAccountToGroup(accountId, mappedUpdates.groupId, 'claude')
} }
} }
} }
// 映射字段名前端的expiresAt -> 后端的subscriptionExpiresAt
const mappedUpdates = mapExpiryField(updates, 'Claude', accountId)
await claudeAccountService.updateAccount(accountId, mappedUpdates) await claudeAccountService.updateAccount(accountId, mappedUpdates)
logger.success(`📝 Admin updated Claude account: ${accountId}`) logger.success(`📝 Admin updated Claude account: ${accountId}`)

View File

@@ -913,6 +913,19 @@ class CcrAccountService {
throw error throw error
} }
} }
/**
* ⏰ 检查账户订阅是否过期
* @param {Object} account - 账户对象
* @returns {boolean} - true: 已过期, false: 未过期
*/
isSubscriptionExpired(account) {
if (!account.subscriptionExpiresAt) {
return false // 未设置视为永不过期
}
const expiryDate = new Date(account.subscriptionExpiresAt)
return expiryDate <= new Date()
}
} }
module.exports = new CcrAccountService() module.exports = new CcrAccountService()

View File

@@ -1254,6 +1254,19 @@ class ClaudeConsoleAccountService {
throw error throw error
} }
} }
/**
* ⏰ 检查账户订阅是否过期
* @param {Object} account - 账户对象
* @returns {boolean} - true: 已过期, false: 未过期
*/
isSubscriptionExpired(account) {
if (!account.subscriptionExpiresAt) {
return false // 未设置视为永不过期
}
const expiryDate = new Date(account.subscriptionExpiresAt)
return expiryDate <= new Date()
}
} }
module.exports = new ClaudeConsoleAccountService() module.exports = new ClaudeConsoleAccountService()

View File

@@ -546,16 +546,12 @@ class UnifiedClaudeScheduler {
} }
// 检查订阅是否过期 // 检查订阅是否过期
if (account.subscriptionExpiresAt) { if (claudeConsoleAccountService.isSubscriptionExpired(account)) {
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug( logger.debug(
`⏰ Claude Console account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}` `⏰ Claude Console account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
) )
continue continue
} }
}
// 主动触发一次额度检查,确保状态即时生效 // 主动触发一次额度检查,确保状态即时生效
try { try {
@@ -655,16 +651,12 @@ class UnifiedClaudeScheduler {
} }
// 检查订阅是否过期 // 检查订阅是否过期
if (account.subscriptionExpiresAt) { if (ccrAccountService.isSubscriptionExpired(account)) {
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug( logger.debug(
`⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}` `⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
) )
continue continue
} }
}
// 检查是否被限流 // 检查是否被限流
const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id) const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id)
@@ -799,16 +791,12 @@ class UnifiedClaudeScheduler {
return false return false
} }
// 检查订阅是否过期 // 检查订阅是否过期
if (account.subscriptionExpiresAt) { if (claudeConsoleAccountService.isSubscriptionExpired(account)) {
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug( logger.debug(
`⏰ Claude Console account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)` `⏰ Claude Console account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)`
) )
return false return false
} }
}
// 检查是否超额 // 检查是否超额
try { try {
await claudeConsoleAccountService.checkQuotaUsage(accountId) await claudeConsoleAccountService.checkQuotaUsage(accountId)
@@ -868,16 +856,12 @@ class UnifiedClaudeScheduler {
return false return false
} }
// 检查订阅是否过期 // 检查订阅是否过期
if (account.subscriptionExpiresAt) { if (ccrAccountService.isSubscriptionExpired(account)) {
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug( logger.debug(
`⏰ CCR account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)` `⏰ CCR account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)`
) )
return false return false
} }
}
// 检查是否超额 // 检查是否超额
try { try {
await ccrAccountService.checkQuotaUsage(accountId) await ccrAccountService.checkQuotaUsage(accountId)
@@ -1400,16 +1384,12 @@ class UnifiedClaudeScheduler {
} }
// 检查订阅是否过期 // 检查订阅是否过期
if (account.subscriptionExpiresAt) { if (ccrAccountService.isSubscriptionExpired(account)) {
const expiryDate = new Date(account.subscriptionExpiresAt)
const now = new Date()
if (expiryDate <= now) {
logger.debug( logger.debug(
`⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}` `⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
) )
continue continue
} }
}
// 检查是否被限流或超额 // 检查是否被限流或超额
const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id) const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id)