- 创建分组时可选择从已有分组复制账号 - 编辑分组时支持同步账号(全量替换操作) - 仅允许选择相同平台的源分组 - 添加完整的数据校验:去重、自引用检查、平台一致性检查 - 前端支持多选源分组,带提示说明操作行为
213 lines
6.2 KiB
Go
213 lines
6.2 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||
)
|
||
|
||
var (
|
||
ErrGroupNotFound = infraerrors.NotFound("GROUP_NOT_FOUND", "group not found")
|
||
ErrGroupExists = infraerrors.Conflict("GROUP_EXISTS", "group name already exists")
|
||
)
|
||
|
||
type GroupRepository interface {
|
||
Create(ctx context.Context, group *Group) error
|
||
GetByID(ctx context.Context, id int64) (*Group, error)
|
||
GetByIDLite(ctx context.Context, id int64) (*Group, error)
|
||
Update(ctx context.Context, group *Group) error
|
||
Delete(ctx context.Context, id int64) error
|
||
DeleteCascade(ctx context.Context, id int64) ([]int64, error)
|
||
|
||
List(ctx context.Context, params pagination.PaginationParams) ([]Group, *pagination.PaginationResult, error)
|
||
ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, status, search string, isExclusive *bool) ([]Group, *pagination.PaginationResult, error)
|
||
ListActive(ctx context.Context) ([]Group, error)
|
||
ListActiveByPlatform(ctx context.Context, platform string) ([]Group, error)
|
||
|
||
ExistsByName(ctx context.Context, name string) (bool, error)
|
||
GetAccountCount(ctx context.Context, groupID int64) (int64, error)
|
||
DeleteAccountGroupsByGroupID(ctx context.Context, groupID int64) (int64, error)
|
||
// GetAccountIDsByGroupIDs 获取多个分组的所有账号 ID(去重)
|
||
GetAccountIDsByGroupIDs(ctx context.Context, groupIDs []int64) ([]int64, error)
|
||
// BindAccountsToGroup 将多个账号绑定到指定分组
|
||
BindAccountsToGroup(ctx context.Context, groupID int64, accountIDs []int64) error
|
||
}
|
||
|
||
// CreateGroupRequest 创建分组请求
|
||
type CreateGroupRequest struct {
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
RateMultiplier float64 `json:"rate_multiplier"`
|
||
IsExclusive bool `json:"is_exclusive"`
|
||
}
|
||
|
||
// UpdateGroupRequest 更新分组请求
|
||
type UpdateGroupRequest struct {
|
||
Name *string `json:"name"`
|
||
Description *string `json:"description"`
|
||
RateMultiplier *float64 `json:"rate_multiplier"`
|
||
IsExclusive *bool `json:"is_exclusive"`
|
||
Status *string `json:"status"`
|
||
}
|
||
|
||
// GroupService 分组管理服务
|
||
type GroupService struct {
|
||
groupRepo GroupRepository
|
||
authCacheInvalidator APIKeyAuthCacheInvalidator
|
||
}
|
||
|
||
// NewGroupService 创建分组服务实例
|
||
func NewGroupService(groupRepo GroupRepository, authCacheInvalidator APIKeyAuthCacheInvalidator) *GroupService {
|
||
return &GroupService{
|
||
groupRepo: groupRepo,
|
||
authCacheInvalidator: authCacheInvalidator,
|
||
}
|
||
}
|
||
|
||
// Create 创建分组
|
||
func (s *GroupService) Create(ctx context.Context, req CreateGroupRequest) (*Group, error) {
|
||
// 检查名称是否已存在
|
||
exists, err := s.groupRepo.ExistsByName(ctx, req.Name)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("check group exists: %w", err)
|
||
}
|
||
if exists {
|
||
return nil, ErrGroupExists
|
||
}
|
||
|
||
// 创建分组
|
||
group := &Group{
|
||
Name: req.Name,
|
||
Description: req.Description,
|
||
Platform: PlatformAnthropic,
|
||
RateMultiplier: req.RateMultiplier,
|
||
IsExclusive: req.IsExclusive,
|
||
Status: StatusActive,
|
||
SubscriptionType: SubscriptionTypeStandard,
|
||
}
|
||
|
||
if err := s.groupRepo.Create(ctx, group); err != nil {
|
||
return nil, fmt.Errorf("create group: %w", err)
|
||
}
|
||
|
||
return group, nil
|
||
}
|
||
|
||
// GetByID 根据ID获取分组
|
||
func (s *GroupService) GetByID(ctx context.Context, id int64) (*Group, error) {
|
||
group, err := s.groupRepo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get group: %w", err)
|
||
}
|
||
return group, nil
|
||
}
|
||
|
||
// List 获取分组列表
|
||
func (s *GroupService) List(ctx context.Context, params pagination.PaginationParams) ([]Group, *pagination.PaginationResult, error) {
|
||
groups, pagination, err := s.groupRepo.List(ctx, params)
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("list groups: %w", err)
|
||
}
|
||
return groups, pagination, nil
|
||
}
|
||
|
||
// ListActive 获取活跃分组列表
|
||
func (s *GroupService) ListActive(ctx context.Context) ([]Group, error) {
|
||
groups, err := s.groupRepo.ListActive(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("list active groups: %w", err)
|
||
}
|
||
return groups, nil
|
||
}
|
||
|
||
// Update 更新分组
|
||
func (s *GroupService) Update(ctx context.Context, id int64, req UpdateGroupRequest) (*Group, error) {
|
||
group, err := s.groupRepo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get group: %w", err)
|
||
}
|
||
|
||
// 更新字段
|
||
if req.Name != nil && *req.Name != group.Name {
|
||
// 检查新名称是否已存在
|
||
exists, err := s.groupRepo.ExistsByName(ctx, *req.Name)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("check group exists: %w", err)
|
||
}
|
||
if exists {
|
||
return nil, ErrGroupExists
|
||
}
|
||
group.Name = *req.Name
|
||
}
|
||
|
||
if req.Description != nil {
|
||
group.Description = *req.Description
|
||
}
|
||
|
||
if req.RateMultiplier != nil {
|
||
group.RateMultiplier = *req.RateMultiplier
|
||
}
|
||
|
||
if req.IsExclusive != nil {
|
||
group.IsExclusive = *req.IsExclusive
|
||
}
|
||
|
||
if req.Status != nil {
|
||
group.Status = *req.Status
|
||
}
|
||
|
||
if err := s.groupRepo.Update(ctx, group); err != nil {
|
||
return nil, fmt.Errorf("update group: %w", err)
|
||
}
|
||
if s.authCacheInvalidator != nil {
|
||
s.authCacheInvalidator.InvalidateAuthCacheByGroupID(ctx, id)
|
||
}
|
||
|
||
return group, nil
|
||
}
|
||
|
||
// Delete 删除分组
|
||
func (s *GroupService) Delete(ctx context.Context, id int64) error {
|
||
// 检查分组是否存在
|
||
_, err := s.groupRepo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return fmt.Errorf("get group: %w", err)
|
||
}
|
||
|
||
if s.authCacheInvalidator != nil {
|
||
s.authCacheInvalidator.InvalidateAuthCacheByGroupID(ctx, id)
|
||
}
|
||
if err := s.groupRepo.Delete(ctx, id); err != nil {
|
||
return fmt.Errorf("delete group: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetStats 获取分组统计信息
|
||
func (s *GroupService) GetStats(ctx context.Context, id int64) (map[string]any, error) {
|
||
group, err := s.groupRepo.GetByID(ctx, id)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get group: %w", err)
|
||
}
|
||
|
||
// 获取账号数量
|
||
accountCount, err := s.groupRepo.GetAccountCount(ctx, id)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get account count: %w", err)
|
||
}
|
||
|
||
stats := map[string]any{
|
||
"id": group.ID,
|
||
"name": group.Name,
|
||
"rate_multiplier": group.RateMultiplier,
|
||
"is_exclusive": group.IsExclusive,
|
||
"status": group.Status,
|
||
"account_count": accountCount,
|
||
}
|
||
|
||
return stats, nil
|
||
}
|