feat: 优化专属账号删除逻辑

This commit is contained in:
shaw
2025-09-23 15:48:38 +08:00
parent bd091ede61
commit 0ba048aced
3 changed files with 200 additions and 31 deletions

View File

@@ -2256,6 +2256,9 @@ router.delete('/claude-accounts/:accountId', authenticateAdmin, async (req, res)
try {
const { accountId } = req.params
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(accountId, 'claude')
// 获取账户信息以检查是否在分组中
const account = await claudeAccountService.getAccount(accountId)
if (account && account.accountType === 'group') {
@@ -2267,8 +2270,17 @@ router.delete('/claude-accounts/:accountId', authenticateAdmin, async (req, res)
await claudeAccountService.deleteAccount(accountId)
logger.success(`🗑️ Admin deleted Claude account: ${accountId}`)
return res.json({ success: true, message: 'Claude account deleted successfully' })
let message = 'Claude账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted Claude account: ${accountId}, unbound ${unboundCount} keys`)
return res.json({
success: true,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('❌ Failed to delete Claude account:', error)
return res
@@ -2634,6 +2646,9 @@ router.delete('/claude-console-accounts/:accountId', authenticateAdmin, async (r
try {
const { accountId } = req.params
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(accountId, 'claude-console')
// 获取账户信息以检查是否在分组中
const account = await claudeConsoleAccountService.getAccount(accountId)
if (account && account.accountType === 'group') {
@@ -2645,8 +2660,19 @@ router.delete('/claude-console-accounts/:accountId', authenticateAdmin, async (r
await claudeConsoleAccountService.deleteAccount(accountId)
logger.success(`🗑️ Admin deleted Claude Console account: ${accountId}`)
return res.json({ success: true, message: 'Claude Console account deleted successfully' })
let message = 'Claude Console账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(
`🗑️ Admin deleted Claude Console account: ${accountId}, unbound ${unboundCount} keys`
)
return res.json({
success: true,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('❌ Failed to delete Claude Console account:', error)
return res
@@ -3028,6 +3054,9 @@ router.delete('/ccr-accounts/:accountId', authenticateAdmin, async (req, res) =>
try {
const { accountId } = req.params
// 尝试自动解绑CCR账户实际上不会绑定API Key但保持代码一致性
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(accountId, 'ccr')
// 获取账户信息以检查是否在分组中
const account = await ccrAccountService.getAccount(accountId)
if (account && account.accountType === 'group') {
@@ -3039,8 +3068,18 @@ router.delete('/ccr-accounts/:accountId', authenticateAdmin, async (req, res) =>
await ccrAccountService.deleteAccount(accountId)
let message = 'CCR账号已成功删除'
if (unboundCount > 0) {
// 理论上不会发生,但保持消息格式一致
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted CCR account: ${accountId}`)
return res.json({ success: true, message: 'CCR account deleted successfully' })
return res.json({
success: true,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('❌ Failed to delete CCR account:', error)
return res.status(500).json({ error: 'Failed to delete CCR account', message: error.message })
@@ -3384,6 +3423,9 @@ router.delete('/bedrock-accounts/:accountId', authenticateAdmin, async (req, res
try {
const { accountId } = req.params
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(accountId, 'bedrock')
const result = await bedrockAccountService.deleteAccount(accountId)
if (!result.success) {
@@ -3392,8 +3434,17 @@ router.delete('/bedrock-accounts/:accountId', authenticateAdmin, async (req, res
.json({ error: 'Failed to delete Bedrock account', message: result.error })
}
logger.success(`🗑️ Admin deleted Bedrock account: ${accountId}`)
return res.json({ success: true, message: 'Bedrock account deleted successfully' })
let message = 'Bedrock账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted Bedrock account: ${accountId}, unbound ${unboundCount} keys`)
return res.json({
success: true,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('❌ Failed to delete Bedrock account:', error)
return res
@@ -3830,6 +3881,9 @@ router.delete('/gemini-accounts/:accountId', authenticateAdmin, async (req, res)
try {
const { accountId } = req.params
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(accountId, 'gemini')
// 获取账户信息以检查是否在分组中
const account = await geminiAccountService.getAccount(accountId)
if (account && account.accountType === 'group') {
@@ -3841,8 +3895,17 @@ router.delete('/gemini-accounts/:accountId', authenticateAdmin, async (req, res)
await geminiAccountService.deleteAccount(accountId)
logger.success(`🗑️ Admin deleted Gemini account: ${accountId}`)
return res.json({ success: true, message: 'Gemini account deleted successfully' })
let message = 'Gemini账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted Gemini account: ${accountId}, unbound ${unboundCount} keys`)
return res.json({
success: true,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('❌ Failed to delete Gemini account:', error)
return res.status(500).json({ error: 'Failed to delete account', message: error.message })
@@ -6692,6 +6755,9 @@ router.delete('/openai-accounts/:id', authenticateAdmin, async (req, res) => {
})
}
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'openai')
// 如果账户在分组中,从分组中移除
if (account.accountType === 'group') {
const group = await accountGroupService.getAccountGroup(id)
@@ -6702,11 +6768,19 @@ router.delete('/openai-accounts/:id', authenticateAdmin, async (req, res) => {
await openaiAccountService.deleteAccount(id)
logger.success(`✅ 删除 OpenAI 账户成功: ${account.name} (ID: ${id})`)
let message = 'OpenAI账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(
`✅ 删除 OpenAI 账户成功: ${account.name} (ID: ${id}), unbound ${unboundCount} keys`
)
return res.json({
success: true,
message: '账户删除成功'
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('删除 OpenAI 账户失败:', error)
@@ -7055,11 +7129,22 @@ router.delete('/azure-openai-accounts/:id', authenticateAdmin, async (req, res)
try {
const { id } = req.params
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'azure_openai')
await azureOpenaiAccountService.deleteAccount(id)
let message = 'Azure OpenAI账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted Azure OpenAI account: ${id}, unbound ${unboundCount} keys`)
res.json({
success: true,
message: 'Azure OpenAI account deleted successfully'
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('Failed to delete Azure OpenAI account:', error)
@@ -7424,6 +7509,9 @@ router.delete('/openai-responses-accounts/:id', authenticateAdmin, async (req, r
})
}
// 自动解绑所有绑定的 API Keys
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'openai-responses')
// 检查是否在分组中
const groups = await accountGroupService.getAllGroups()
for (const group of groups) {
@@ -7434,7 +7522,20 @@ router.delete('/openai-responses-accounts/:id', authenticateAdmin, async (req, r
}
const result = await openaiResponsesAccountService.deleteAccount(id)
res.json({ success: true, ...result })
let message = 'OpenAI-Responses账号已成功删除'
if (unboundCount > 0) {
message += `${unboundCount} 个 API Key 已切换为共享池模式`
}
logger.success(`🗑️ Admin deleted OpenAI-Responses account: ${id}, unbound ${unboundCount} keys`)
res.json({
success: true,
...result,
message,
unboundKeys: unboundCount
})
} catch (error) {
logger.error('Failed to delete OpenAI-Responses account:', error)
res.status(500).json({

View File

@@ -1328,6 +1328,70 @@ class ApiKeyService {
}
}
// 🔓 解绑账号从所有API Keys
async unbindAccountFromAllKeys(accountId, accountType) {
try {
// 账号类型与字段的映射关系
const fieldMap = {
claude: 'claudeAccountId',
'claude-console': 'claudeConsoleAccountId',
gemini: 'geminiAccountId',
openai: 'openaiAccountId',
'openai-responses': 'openaiAccountId', // 特殊处理,带 responses: 前缀
azure_openai: 'azureOpenaiAccountId',
bedrock: 'bedrockAccountId',
ccr: null // CCR 账号没有对应的 API Key 字段
}
const field = fieldMap[accountType]
if (!field) {
logger.info(`账号类型 ${accountType} 不需要解绑 API Key`)
return 0
}
// 获取所有API Keys
const allKeys = await this.getAllApiKeys()
// 筛选绑定到此账号的 API Keys
let boundKeys = []
if (accountType === 'openai-responses') {
// OpenAI-Responses 特殊处理:查找 openaiAccountId 字段中带 responses: 前缀的
boundKeys = allKeys.filter((key) => key.openaiAccountId === `responses:${accountId}`)
} else {
// 其他账号类型正常匹配
boundKeys = allKeys.filter((key) => key[field] === accountId)
}
// 批量解绑
for (const key of boundKeys) {
const updates = {}
if (accountType === 'openai-responses') {
updates.openaiAccountId = null
} else if (accountType === 'claude-console') {
updates.claudeConsoleAccountId = null
} else {
updates[field] = null
}
await this.updateApiKey(key.id, updates)
logger.info(
`✅ 自动解绑 API Key ${key.id} (${key.name}) 从 ${accountType} 账号 ${accountId}`
)
}
if (boundKeys.length > 0) {
logger.success(
`🔓 成功解绑 ${boundKeys.length} 个 API Key 从 ${accountType} 账号 ${accountId}`
)
}
return boundKeys.length
} catch (error) {
logger.error(`❌ 解绑 API Keys 失败 (${accountType} 账号 ${accountId}):`, error)
return 0
}
}
// 🧹 清理过期的API Keys
async cleanupExpiredKeys() {
try {