package rms import ( "fmt" "sync" "github.com/google/uuid" "go.uber.org/zap" "rmser/internal/domain/account" "rmser/pkg/crypto" "rmser/pkg/logger" ) // Factory управляет созданием и переиспользованием клиентов RMS type Factory struct { accountRepo account.Repository cryptoManager *crypto.CryptoManager // Кэш активных клиентов: ServerID -> *Client mu sync.RWMutex clients map[uuid.UUID]*Client } func NewFactory(repo account.Repository, cm *crypto.CryptoManager) *Factory { return &Factory{ accountRepo: repo, cryptoManager: cm, clients: make(map[uuid.UUID]*Client), } } // GetClientByServerID возвращает готовый клиент для конкретного сервера func (f *Factory) GetClientByServerID(serverID uuid.UUID) (ClientI, error) { // 1. Пытаемся найти в кэше (быстрый путь) f.mu.RLock() client, exists := f.clients[serverID] f.mu.RUnlock() if exists { return client, nil } // 2. Если нет в кэше - ищем в БД (медленный путь) // Здесь нам нужен метод GetServerByID, но в репо есть только GetAll/GetActive. // Для MVP загрузим все сервера юзера и найдем нужный, либо (лучше) добавим метод в репо позже. // ПОКА: предполагаем, что factory используется в контексте User, поэтому лучше метод GetClientForUser return nil, fmt.Errorf("client not found in cache (use GetClientForUser or implement GetServerByID)") } // GetClientForUser находит активный сервер пользователя и возвращает клиент func (f *Factory) GetClientForUser(userID uuid.UUID) (ClientI, error) { // 1. Получаем настройки активного сервера из БД server, err := f.accountRepo.GetActiveServer(userID) if err != nil { return nil, fmt.Errorf("db error: %w", err) } if server == nil { return nil, fmt.Errorf("у пользователя нет активного сервера RMS") } // 2. Проверяем кэш по ID сервера f.mu.RLock() cachedClient, exists := f.clients[server.ID] f.mu.RUnlock() if exists { return cachedClient, nil } // 3. Создаем новый клиент под блокировкой (защита от гонки) f.mu.Lock() defer f.mu.Unlock() // Double check if cachedClient, exists := f.clients[server.ID]; exists { return cachedClient, nil } // Расшифровка пароля plainPass, err := f.cryptoManager.Decrypt(server.EncryptedPassword) if err != nil { return nil, fmt.Errorf("ошибка расшифровки пароля RMS: %w", err) } // Создание клиента newClient := NewClient(server.BaseURL, server.Login, plainPass) // Можно сразу проверить авторизацию (опционально, но полезно для fail-fast) // if err := newClient.Auth(); err != nil { ... } // Но лучше лениво, чтобы не тормозить старт. f.clients[server.ID] = newClient // Запускаем очистку старых клиентов из мапы? Пока нет, iiko токены живут не вечно, // но структура Client легкая. Можно добавить TTL позже. logger.Log.Info("RMS Factory: Client created and cached", zap.String("server_name", server.Name), zap.String("user_id", userID.String())) return newClient, nil } // CreateClientFromRawCredentials создает клиент без сохранения в кэш (для тестов подключения) func (f *Factory) CreateClientFromRawCredentials(url, login, password string) *Client { return NewClient(url, login, password) } // ClearCache сбрасывает кэш для сервера (например, при смене пароля) func (f *Factory) ClearCache(serverID uuid.UUID) { f.mu.Lock() defer f.mu.Unlock() delete(f.clients, serverID) }