package service import ( "context" "log/slog" "net/http" "sync" "time" "juhe-factory/api/internal/provider/apimart" "github.com/google/uuid" ) type balanceChannel struct { ID uuid.UUID Name string BaseURL string APIKeyCiphertext string } func (s *AdminData) triggerChannelBalanceSync() { if !s.balanceSyncing.CompareAndSwap(false, true) { return } go func() { defer s.balanceSyncing.Store(false) s.syncChannelBalances(context.Background()) }() } func (s *AdminData) syncChannelBalances(ctx context.Context) { if s.Encryptor == nil { return } var channels []balanceChannel if err := s.DB.Table("channels").Select("id,name,base_url,api_key_ciphertext"). Where("deleted_at IS NULL AND api_key_ciphertext IS NOT NULL AND api_key_ciphertext<>''"). Find(&channels).Error; err != nil { slog.Warn("读取待同步渠道余额失败", "error", err) return } syncCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() client := s.HTTPClient if client == nil { client = http.DefaultClient } var wait sync.WaitGroup for _, channel := range channels { channel := channel wait.Add(1) go func() { defer wait.Done() apiKey, err := s.Encryptor.Decrypt(channel.APIKeyCiphertext) if err != nil { slog.Warn("解密渠道 API Key 失败", "channel", channel.Name, "error", err) return } balance, err := apimart.NewClient(client).Balance(syncCtx, channel.BaseURL, apiKey) if err != nil { slog.Warn("同步渠道余额失败", "channel", channel.Name, "error", err) return } remainBalance := balance.RemainBalance if balance.UnlimitedQuota { remainBalance = -1 } if err := s.DB.Exec(`INSERT INTO channel_balance_snapshots(channel_id,balance,currency,synced_at) VALUES(?,?,?,?)`, channel.ID, remainBalance, "USD", time.Now()).Error; err != nil { slog.Warn("保存渠道余额快照失败", "channel", channel.Name, "error", err) } }() } wait.Wait() }