From 25c7b0d9f40e2609683702ebe4ac2a04d392a225 Mon Sep 17 00:00:00 2001 From: KnowSky404 Date: Mon, 27 Apr 2026 17:59:49 +0800 Subject: [PATCH] feat: support filter-target account bulk update --- .../internal/handler/admin/account_handler.go | 31 ++++++++- backend/internal/service/admin_service.go | 68 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 7454451a..3c97c753 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -134,7 +134,8 @@ type UpdateAccountRequest struct { // BulkUpdateAccountsRequest represents the payload for bulk editing accounts type BulkUpdateAccountsRequest struct { - AccountIDs []int64 `json:"account_ids" binding:"required,min=1"` + AccountIDs []int64 `json:"account_ids"` + Filters *BulkUpdateAccountFilters `json:"filters"` Name string `json:"name"` ProxyID *int64 `json:"proxy_id"` Concurrency *int `json:"concurrency"` @@ -149,6 +150,15 @@ type BulkUpdateAccountsRequest struct { ConfirmMixedChannelRisk *bool `json:"confirm_mixed_channel_risk"` // 用户确认混合渠道风险 } +type BulkUpdateAccountFilters struct { + Platform string `json:"platform"` + Type string `json:"type"` + Status string `json:"status"` + Group string `json:"group"` + Search string `json:"search"` + PrivacyMode string `json:"privacy_mode"` +} + // CheckMixedChannelRequest represents check mixed channel risk request type CheckMixedChannelRequest struct { Platform string `json:"platform" binding:"required"` @@ -1369,6 +1379,10 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) { response.BadRequest(c, "rate_multiplier must be >= 0") return } + if len(req.AccountIDs) == 0 && req.Filters == nil { + response.BadRequest(c, "account_ids or filters is required") + return + } // base_rpm 输入校验:负值归零,超过 10000 截断 sanitizeExtraBaseRPM(req.Extra) @@ -1394,6 +1408,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) { result, err := h.adminService.BulkUpdateAccounts(c.Request.Context(), &service.BulkUpdateAccountsInput{ AccountIDs: req.AccountIDs, + Filters: toServiceBulkUpdateAccountFilters(req.Filters), Name: req.Name, ProxyID: req.ProxyID, Concurrency: req.Concurrency, @@ -1429,6 +1444,20 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) { response.Success(c, result) } +func toServiceBulkUpdateAccountFilters(filters *BulkUpdateAccountFilters) *service.BulkUpdateAccountFilters { + if filters == nil { + return nil + } + return &service.BulkUpdateAccountFilters{ + Platform: filters.Platform, + Type: filters.Type, + Status: filters.Status, + Group: filters.Group, + Search: filters.Search, + PrivacyMode: filters.PrivacyMode, + } +} + // ========== OAuth Handlers ========== // GenerateAuthURLRequest represents the request for generating auth URL diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index 434f1f38..86777dc9 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -9,6 +9,7 @@ import ( "log/slog" "net/http" "sort" + "strconv" "strings" "time" @@ -291,6 +292,7 @@ type UpdateAccountInput struct { // BulkUpdateAccountsInput describes the payload for bulk updating accounts. type BulkUpdateAccountsInput struct { AccountIDs []int64 + Filters *BulkUpdateAccountFilters Name string ProxyID *int64 Concurrency *int @@ -307,6 +309,15 @@ type BulkUpdateAccountsInput struct { SkipMixedChannelCheck bool } +type BulkUpdateAccountFilters struct { + Platform string + Type string + Status string + Group string + Search string + PrivacyMode string +} + // BulkUpdateAccountResult captures the result for a single account update. type BulkUpdateAccountResult struct { AccountID int64 `json:"account_id"` @@ -2286,6 +2297,14 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U // BulkUpdateAccounts updates multiple accounts in one request. // It merges credentials/extra keys instead of overwriting the whole object. func (s *adminServiceImpl) BulkUpdateAccounts(ctx context.Context, input *BulkUpdateAccountsInput) (*BulkUpdateAccountsResult, error) { + if len(input.AccountIDs) == 0 && input.Filters != nil { + accountIDs, err := s.resolveBulkUpdateTargetIDs(ctx, input.Filters) + if err != nil { + return nil, err + } + input.AccountIDs = accountIDs + } + result := &BulkUpdateAccountsResult{ SuccessIDs: make([]int64, 0, len(input.AccountIDs)), FailedIDs: make([]int64, 0, len(input.AccountIDs)), @@ -2401,6 +2420,55 @@ func (s *adminServiceImpl) BulkUpdateAccounts(ctx context.Context, input *BulkUp return result, nil } +func (s *adminServiceImpl) resolveBulkUpdateTargetIDs(ctx context.Context, filters *BulkUpdateAccountFilters) ([]int64, error) { + if filters == nil { + return nil, nil + } + + groupID := int64(0) + switch strings.TrimSpace(filters.Group) { + case "": + case "ungrouped": + groupID = AccountListGroupUngrouped + default: + parsedGroupID, err := strconv.ParseInt(strings.TrimSpace(filters.Group), 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid group filter: %w", err) + } + groupID = parsedGroupID + } + + const pageSize = 500 + page := 1 + accountIDs := make([]int64, 0, pageSize) + + for { + accounts, total, err := s.ListAccounts( + ctx, + page, + pageSize, + filters.Platform, + filters.Type, + filters.Status, + filters.Search, + groupID, + filters.PrivacyMode, + "", + "", + ) + if err != nil { + return nil, err + } + for _, account := range accounts { + accountIDs = append(accountIDs, account.ID) + } + if int64(len(accountIDs)) >= total || len(accounts) == 0 { + return accountIDs, nil + } + page++ + } +} + func (s *adminServiceImpl) DeleteAccount(ctx context.Context, id int64) error { if err := s.accountRepo.Delete(ctx, id); err != nil { return err