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:' } /** * 创建账户分组 * @param {Object} groupData - 分组数据 * @param {string} groupData.name - 分组名称 * @param {string} groupData.platform - 平台类型 (claude/gemini/openai) * @param {string} groupData.description - 分组描述 * @returns {Object} 创建的分组 */ 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 } } /** * 更新分组信息 * @param {string} groupId - 分组ID * @param {Object} updates - 更新的字段 * @returns {Object} 更新后的分组 */ 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 } } /** * 删除分组 * @param {string} groupId - 分组ID */ 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('分组内还有账户,无法删除') } // 检查是否有API Key绑定此分组 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 } } /** * 获取分组详情 * @param {string} groupId - 分组ID * @returns {Object|null} 分组信息 */ 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 } } /** * 获取所有分组 * @param {string} platform - 平台筛选 (可选) * @returns {Array} 分组列表 */ 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 } } /** * 添加账户到分组 * @param {string} accountId - 账户ID * @param {string} groupId - 分组ID * @param {string} accountPlatform - 账户平台 */ async addAccountToGroup(accountId, groupId, accountPlatform) { try { const client = redis.getClientSafe() // 获取分组信息 const group = await this.getGroup(groupId) if (!group) { throw new Error('分组不存在') } // 验证平台一致性 (Claude和Claude Console视为同一平台) 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 } } /** * 从分组移除账户 * @param {string} accountId - 账户ID * @param {string} groupId - 分组ID */ 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 } } /** * 获取分组成员 * @param {string} groupId - 分组ID * @returns {Array} 成员ID列表 */ 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 } } /** * 检查分组是否为空 * @param {string} groupId - 分组ID * @returns {boolean} 是否为空 */ async isGroupEmpty(groupId) { try { const members = await this.getGroupMembers(groupId) return members.length === 0 } catch (error) { logger.error('❌ 检查分组是否为空失败:', error) throw error } } /** * 获取使用指定分组的API Key列表 * @param {string} groupId - 分组ID * @returns {Array} API Key列表 */ async getApiKeysUsingGroup(groupId) { try { const client = redis.getClientSafe() const groupKey = `group:${groupId}` // 获取所有API Key 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 } } /** * 根据账户ID获取其所属的分组(兼容性方法,返回单个分组) * @param {string} accountId - 账户ID * @returns {Object|null} 分组信息 */ 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 } } /** * 根据账户ID获取其所属的所有分组 * @param {string} accountId - 账户ID * @returns {Array} 分组信息数组 */ 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 } } /** * 批量设置账户的分组 * @param {string} accountId - 账户ID * @param {Array} groupIds - 分组ID数组 * @param {string} accountPlatform - 账户平台 */ 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 } } /** * 从所有分组中移除账户 * @param {string} accountId - 账户ID */ 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()