|
|
const { v4: uuidv4 } = require('uuid') |
|
|
const logger = require('../utils/logger') |
|
|
const redis = require('../models/redis') |
|
|
|
|
|
class AccountGroupService { |
|
|
constructor() { |
|
|
this.GROUPS_KEY = 'account_groups' |
|
|
this.GROUP_PREFIX = 'account_group:' |
|
|
this.GROUP_MEMBERS_PREFIX = 'account_group_members:' |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async createGroup(groupData) { |
|
|
try { |
|
|
const { name, platform, description = '' } = groupData |
|
|
|
|
|
|
|
|
if (!name || !platform) { |
|
|
throw new Error('分组名称和平台类型为必填项') |
|
|
} |
|
|
|
|
|
|
|
|
if (!['claude', 'gemini', 'openai', 'droid'].includes(platform)) { |
|
|
throw new Error('平台类型必须是 claude、gemini、openai 或 droid') |
|
|
} |
|
|
|
|
|
const client = redis.getClientSafe() |
|
|
const groupId = uuidv4() |
|
|
const now = new Date().toISOString() |
|
|
|
|
|
const group = { |
|
|
id: groupId, |
|
|
name, |
|
|
platform, |
|
|
description, |
|
|
createdAt: now, |
|
|
updatedAt: now |
|
|
} |
|
|
|
|
|
|
|
|
await client.hmset(`${this.GROUP_PREFIX}${groupId}`, group) |
|
|
|
|
|
|
|
|
await client.sadd(this.GROUPS_KEY, groupId) |
|
|
|
|
|
logger.success(`✅ 创建账户分组成功: ${name} (${platform})`) |
|
|
|
|
|
return group |
|
|
} catch (error) { |
|
|
logger.error('❌ 创建账户分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async updateGroup(groupId, updates) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const groupKey = `${this.GROUP_PREFIX}${groupId}` |
|
|
|
|
|
|
|
|
const exists = await client.exists(groupKey) |
|
|
if (!exists) { |
|
|
throw new Error('分组不存在') |
|
|
} |
|
|
|
|
|
|
|
|
const existingGroup = await client.hgetall(groupKey) |
|
|
|
|
|
|
|
|
if (updates.platform && updates.platform !== existingGroup.platform) { |
|
|
throw new Error('不能修改分组的平台类型') |
|
|
} |
|
|
|
|
|
|
|
|
const updateData = { |
|
|
...updates, |
|
|
updatedAt: new Date().toISOString() |
|
|
} |
|
|
|
|
|
|
|
|
delete updateData.id |
|
|
delete updateData.platform |
|
|
delete updateData.createdAt |
|
|
|
|
|
|
|
|
await client.hmset(groupKey, updateData) |
|
|
|
|
|
|
|
|
const updatedGroup = await client.hgetall(groupKey) |
|
|
|
|
|
logger.success(`✅ 更新账户分组成功: ${updatedGroup.name}`) |
|
|
|
|
|
return updatedGroup |
|
|
} catch (error) { |
|
|
logger.error('❌ 更新账户分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async deleteGroup(groupId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
|
|
|
|
|
|
const group = await this.getGroup(groupId) |
|
|
if (!group) { |
|
|
throw new Error('分组不存在') |
|
|
} |
|
|
|
|
|
|
|
|
const members = await this.getGroupMembers(groupId) |
|
|
if (members.length > 0) { |
|
|
throw new Error('分组内还有账户,无法删除') |
|
|
} |
|
|
|
|
|
|
|
|
const boundApiKeys = await this.getApiKeysUsingGroup(groupId) |
|
|
if (boundApiKeys.length > 0) { |
|
|
throw new Error('还有API Key使用此分组,无法删除') |
|
|
} |
|
|
|
|
|
|
|
|
await client.del(`${this.GROUP_PREFIX}${groupId}`) |
|
|
await client.del(`${this.GROUP_MEMBERS_PREFIX}${groupId}`) |
|
|
|
|
|
|
|
|
await client.srem(this.GROUPS_KEY, groupId) |
|
|
|
|
|
logger.success(`✅ 删除账户分组成功: ${group.name}`) |
|
|
} catch (error) { |
|
|
logger.error('❌ 删除账户分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getGroup(groupId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const groupData = await client.hgetall(`${this.GROUP_PREFIX}${groupId}`) |
|
|
|
|
|
if (!groupData || Object.keys(groupData).length === 0) { |
|
|
return null |
|
|
} |
|
|
|
|
|
|
|
|
const memberCount = await client.scard(`${this.GROUP_MEMBERS_PREFIX}${groupId}`) |
|
|
|
|
|
return { |
|
|
...groupData, |
|
|
memberCount: memberCount || 0 |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取分组详情失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getAllGroups(platform = null) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const groupIds = await client.smembers(this.GROUPS_KEY) |
|
|
|
|
|
const groups = [] |
|
|
for (const groupId of groupIds) { |
|
|
const group = await this.getGroup(groupId) |
|
|
if (group) { |
|
|
|
|
|
if (!platform || group.platform === platform) { |
|
|
groups.push(group) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
groups.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) |
|
|
|
|
|
return groups |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取分组列表失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async addAccountToGroup(accountId, groupId, accountPlatform) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
|
|
|
|
|
|
const group = await this.getGroup(groupId) |
|
|
if (!group) { |
|
|
throw new Error('分组不存在') |
|
|
} |
|
|
|
|
|
|
|
|
const normalizedAccountPlatform = |
|
|
accountPlatform === 'claude-console' ? 'claude' : accountPlatform |
|
|
if (normalizedAccountPlatform !== group.platform) { |
|
|
throw new Error('账户平台与分组平台不匹配') |
|
|
} |
|
|
|
|
|
|
|
|
await client.sadd(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId) |
|
|
|
|
|
logger.success(`✅ 添加账户到分组成功: ${accountId} -> ${group.name}`) |
|
|
} catch (error) { |
|
|
logger.error('❌ 添加账户到分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async removeAccountFromGroup(accountId, groupId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
|
|
|
|
|
|
await client.srem(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId) |
|
|
|
|
|
logger.success(`✅ 从分组移除账户成功: ${accountId}`) |
|
|
} catch (error) { |
|
|
logger.error('❌ 从分组移除账户失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getGroupMembers(groupId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const members = await client.smembers(`${this.GROUP_MEMBERS_PREFIX}${groupId}`) |
|
|
return members || [] |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取分组成员失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async isGroupEmpty(groupId) { |
|
|
try { |
|
|
const members = await this.getGroupMembers(groupId) |
|
|
return members.length === 0 |
|
|
} catch (error) { |
|
|
logger.error('❌ 检查分组是否为空失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getApiKeysUsingGroup(groupId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const groupKey = `group:${groupId}` |
|
|
|
|
|
|
|
|
const apiKeyIds = await client.smembers('api_keys') |
|
|
const boundApiKeys = [] |
|
|
|
|
|
for (const keyId of apiKeyIds) { |
|
|
const keyData = await client.hgetall(`api_key:${keyId}`) |
|
|
if ( |
|
|
keyData && |
|
|
(keyData.claudeAccountId === groupKey || |
|
|
keyData.geminiAccountId === groupKey || |
|
|
keyData.openaiAccountId === groupKey || |
|
|
keyData.droidAccountId === groupKey) |
|
|
) { |
|
|
boundApiKeys.push({ |
|
|
id: keyId, |
|
|
name: keyData.name |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
return boundApiKeys |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取使用分组的API Key失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getAccountGroup(accountId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const allGroupIds = await client.smembers(this.GROUPS_KEY) |
|
|
|
|
|
for (const groupId of allGroupIds) { |
|
|
const isMember = await client.sismember(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId) |
|
|
if (isMember) { |
|
|
return await this.getGroup(groupId) |
|
|
} |
|
|
} |
|
|
|
|
|
return null |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取账户所属分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getAccountGroups(accountId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const allGroupIds = await client.smembers(this.GROUPS_KEY) |
|
|
const memberGroups = [] |
|
|
|
|
|
for (const groupId of allGroupIds) { |
|
|
const isMember = await client.sismember(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId) |
|
|
if (isMember) { |
|
|
const group = await this.getGroup(groupId) |
|
|
if (group) { |
|
|
memberGroups.push(group) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
memberGroups.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) |
|
|
|
|
|
return memberGroups |
|
|
} catch (error) { |
|
|
logger.error('❌ 获取账户所属分组列表失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async setAccountGroups(accountId, groupIds, accountPlatform) { |
|
|
try { |
|
|
|
|
|
await this.removeAccountFromAllGroups(accountId) |
|
|
|
|
|
|
|
|
for (const groupId of groupIds) { |
|
|
await this.addAccountToGroup(accountId, groupId, accountPlatform) |
|
|
} |
|
|
|
|
|
logger.success(`✅ 批量设置账户分组成功: ${accountId} -> [${groupIds.join(', ')}]`) |
|
|
} catch (error) { |
|
|
logger.error('❌ 批量设置账户分组失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async removeAccountFromAllGroups(accountId) { |
|
|
try { |
|
|
const client = redis.getClientSafe() |
|
|
const allGroupIds = await client.smembers(this.GROUPS_KEY) |
|
|
|
|
|
for (const groupId of allGroupIds) { |
|
|
await client.srem(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId) |
|
|
} |
|
|
|
|
|
logger.success(`✅ 从所有分组移除账户成功: ${accountId}`) |
|
|
} catch (error) { |
|
|
logger.error('❌ 从所有分组移除账户失败:', error) |
|
|
throw error |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
module.exports = new AccountGroupService() |
|
|
|