@@ -177,6 +177,18 @@ type AccountListFilters struct {
177177 PrivacyMode string
178178}
179179
180+ type BulkUpdateOwnedAccountsInput struct {
181+ AccountIDs []int64
182+ Concurrency * int
183+ Priority * int
184+ LoadFactor * int
185+ Status string
186+ Schedulable * bool
187+ GroupIDs * []int64
188+ Credentials map [string ]any
189+ Extra map [string ]any
190+ }
191+
180192// NewAccountService 创建账号服务实例
181193func NewAccountService (
182194 accountRepo AccountRepository ,
@@ -640,6 +652,157 @@ func (s *AccountService) DeleteOwned(ctx context.Context, ownerUserID, accountID
640652// Delete 删除账号
641653// 优化:使用 ExistsByID 替代 GetByID 进行存在性检查,
642654// 避免加载完整账号对象及其关联数据,提升删除操作的性能
655+ func normalizeOwnedBulkAccountIDs (ids []int64 ) []int64 {
656+ if len (ids ) == 0 {
657+ return nil
658+ }
659+ out := make ([]int64 , 0 , len (ids ))
660+ seen := make (map [int64 ]struct {}, len (ids ))
661+ for _ , id := range ids {
662+ if id <= 0 {
663+ continue
664+ }
665+ if _ , ok := seen [id ]; ok {
666+ continue
667+ }
668+ seen [id ] = struct {}{}
669+ out = append (out , id )
670+ }
671+ return out
672+ }
673+
674+ func normalizeOwnedBulkStatus (status string ) (string , error ) {
675+ normalized := strings .ToLower (strings .TrimSpace (status ))
676+ if normalized == "" {
677+ return "" , nil
678+ }
679+ if normalized == "inactive" {
680+ normalized = StatusDisabled
681+ }
682+ switch normalized {
683+ case StatusActive , StatusDisabled :
684+ return normalized , nil
685+ default :
686+ return "" , fmt .Errorf ("invalid account status: %s" , status )
687+ }
688+ }
689+
690+ func mergeAccountMap (current map [string ]any , updates map [string ]any ) map [string ]any {
691+ if len (current ) == 0 && len (updates ) == 0 {
692+ return nil
693+ }
694+ next := make (map [string ]any , len (current )+ len (updates ))
695+ for key , value := range current {
696+ next [key ] = value
697+ }
698+ for key , value := range updates {
699+ next [key ] = value
700+ }
701+ return next
702+ }
703+
704+ func (s * AccountService ) BulkUpdateOwned (ctx context.Context , ownerUserID int64 , input * BulkUpdateOwnedAccountsInput ) (* BulkUpdateAccountsResult , error ) {
705+ if ownerUserID <= 0 {
706+ return nil , ErrUserNotFound
707+ }
708+ if input == nil {
709+ return nil , ErrAccountNilInput
710+ }
711+
712+ accountIDs := normalizeOwnedBulkAccountIDs (input .AccountIDs )
713+ result := & BulkUpdateAccountsResult {
714+ SuccessIDs : make ([]int64 , 0 , len (accountIDs )),
715+ FailedIDs : make ([]int64 , 0 , len (accountIDs )),
716+ Results : make ([]BulkUpdateAccountResult , 0 , len (accountIDs )),
717+ }
718+ if len (accountIDs ) == 0 {
719+ return result , nil
720+ }
721+
722+ if input .Concurrency != nil && * input .Concurrency <= 0 {
723+ return nil , fmt .Errorf ("concurrency must be > 0" )
724+ }
725+ if input .Priority != nil && * input .Priority <= 0 {
726+ return nil , fmt .Errorf ("priority must be > 0" )
727+ }
728+ if input .LoadFactor != nil && * input .LoadFactor > 10000 {
729+ return nil , fmt .Errorf ("load_factor must be <= 10000" )
730+ }
731+ status , err := normalizeOwnedBulkStatus (input .Status )
732+ if err != nil {
733+ return nil , err
734+ }
735+
736+ accounts , err := s .accountRepo .GetByIDs (ctx , accountIDs )
737+ if err != nil {
738+ return nil , fmt .Errorf ("get accounts: %w" , err )
739+ }
740+ accountsByID := make (map [int64 ]* Account , len (accounts ))
741+ for _ , account := range accounts {
742+ if account != nil {
743+ accountsByID [account .ID ] = account
744+ }
745+ }
746+
747+ validatedGroupIDsByAccount := make (map [int64 ][]int64 , len (accountIDs ))
748+ for _ , accountID := range accountIDs {
749+ account := accountsByID [accountID ]
750+ if account == nil || account .OwnerUserID == nil || * account .OwnerUserID != ownerUserID {
751+ return nil , ErrAccountNotFound
752+ }
753+
754+ nextCredentials := mergeAccountMap (account .Credentials , input .Credentials )
755+ nextExtra := mergeAccountMap (account .Extra , input .Extra )
756+ if err := validateOwnedAccountSource (account .Type , nextCredentials , nextExtra ); err != nil {
757+ return nil , err
758+ }
759+
760+ if input .GroupIDs != nil {
761+ groupIDs , err := s .validateOwnedAccountGroupBinding (ctx , ownerUserID , account .Platform , account .Type , * input .GroupIDs )
762+ if err != nil {
763+ return nil , err
764+ }
765+ validatedGroupIDsByAccount [accountID ] = groupIDs
766+ }
767+ }
768+
769+ repoUpdates := AccountBulkUpdate {
770+ Concurrency : input .Concurrency ,
771+ Priority : input .Priority ,
772+ LoadFactor : input .LoadFactor ,
773+ Schedulable : input .Schedulable ,
774+ Credentials : input .Credentials ,
775+ Extra : input .Extra ,
776+ }
777+ if status != "" {
778+ repoUpdates .Status = & status
779+ }
780+
781+ if _ , err := s .accountRepo .BulkUpdate (ctx , accountIDs , repoUpdates ); err != nil {
782+ return nil , fmt .Errorf ("bulk update accounts: %w" , err )
783+ }
784+
785+ for _ , accountID := range accountIDs {
786+ entry := BulkUpdateAccountResult {AccountID : accountID }
787+ if input .GroupIDs != nil {
788+ if err := s .accountRepo .BindGroups (ctx , accountID , validatedGroupIDsByAccount [accountID ]); err != nil {
789+ entry .Success = false
790+ entry .Error = err .Error ()
791+ result .Failed ++
792+ result .FailedIDs = append (result .FailedIDs , accountID )
793+ result .Results = append (result .Results , entry )
794+ continue
795+ }
796+ }
797+ entry .Success = true
798+ result .Success ++
799+ result .SuccessIDs = append (result .SuccessIDs , accountID )
800+ result .Results = append (result .Results , entry )
801+ }
802+
803+ return result , nil
804+ }
805+
643806func (s * AccountService ) Delete (ctx context.Context , id int64 ) error {
644807 // 使用轻量级的存在性检查,而非加载完整账号对象
645808 exists , err := s .accountRepo .ExistsByID (ctx , id )
0 commit comments