cc / src /services /accountGroupService.js
hequ's picture
Upload 221 files
69b897d verified
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()